Repository navigation
feat(core): organization roles for organization-owned records - #683
seanspeaks wants to merge 6 commits into
Conversation
… §3 permissions) Anyone in an organization may use its entities, integrations and credentials; only an organization admin may create, change or delete them. - User carries an explicit auth source (shared-secret | adopter-jwt | bearer-token) set by AuthenticateUser. A shared-secret caller is the app's own backend and manages. A token caller has the role the token carries, or MEMBER: a missing role never means admin. - user.orgAdminRoles (default ['ADMIN'], case-insensitive) names the admin roles; validated at app-definition load and in the schema. - Gates (403 ORG_ADMIN_REQUIRED): organization entity and integration create, entity delete, integration PATCH/DELETE (v1 and v2), credential delete and re-authorize. One shared check, assertCanManage; access comes from describeRecord or scopePolicy.accessToOwnedBy for credentials. - Docs: entity-scopes guide, organization-memberships guide, core README. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
There was a problem hiding this comment.
If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.
Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.
…ernal-mcp-05b-org-roles
…ernal-mcp-05b-org-roles
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f45da33. Configure here.
…ernal-mcp-05b-org-roles Brings in the DocumentDB list drain, the v1 POST /api/entity owner resolution and the owner-built v1 entity modules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
With the owner of POST /api/entity resolved through AuthorizeEntityForUser.resolveOwnership (merged from 05), the 05b gate in resolveOwnerForScope applies to it as it does to /api/authorize: a member token, or a token with no role, gets 403 ORG_ADMIN_REQUIRED for an organization type before any credential or module is touched. An admin token may create it, and a member may still create their own entity. Closes the open Bugbot thread on #683. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
…earer tokens keep pre-roles behaviour 05b treated every caller whose auth source carries no role as MEMBER. Frigg bearer tokens never carry one, so in an organization-primary app (default scope organization) a Frigg-UI user could no longer connect anything, a regression for existing adopters. And OrganizationMembership.orgRole was stored but never read. canManageOrganization now decides: - shared secret: always (unchanged); - Frigg bearer token: always, as before roles existed, unless the app sets user.orgAdminGateBearerTokens: true (new, validated at load and in the schema); then judged like the rest; - otherwise the token's role claim when it carries one, else the stored orgRole of the caller's membership of the active organization (the legacy pointer reads as MEMBER), else MEMBER. GetUserFromAdopterJwt puts the membership's stored role on the User (User#getMembershipRole). Docs, types and the schema say the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
|
Frigg 2.0.1 review (Stack A) — COMMENT onlyVerdict (would): would approve Create/update/delete gates for org-scoped resources via BlockingNone. Should-fix
Nits
No formal approve / request-changes on this review. |
| ); | ||
| user.setOrgRole(orgRole); | ||
| // What the membership row says: the role when the token carries none. | ||
| user.setMembershipRole(membership?.orgRole ?? null); |
There was a problem hiding this comment.
should-fix — sticky ADMIN when the JWT omits org_role after demotion
orgRole from a missing claim is null, then setMembershipRole(membership?.orgRole ?? null) falls back to the stored row. Prisma membership upsert uses update: {} (role never refreshed on later logins).
Scenario: User first authenticates with org_role=ADMIN (persisted). IdP later demotes them and issues tokens without a role claim → membershipRole stays ADMIN → still manages org entities/credentials/integrations.
Please either (a) update orgRole on every successful JWT membership ensure when the claim is present, and treat “claim absent” as MEMBER, or (b) document hard that demotion must send org_role=MEMBER and that omitting the claim preserves the first-login role forever.




