Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6858ec2
feat(authz): replace bespoke FGA with embedded OpenFGA ReBAC engine
lakhansamani Jun 8, 2026
c64757b
docs(authz): OpenFGA migration plan, agentic-auth design, enterprise …
lakhansamani Jun 8, 2026
ccde4d4
feat(authz): add _fga_list_users/_fga_expand and trust-gated subject …
lakhansamani Jun 8, 2026
39cb463
test(authz): close FGA coverage gaps
lakhansamani Jun 8, 2026
1c1739c
fix(authz): _fga_get_model returns model id; cover validate_jwt_token…
lakhansamani Jun 8, 2026
cceddb5
refactor(authz): drop --authorization-engine flag; enable FGA via sto…
lakhansamani Jun 8, 2026
99ae941
refactor(authz): drop external-mode + dead old-engine flags; embed-on…
lakhansamani Jun 8, 2026
d3da2ab
fix(dashboard): correct FGA "not enabled" message; polish empty states
lakhansamani Jun 8, 2026
07cbf0a
feat(authz): FGA reuses the main database; --fga-store is now an over…
lakhansamani Jun 8, 2026
3a62764
test(authz): FGA disabled (and instance works) for unsupported DBs wi…
lakhansamani Jun 8, 2026
e513cc5
feat(dashboard): visual authorization-model builder (no DSL needed)
lakhansamani Jun 8, 2026
1fb6775
feat(dashboard): guided 3-step FGA flow, worked examples, collapsible…
lakhansamani Jun 8, 2026
0993846
refactor(dashboard): replace fragile model builder with react-arboris…
lakhansamani Jun 8, 2026
2492cb8
feat(dashboard): simple example-driven model editor with a full examp…
lakhansamani Jun 8, 2026
6007c15
fix(dashboard): Authorization nav no longer looks disabled
lakhansamani Jun 8, 2026
e292ef8
feat(dashboard): FGA docs links, grant-pattern examples, accurate ste…
lakhansamani Jun 8, 2026
9d46957
docs(dashboard): explain model versioning in the model editor
lakhansamani Jun 8, 2026
fafaf72
feat(fga): add guarded reset for the authorization model
lakhansamani Jun 8, 2026
6e16c77
feat(fga): empty-model state + Prometheus metrics for FGA resolvers
lakhansamani Jun 9, 2026
fde5a55
feat(dashboard): friendly FGA model builder, example modals, tester s…
lakhansamani Jun 9, 2026
a7dd8e5
feat(fga): _admin_meta query + seed model builder from configured roles
lakhansamani Jun 10, 2026
e6c457c
docs(fga): ReBAC hierarchy guide, concentric examples, user-id conven…
lakhansamani Jun 10, 2026
0451e0a
fix(fga): align all shipped models with OpenFGA best practices + vali…
lakhansamani Jun 10, 2026
b473534
docs(specs): v1→v2 migration tool design spec
lakhansamani Jun 10, 2026
0bfde7c
fix(dashboard): user:<id> examples everywhere + grant-form alignment
lakhansamani Jun 10, 2026
54f533a
feat(dashboard): generic RBAC seed with instance roles as suggestions…
lakhansamani Jun 10, 2026
6d95bab
feat(fga)!: check_permissions + list_permissions public API, one reso…
lakhansamani Jun 10, 2026
094ea8a
docs: point openfga-modeling skill reference at the new permission APIs
lakhansamani Jun 10, 2026
0e374df
docs(fga): note exact-string self-match semantics in the trust gate
lakhansamani Jun 10, 2026
e89291c
fix(fga): actionable error when a tuple doesn't match the model
lakhansamani Jun 10, 2026
0bced19
docs!: move design specs and guides to the authorizer-docs repo
lakhansamani Jun 11, 2026
94ea475
security(fga): cap contextual tuples per check at the API boundary
lakhansamani Jun 11, 2026
e44cd3c
fix(fga)!: recover store and model across restarts; non-fatal engine …
lakhansamani Jun 11, 2026
99c052b
feat(fga): list_permissions returns all subject permissions when filt…
lakhansamani Jun 11, 2026
bdfe3dd
feat(dashboard): user permissions modal lists everything by default
lakhansamani Jun 11, 2026
67ccb24
feat(dashboard): copyable user ID under the email in the Users table
lakhansamani Jun 11, 2026
8119fa7
feat(dashboard): permissions modal auto-loads the full list on open
lakhansamani Jun 11, 2026
5e96925
test(fga): fail-closed coverage for every admin op + explicit store o…
lakhansamani Jun 11, 2026
099dc47
test(dashboard): FgaNotEnabled rendering + not-enabled error detection
lakhansamani Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Detailed rules load via skills (see below) — don't restate them here.
| `principal-engineer` | opus | Full SDLC: Plan → Execute → Test → Review across Go, storage, GraphQL, HTTP. Use for any change touching >1 subsystem. |
| `security-engineer` | opus | OAuth2/OIDC, JWT, MFA, vulnerability audit. Second-pass on auth-sensitive PRs. |
| `doc-writer` | haiku | API docs, guides, migration docs. |
| `authz-researcher` | opus | Deep, adversarially-verified research on authz standards (OpenFGA, RFC 8693/8707/9728, MCP, CIBA, AuthZEN). Run before designing/building any authz capability. |
| `fga-engineer` | opus | Implements the OpenFGA migration (Wave 1) per specs/FGA_OPENFGA_MIGRATION_PLAN.md (authorizer-docs repo). |
| `delegation-engineer` | opus | Implements the agentic delegation chain (Wave 2) per specs/AGENTIC_DELEGATION_DESIGN.md (authorizer-docs repo). Security-critical. |

## Project Skills (auto-load on matching files)

Expand All @@ -70,6 +73,8 @@ Detailed rules load via skills (see below) — don't restate them here.
| `authorizer-security` | auth-sensitive code or `security/` branches |
| `authorizer-testing` | any `*_test.go` |
| `authorizer-frontend` | `web/app/`, `web/dashboard/` |
| `openfga-modeling` | FGA engine (`internal/authorization/`), authz models/tuples, `check_permissions`/`list_permissions`/`_fga_*` GraphQL |
| `agentic-auth-standards` | token exchange, delegation, MCP, agent-identity (`internal/token/`, `internal/http_handlers/`) |

## Token Optimization Notes

Expand Down
38 changes: 36 additions & 2 deletions ROADMAP_V2.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
| 13+ Database backends | Done | Unique differentiator |
| Rate Limiting / Brute Force | Missing | No protection at all |
| M2M / Client Credentials | Missing | No service accounts or API keys |
| Fine-Grained Permissions | Missing | Roles only, no resource-level control |
| Fine-Grained Permissions | In progress (pre-stable) | RBAC/ABAC Resource/Scope/Policy/Permission engine exists; migrating to OpenFGA ReBAC — see specs/FGA_OPENFGA_MIGRATION_PLAN.md (authorizer-docs repo) |
| SAML | Missing | Zero support |
| SCIM / Directory Sync | Missing | No provisioning |
| Audit Logs | Missing | Webhooks only, no queryable audit trail |
| Audit Logs | Partial | Structured AuditLog + audit provider exist (single actor); needs delegation-chain fields for agents — see specs/AGENTIC_DELEGATION_DESIGN.md (authorizer-docs repo) |
| Bot Detection | Missing | No CAPTCHA, no fingerprinting |
| Monitoring / Metrics | Missing | Health check only, no Prometheus |
| MCP Auth | Missing | No OAuth 2.1 AS capabilities |
Expand Down Expand Up @@ -328,6 +328,40 @@ These are table-stakes features that every competitor has. Without them, Authori

---

## Agentic Authorization Track (cross-phase sequencing)

> A re-sequencing lens over the items above, ordered by dependency for enterprise + agentic auth. Authorization for agents is a **pipeline**, not one feature; each wave builds on the last. ReBAC is necessary but not sufficient. Design detail: `../authorizer-docs/specs/FGA_OPENFGA_MIGRATION_PLAN.md`, `../authorizer-docs/specs/AGENTIC_DELEGATION_DESIGN.md`.

**The pipeline (evaluated per request):** Identity → Authentication → Token/Delegation → Authorization (scope → RBAC → ReBAC → ABAC → list_objects) → Human-in-the-loop → Governance.

### Wave 1 — Decision core *(now; replaces 2.1)*
Object-level authorization + the RAG primitive.
- [ ] **OpenFGA ReBAC engine** (replaces Resource/Scope/Policy/Permission) — `Check`, `list_objects`, `batch_check`, Conditions (ABAC). SQL-backed FGA store (embedded default / external optional).
- [ ] **Keep & integrate** OAuth scopes (coarse gate), RBAC roles (optionally mirrored as tuples), custom-token-script hook (may call FGA).
- [ ] **FGA-for-RAG** SDK helpers (`list_objects` pre-filter) in authorizer-go / authorizer-js.
- *Unlocks:* document sharing, hierarchical/B2B authz, secure RAG retrieval.

