Skip to content

Permissions in GraphQL API#581

Merged
irees merged 17 commits intomainfrom
graphql-permissions
Apr 8, 2026
Merged

Permissions in GraphQL API#581
irees merged 17 commits intomainfrom
graphql-permissions

Conversation

@irees
Copy link
Copy Markdown
Contributor

@irees irees commented Mar 24, 2026

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 configured

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 24, 2026

Generated Code Check ✅

All generated code is up to date.

@irees irees marked this pull request as ready for review March 24, 2026 11:16
Copilot AI review requested due to automatic review settings March 24, 2026 11:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces first-class permission management to the GraphQL API by adding new Tenant/Group/Permissions schema types plus query & mutation resolvers that read/write authorization data via cfg.PermissionManager.

Changes:

  • Adds new GraphQL schema/types for tenants, groups, and hierarchical permissions, plus queries and mutations to manage them.
  • Implements new gqlgen resolvers to list tenants/groups and to add/remove permissions and set parent relationships.
  • Adds a loader middleware nil-guard and introduces resolver-focused tests with a mocked PermissionManager.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
server/model/models_gen.go Adds generated model structs for Tenant, Group, and permission-related types/inputs.
server/model/finders.go Exposes PermissionManager type alias from authz.
server/model/config.go Adds PermissionManager to model.Config.
schema/graphql/permissions.graphqls Defines the new GraphQL types, queries, mutations, and permissions fields on Feed/FeedVersion.
gqlgen.yml Marks Tenant.groups, Group.*, Feed.permissions, and FeedVersion.permissions as resolver-backed fields.
server/gql/permission_resolver.go New resolvers for tenants/groups, permission fields, and permission/admin mutations.
server/gql/loaders.go Skips creating loaders when cfg.Finder is nil.
server/gql/permission_resolver_test.go Adds mock-based resolver tests for tenant/group queries and permission mutations.
internal/generated/gqlout/generated.go Regenerated gqlgen output for new schema/resolvers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/gql/permission_resolver.go Outdated
Comment thread server/gql/permission_resolver.go Outdated
Comment thread server/gql/permission_resolver.go
Comment thread server/gql/loaders.go
Comment thread server/gql/permission_resolver.go Outdated
Comment thread server/model/config.go
Comment thread server/gql/permission_resolver_test.go
Comment thread server/gql/permission_resolver.go Outdated
Comment thread server/gql/permission_resolver.go
Comment thread server/gql/permission_resolver.go Outdated
@irees irees merged commit 0e0a4d7 into main Apr 8, 2026
6 checks passed
@irees irees deleted the graphql-permissions branch April 8, 2026 00:06
irees added a commit that referenced this pull request Apr 8, 2026
## Summary

Adds missing GraphQL query filters and a new `users` resolver to the
permissions API, following up on the permissions PR (#581). The
`tenants` and `groups` queries now accept `ids: [Int!]` for direct
lookup, matching the convention used by `feeds`, `agencies`, and other
existing queries. A new `users` query exposes user search through the
`AdminManager` interface, using the standard `where` filter pattern.

### Schema changes
- `tenants` and `groups` queries accept `ids: [Int!]` parameter for
direct ID lookup
- New `User` type with `id`, `name`, and `email` fields
- New `UserFilter` input with `id` (single lookup) and `q` (search)
fields
- New `users(limit: Int, where: UserFilter)` query

### Resolver and interface changes
- `AdminManager` interface now includes `UserList` and `User` methods
(already implemented on `azchecker.Checker`)
- `Tenants`/`Groups` resolvers call `ObjectPermissions` directly for
each requested ID, skipping the full `ListObjects` call
- `Users` resolver returns empty list gracefully when `AdminManager` is
not configured or user is not found

## Test plan
- Verify `tenants(ids: [...])` returns matching tenants and empty list
for nonexistent IDs
- Verify `groups(ids: [...])` returns matching groups and empty list for
nonexistent IDs
- Verify `users` query lists all users, searches by `q`, looks up by
`id`, and respects `limit`
- Verify `users(where:{id:...})` returns empty list for unknown IDs
(consistent with tenants/groups)
- Verify `users` returns empty list when no `AdminManager` is configured
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants