Skip to content

feat(fga): implement effective-permissions listing endpoints - #84

Merged
gjtorikian merged 9 commits into
mainfrom
feat/effective-permissions
Aug 27, 2026
Merged

feat(fga): implement effective-permissions listing endpoints#84
gjtorikian merged 9 commits into
mainfrom
feat/effective-permissions

Conversation

@gjtorikian

Copy link
Copy Markdown
Collaborator

Summary

Fixes #81.

workos.authorization.listEffectivePermissionsByExternalId() (and listEffectivePermissions()) 404ed unconditionally against the emulator because no effective-permissions route existed.

Changes

Three routes sharing one handler, verified against the paths @workos-inc/node actually builds:

  • GET /authorization/organization_memberships/:id/resources/:resource_type_slug/:external_id/permissions — used by listEffectivePermissionsByExternalId()
  • GET /authorization/organization_memberships/:id/resources/:resource_id/permissions — production controller's by-id variant
  • GET /authorization/resources/:resource_id/organization_memberships/:membership_id/permissions — used by listEffectivePermissions() (note: the SDK builds this resource-centric path, not the membership-centric by-id shape)

Responses are standard paginated lists of permission objects (formatListResponse + formatPermission), compatible with the SDK's AutoPaginatable.

Semantics: the emulator has no resource-scoped role assignments or ancestor inheritance, so the effective set is the membership's full permission set (primary role + role assignments, same as .../check). The resource segments gate 404s: unknown membership, unknown resource, and cross-org resources all 404.

Testing

  • 8 new specs covering all three routes, permissions from additional role assignments, pagination, and the 404 cases.
  • Full suite: 911 pass, 0 fail. typecheck, lint, fmt:check clean.
  • gen:supported: FGA endpoint coverage 13/19 → 15/19 (overall 143 → 145 of 212).

The Node SDK's listEffectivePermissionsByExternalId() and
listEffectivePermissions() 404ed unconditionally because the emulator
served no effective-permissions routes. Add the production routes:

- GET /authorization/organization_memberships/:id/resources/:resource_type_slug/:external_id/permissions
- GET /authorization/organization_memberships/:id/resources/:resource_id/permissions
- GET /authorization/resources/:resource_id/organization_memberships/:membership_id/permissions

The emulator has no resource-scoped role assignments or ancestor
inheritance, so the effective set is the membership's full permission
set; the resource path segments gate 404s (unknown resource, unknown
membership, cross-org access).

Fixes #81
@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds three effective-permissions routes and aligns membership-role resolution across authorization checks, role assignments, and token minting.

  • Returns paginated effective permissions from primary and additional membership roles.
  • Validates resource and membership organization boundaries for all supported route shapes.
  • Centralizes organization-role precedence and restricts environment-role fallback to unowned roles.
  • Updates endpoint coverage documentation and adds regression coverage for pagination, route variants, collisions, and cross-organization access.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/workos/role-helpers.ts Centralizes organization-first primary-role resolution and prevents organization-owned seeded roles from serving as environment fallbacks.
src/workos/routes/authorization-checks.ts Adds effective-permissions handlers, scopes external resource lookup to the membership organization, and validates role-assignment ownership.
src/workos/routes/auth.ts Uses the shared role resolver and permission helper so token claims match authorization endpoint behavior.
src/workos/routes/authorization-checks.spec.ts Covers all route shapes, pagination, additional assignments, lookup collisions, role precedence, and organization-boundary failures.
src/workos/routes/auth.spec.ts Verifies organization roles shadow same-slug environment roles when access-token permissions are minted.
SUPPORTED.md Updates generated endpoint coverage to reflect the two newly supported specification operations.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Client[SDK client] --> Route{Effective-permissions route}
  Route --> Membership[Load membership]
  Route --> Resource[Resolve resource]
  Membership --> Boundary{Same organization?}
  Resource --> Boundary
  Boundary -- No --> NotFound[Return 404]
  Boundary -- Yes --> Primary[Resolve primary role]
  Boundary -- Yes --> Assignments[Load additional assignments]
  Primary --> Permissions[Union permission slugs]
  Assignments --> Permissions
  Permissions --> Page[Paginate and format permissions]
  Page --> Response[List response]
Loading

Reviews (9): Last reviewed commit: "fix(fga): findEnvRole only matches roles..." | Re-trigger Greptile

Comment thread src/workos/routes/authorization-checks.ts Outdated
Comment thread src/workos/routes/authorization-checks.ts
…zation

Address review findings on the effective-permissions endpoints:

- The external-id route matched the first resource with the given type
  and external id across all organizations, so a cross-org collision
  could 404 a valid request. The lookup now requires the membership's
  organization.
- getPermissionsForMembership picked whichever same-slug role was
  inserted first; an organization role now shadows an environment role
  with the same slug.

Adds regression tests for both cases.
Review follow-ups on the effective-permissions endpoints:

- The resource-centric route swaps parameter order relative to the
  membership-centric routes but had no error-path coverage; add 404
  specs for an unknown resource and an unknown membership.
- The org-over-environment role shadowing lives in the shared
  getPermissionsForMembership helper, so assert it through /check as
  well, not just the new listing route.
Comment thread src/workos/routes/authorization-checks.ts Outdated
…tion

The access token's permissions claim resolved the membership role with
first-match logic, so an environment role inserted before a same-slug
organization role won — while /check and the effective-permissions
endpoints resolve the organization role. Extract resolvePrimaryRole
into role-helpers and use it in both places so the token claim always
agrees with the authorization endpoints.

The resolver matches on organization_id rather than role type because
seeded roles can carry an organization_id while type defaults to
EnvironmentRole.
…sions

# Conflicts:
#	src/workos/routes/authorization-checks.ts
Comment thread src/workos/role-helpers.ts Outdated
Seed normalization lets a role carry an organization_id while its type
defaults to EnvironmentRole. The primary-role fallback matched on type
alone, so another organization's seeded role could satisfy it by
insertion order and leak its permissions into effective-permission
responses, checks, and token claims. The fallback now also requires a
null organization_id.
Comment thread src/workos/role-helpers.ts
…resolution

The role_slug path of role-assignment creation fell back to an
ownership-agnostic findEnvRole, so a foreign organization's seeded role
whose type defaulted to EnvironmentRole could be assigned to a local
membership, leaking its permissions into checks and effective
permissions. Route the lookup through resolvePrimaryRole, whose
environment fallback requires a null organization_id.
Comment thread src/workos/routes/authorization-checks.ts
The back-compat role_id branch of role-assignment creation loaded any
role by id without checking ownership, so another organization's role
could be assigned to a local membership and leak its permissions into
checks and effective permissions. Assigned roles must now belong to the
membership's organization or the environment, however addressed.
An environment role is unowned by definition, but findEnvRole matched
on type alone, so a seeded role carrying an organization_id with its
type defaulted to EnvironmentRole was addressable as an environment
role from any organization. Require a null organization_id at the
helper so every caller inherits the boundary.
@gjtorikian
gjtorikian merged commit 4c444e2 into main Aug 27, 2026
13 of 14 checks passed
@gjtorikian
gjtorikian deleted the feat/effective-permissions branch August 27, 2026 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

FGA: effective-permissions listing endpoints (listEffectivePermissionsByExternalId) return 404 (not implemented)

1 participant