### Wave 2 — Delegation core *(next; folds in 2.2, 4.2, 4.3, 5.5)*
Who is asking, with whose borrowed authority, constrained to what.
- [ ] **Agent identity** — agents as first-class service-account principals (4.2).
- [ ] **RFC 8693 token exchange** + **`act` delegation claim** (5.5 / 4.2) — see design doc.
- [ ] **Attenuation / least-privilege** — exchanged-token scope = `subject ∩ requested ∩ agent ceiling` (reuses `Principal.MaxScopes`) (4.3).
- [ ] **Audit delegation chain** — `on_behalf_of` + `act` chain on AuditLog (all DBs).
- *Unlocks:* "agent acting for user, least-privilege, fully audited."

### Wave 3 — Async + credential custody
Human approval and safe third-party access.
- [ ] **CIBA + Rich Authorization Requests (RAR)** — out-of-band human approval for sensitive agent actions.
- [ ] **Token Vault** — encrypted per-user third-party token custody; agent never sees raw credentials.
- *Unlocks:* human-in-the-loop, agents calling Google/Slack/etc. on a user's behalf.

### Wave 4 — Enterprise hardening
- [ ] **MCP authorization** (OAuth 2.1 + RFC 9728 + RFC 8707) (4.1) and **ID-JAG / Cross-App Access** for enterprise-managed MCP.
- [ ] **JIT / time-bound grants** (TTL tuples), **per-agent guardrails** (spend/rate limits), **consent management**.
- *Unlocks:* enterprise-managed agent deployments at scale.

---

## Phase 5: Advanced Security & Enterprise (Q2-Q3 2027)