Summary
Only an organization's admins can manage what the organization owns; members can use it. This PR is stacked on #682 (entity scopes), the second of three.
What it adds
authSourceonUser, set explicitly by eachAuthenticateUserbranch:shared-secret(x-frigg headers),adopter-jwtorbearer-token. It is never inferred from a null role.canManageOrganization:user.orgAdminRoles(default['ADMIN'], case-insensitive). A token with no role claim is aMEMBER, so a missing role is never treated as admin.user.orgAdminRolesis validated at app-definition load and in the schema.Gates return 403
ORG_ADMIN_REQUIREDon:They run through one shared
assertCanManage(modules/use-cases/manage-access.js) and theACCESSconstants.Behaviour change
Adopters using adopter-JWT or bearer tokens whose tokens carry no role claim lose org-level management until they send a role in
orgAdminRoles, or configure it. Header (shared-secret) callers are unaffected.Test plan
packages/schemas: 199 pass.orgAdminRolesvalidation, every gate andassertCanManage.🤖 Generated with Claude Code
https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
Generated by Claude Code
Note
High Risk
Changes authorization for organization-owned resources and alters behavior for adopter-JWT/bearer callers without role claims; misconfiguration of orgAdminRoles or gateBearerTokens could block legitimate admins or leave tokens over-privileged.
Overview
Introduces organization admin roles so members can use organization-owned entities, integrations, and credentials, but only admins (or trusted callers) can create, change, or delete them.
Authentication & policy:
AuthenticateUsernow sets explicitauthSourceonUser(shared-secret,adopter-jwt,bearer-token). Scope visibility derivescanManageOrgviacanManageOrganization(token role vs stored membershiporgRole, defaultMEMBERwhen missing). App config addsuser.orgAdminRoles(default['ADMIN']) and optionaluser.orgAdminGateBearerTokens. List/access metadata now returnsmanagevsusefor organization rows.Enforcement: Shared
assertCanManagereturns 403ORG_ADMIN_REQUIREDwhen access is notmanage. Applied on org-level authorize/create, entity delete, integration PATCH/DELETE (v1/v2 via{ manage: true }), and credential delete/reauthorize (scopePolicy.accessToOwnedBy). Adopter-JWT callers without a role claim lose org-level management unless their membership role qualifies; Frigg bearer tokens stay ungated by default; shared-secret callers still manage.Docs, schema, and types updated; broad test coverage for gates and role resolution.
Reviewed by Cursor Bugbot for commit 8a84ab1. Bugbot is set up for automated code reviews on this repo. Configure here.
📦 Published PR as canary version:
2.0.0--canary.683.8a84ab1.0✨ Test out this PR locally via:
npm install @friggframework/admin-scripts@2.0.0--canary.683.8a84ab1.0 npm install @friggframework/core@2.0.0--canary.683.8a84ab1.0 npm install @friggframework/devtools@2.0.0--canary.683.8a84ab1.0 npm install @friggframework/eslint-config@2.0.0--canary.683.8a84ab1.0 npm install @friggframework/prettier-config@2.0.0--canary.683.8a84ab1.0 npm install @friggframework/schemas@2.0.0--canary.683.8a84ab1.0 npm install @friggframework/serverless-plugin@2.0.0--canary.683.8a84ab1.0 npm install @friggframework/test@2.0.0--canary.683.8a84ab1.0 npm install @friggframework/ui@2.0.0--canary.683.8a84ab1.0 # or yarn add @friggframework/admin-scripts@2.0.0--canary.683.8a84ab1.0 yarn add @friggframework/core@2.0.0--canary.683.8a84ab1.0 yarn add @friggframework/devtools@2.0.0--canary.683.8a84ab1.0 yarn add @friggframework/eslint-config@2.0.0--canary.683.8a84ab1.0 yarn add @friggframework/prettier-config@2.0.0--canary.683.8a84ab1.0 yarn add @friggframework/schemas@2.0.0--canary.683.8a84ab1.0 yarn add @friggframework/serverless-plugin@2.0.0--canary.683.8a84ab1.0 yarn add @friggframework/test@2.0.0--canary.683.8a84ab1.0 yarn add @friggframework/ui@2.0.0--canary.683.8a84ab1.0