Commit 0e0a4d7
authored
Permissions in GraphQL API (#581)
## Summary
Add permission management to the GraphQL API, enabling the frontend to
query and mutate authorization data alongside transit entities. This is
the first step toward sunsetting the separate admin REST API — all
permission reads and writes are now expressible through GraphQL. Also
decouples `azchecker.Checker` from its `auth0` and `fga` dependencies,
making it pluggable for different auth backends.
### New GraphQL types
- `Tenant` — represents a tenant organization with `id`, `name`,
`groups`, and `permissions`
- `Group` — represents a group within a tenant with `id`, `name`,
`tenant`, `feeds`, and `permissions`
- `Permissions` — generic permissions type reused across all entity
types, containing `actions`, `subjects`, `parent`, and `children`
- `PermissionSubject` — describes a user/group with a relationship to an
entity
- `PermissionRef` — lightweight reference to a parent/child entity in
the auth hierarchy
### New queries
- `tenants` — list tenants accessible to the current user (returns empty
list when auth not configured)
- `groups` — list groups accessible to the current user (returns empty
list when auth not configured)
- `permissions` field added to `Feed` and `FeedVersion` types (nullable,
returns null when auth not configured)
### New mutations
- `permission_add(type, id, input)` — add a permission to any entity
type
- `permission_remove(type, id, input)` — remove a permission from any
entity type
- `permission_set_parent(type, id, input)` — set an entity's parent in
the auth hierarchy
- `tenant_save(id, input)` — update a tenant's name
- `tenant_create_group(id, input)` — create a group within a tenant
- `group_save(id, input)` — update a group's name
### Checker decoupling
- `NewCheckerFromConfig` now accepts `UserProvider` and `FGAProvider` as
parameters instead of constructing them internally
- Removed `auth0` and `fga` imports from `azchecker/checker.go` —
deployments inject their own providers
- `CheckerConfig` reduced to just `GlobalAdmin` — all provider config is
handled by callers
- Read-only `Checker` implementations (without `PermissionManager` or
`AdminManager`) are supported: permission fields return null, admin
mutations return errors, queries/data path works normally
- `AdminManager` interface documented with guidance that `UserProvider`
implementations are responsible for user visibility scoping
- `EKLookup` exported for callers that need to resolve symbolic tuple
names to DB IDs (e.g., test setup)
### Implementation details
- All permission resolvers use `cfg.Checker` from context,
type-asserting to `PermissionManager` or `AdminManager` as needed
- Read-only `permissions` field on Feed/FeedVersion returns `null` when
`PermissionManager` is not configured (no error)
- `tenants` and `groups` queries return empty lists when unconfigured,
matching the nullable pattern
- Mutation resolvers validate string enum inputs (`type`, `relation`)
via `ObjectTypeString`/`RelationString`
- `Group.feeds` resolves full `Feed` entities via `cfg.Finder.FindFeeds`
(not just ObjectRef)
- `loaders.go` nil-guard added to prevent panic when Finder is not
configured
- `"group"` added as alias for `"org"` in `ObjectType_value` — GraphQL
API exposes "group" while FGA model uses "org"
### JWT hardening
- Empty user ID check rejects tokens with missing subject or email (when
`useEmailAsId` is set)
- OIDC `WithSkipJWKValidation()` option for providers with incorrectly
encoded x5t values
### Tests
- 8 test functions with real in-memory OpenFGA (not mocks): tenant
queries, group queries, feed/feed_version permissions, all mutations,
invalid input handling, nil-config graceful degradation, unauthorized
user access, mutation round-trip (add/verify/remove/verify)
- Admin mutation tests use transaction rollback to avoid persisting test
data
- JWT middleware tests for empty subject and missing email edge cases
- Tests require `TL_TEST_FGA_ENDPOINT` and test database
### Breaking changes
- `NewCheckerFromConfig` signature changed: `(ctx, cfg, db) ->
(*Checker, error)` is now `(cfg, userClient, fgaClient, db) -> *Checker`
- Downstream callers (e.g., tlv2-apps) need to construct their own
`UserProvider` and `FGAProvider` and pass them in
### Existing REST API
- The admin REST API in `azchecker/server.go` is unchanged — same
endpoints, same response shapes
## Test plan
- Run `go test -run TestPermissionResolver -v ./server/gql/` — all 8
test functions pass
- Run `go test -run TestNewJWTHandler -v ./server/auth/mw/jwtcheck/` —
JWT tests pass
- Run full test suite `go test ./server/gql/...` with test DB to verify
no regressions on existing resolvers
- Verify `go build ./server/...` compiles cleanly
- Manual: query `{ feeds { permissions { actions } } }` with and without
auth configured1 parent 201120b commit 0e0a4d7
22 files changed
Lines changed: 4714 additions & 384 deletions
File tree
- ext/bestpractices
- gtfs
- internal
- generated/gqlout
- testconfig
- testreadme
- rules
- schema/graphql
- server
- auth
- authz
- azchecker
- mw/jwtcheck
- gql
- model
- tlcsv
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | | - | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
123 | | - | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
124 | 138 | | |
125 | 139 | | |
126 | 140 | | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
127 | 147 | | |
128 | 148 | | |
129 | 149 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | | - | |
0 commit comments