### 5.1 Passkeys / WebAuthn
Expand Down
89 changes: 54 additions & 35 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import (

"github.com/authorizerdev/authorizer/internal/audit"
"github.com/authorizerdev/authorizer/internal/authenticators"
"github.com/authorizerdev/authorizer/internal/authorization"
"github.com/authorizerdev/authorizer/internal/authorization/engine"
fgaengine "github.com/authorizerdev/authorizer/internal/authorization/engine/openfga"
"github.com/authorizerdev/authorizer/internal/config"
"github.com/authorizerdev/authorizer/internal/constants"
"github.com/authorizerdev/authorizer/internal/email"
"github.com/authorizerdev/authorizer/internal/events"
"github.com/authorizerdev/authorizer/internal/graph/model"
"github.com/authorizerdev/authorizer/internal/http_handlers"
"github.com/authorizerdev/authorizer/internal/memory_store"
"github.com/authorizerdev/authorizer/internal/metrics"
Expand Down Expand Up @@ -237,10 +237,12 @@ func init() {
// Back-channel logout (OIDC BCL 1.0)
f.StringVar(&rootArgs.config.BackchannelLogoutURI, "backchannel-logout-uri", "", "URL to POST a signed logout_token to when users log out successfully. Leave empty (default) to disable back-channel logout notifications. See OIDC Back-Channel Logout 1.0.")

// Fine-grained authorization flags
f.Int64Var(&rootArgs.config.AuthorizationCacheTTL, "authorization-cache-ttl", 300, "Cache TTL in seconds for permission checks (0 to disable)")
f.BoolVar(&rootArgs.config.IncludePermissionsInToken, "include-permissions-in-token", false, "Include permissions in JWT access tokens")
f.BoolVar(&rootArgs.config.AuthorizationLogAllChecks, "authorization-log-all-checks", false, "Audit log all permission checks, not just denials")
// OpenFGA fine-grained authorization. By default FGA reuses the main database
// when it is sqlite/postgres/mysql/mariadb (no extra config needed). These
// flags override that — required only when the main DB is unsupported
// (mongodb, dynamodb, …) or to use a dedicated FGA store.
f.StringVar(&rootArgs.config.FGAStore, "fga-store", "", "Override the OpenFGA datastore: 'sqlite', 'postgres', 'mysql', or 'memory' (dev). Default: reuse the main database when it is SQL-compatible; required only for unsupported main DBs (mongodb, dynamodb, …)")
f.StringVar(&rootArgs.config.FGAStoreURL, "fga-store-url", "", "Connection URI for an overridden --fga-store (file: URI for sqlite, DSN for postgres/mysql). Ignored when FGA reuses the main database")

// Deprecated flags
f.MarkDeprecated("database_url", "use --database-url instead")
Expand Down Expand Up @@ -462,34 +464,51 @@ func runRoot(c *cobra.Command, args []string) {
}
defer rateLimitProvider.Close()

// Authorization provider
authorizationProvider, err := authorization.New(
&authorization.Config{
CacheTTL: rootArgs.config.AuthorizationCacheTTL,
},
&authorization.Dependencies{
Log: &log,
StorageProvider: storageProvider,
MemoryStoreProvider: memoryStoreProvider,
},
)
if err != nil {
log.Fatal().Err(err).Msg("failed to create authorization provider")
}

// Check once at startup whether any permissions exist. If zero, emit a
// loud warn so operators don't lock themselves out in prod. Bounded
// context prevents a hung DB at boot from blocking startup indefinitely.
probeCtx, probeCancel := context.WithTimeout(context.Background(), 5*time.Second)
_, pr, lerr := storageProvider.ListPermissions(probeCtx, &model.Pagination{Limit: 1, Page: 1})
probeCancel()
switch {
case lerr != nil:
log.Warn().Err(lerr).Msg("authz: failed to probe permission count at startup; authorization is enforcing")
case pr != nil && pr.Total == 0:
log.Warn().Msg("authz: 0 permissions configured — all authorization checks will DENY. Seed permissions via the dashboard or admin GraphQL mutations.")
default:
log.Info().Msg("authz: enforcing; unmatched CheckPermission calls will be DENIED.")
// OpenFGA fine-grained authorization engine (embedded, in-process).
//
// Authorizer embeds OpenFGA — it IS the engine. The engine is constructed
// only when an FGA store is configured (--fga-store); otherwise it stays nil
// and the fga_* resolvers fail closed ("fine-grained authorization is not
// enabled"). Routed into GraphQL and session/validate below.
//
// By default FGA reuses the main database (sqlite/postgres/mysql/mariadb);
// --fga-store is only needed when the main DB is unsupported (mongodb,
// dynamodb, etc.) or to point at a dedicated store. FGAStoreConfig() resolves
// this. OpenFGA migrations run on boot for SQL stores (idempotent); memory
// needs none. NOTE: multi-replica deployments should prefer running
// migrations once via an init job — concurrent on-boot migrations rely on
// the migration tool's own locking and add cold-start latency.
//
// Engine-init failure is deliberately NON-fatal: FGA is an optional
// subsystem, so a failure here (e.g. the DB user lacks DDL rights for the
// OpenFGA tables) logs loudly and leaves authzEngine nil — fga_* and the
// permission APIs fail closed while core authentication keeps serving.
var authzEngine engine.AuthorizationEngine
if fgaStore, fgaStoreURL, fgaEnabled := rootArgs.config.FGAStoreConfig(); fgaEnabled {
runMigrations := !strings.EqualFold(fgaStore, fgaengine.StoreMemory)
fgaEngine, ferr := fgaengine.New(
&fgaengine.Config{
Store: fgaStore,
StoreURL: fgaStoreURL,
StoreName: rootArgs.config.OrganizationName,
RunMigrations: runMigrations,
},
&fgaengine.Dependencies{Log: &log},
)
if ferr != nil {
log.Error().Err(ferr).
Str("fga_store", fgaStore).
Msg("failed to initialize OpenFGA authorization engine; fine-grained authorization is DISABLED (fail-closed) — core auth continues")
} else {
if closer, ok := fgaEngine.(interface{ Close() }); ok {
defer closer.Close()
}
authzEngine = fgaEngine
log.Info().
Str("fga_store", fgaStore).
Bool("reused_main_db", strings.TrimSpace(rootArgs.config.FGAStore) == "").
Msg("OpenFGA authorization engine initialized (embedded); routed into GraphQL + session/validate")
}
}

// SMS provider
Expand Down Expand Up @@ -542,7 +561,7 @@ func runRoot(c *cobra.Command, args []string) {
TokenProvider: tokenProvider,
OAuthProvider: oauthProvider,
RateLimitProvider: rateLimitProvider,
AuthorizationProvider: authorizationProvider,
AuthzEngine: authzEngine,
})
if err != nil {
log.Fatal().Err(err).Msg("failed to create http provider")
Expand Down
Loading
Loading