Skip to content

Commit 8e937ba

Browse files
rafiki270claude
andcommitted
docs: apply loop 5 review fixes
- feature-flags.md: fix "per organisation" → "per App" (lines 5 and service enablement section) - feature-flags.md: add security note on flag query endpoint (userId validation, server-side only) - roles-and-acl.md: add SCIM GET /Users pagination spec alongside existing Groups spec - api-changes-rebac.md: update auto-enrolment pseudo-code to assign default customRole on TeamMember creation - architecture-api.md: add /internal/admin route group with all 6 file descriptors - architecture-api.md: add org-permission.ts and team-permission.ts to middleware listing - architecture-api.md: document requireOrgRole and requireTeamRole middleware behaviour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 906c712 commit 8e937ba

4 files changed

Lines changed: 28 additions & 3 deletions

File tree

Docs/Auth/architecture-api.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,23 @@ For the full product spec, see [brief.md](./brief.md). For tech stack, see [tech
6262
group-members.ts — POST/PUT/DELETE internal group members
6363
team-group-assignment.ts — PUT team↔group assignment
6464
index.ts — Route registration for /internal/org
65+
/admin
66+
orgs.ts — GET /internal/admin/orgs, GET /internal/admin/orgs/:orgId
67+
org-members.ts — PATCH/DELETE /internal/admin/orgs/:orgId/members/:userId
68+
org-domain-rules.ts — GET/POST/DELETE /internal/admin/orgs/:orgId/domain-rules
69+
team-members.ts — PATCH /internal/admin/teams/:teamId/members/:userId
70+
scim-tokens.ts — GET/POST/DELETE /internal/admin/orgs/:orgId/scim-tokens
71+
scim-group-mappings.ts — GET/POST/DELETE /internal/admin/orgs/:orgId/scim/group-mappings
72+
index.ts — Route registration for /internal/admin
6573
/middleware
6674
config-verifier.ts — Fetches config URL, verifies JWT, attaches config to request
6775
domain-hash-auth.ts — Verifies domain hash token for domain-scoped APIs
6876
superuser-access-token.ts — Verifies user access token and requires superuser role
6977
org-features.ts — Returns 404 when org features are disabled
7078
groups-enabled.ts — Returns 404 when groups are disabled
7179
org-role-guard.ts — Validates user access token and org role for /org endpoints
80+
org-permission.ts — requireOrgRole(minRole) — org-level UOA role enforcement (owner > admin > member)
81+
team-permission.ts — requireTeamRole(minRole) — team-level UOA role enforcement with org-level fallback
7282
error-handler.ts — Global error handler (generic user-facing errors, detailed internal logs)
7383
rate-limiter.ts — Rate limiting
7484
/services
@@ -147,6 +157,8 @@ Request → Route → Middleware → Service → Database (Prisma)
147157
* **org-features** — rejects org endpoints when `org_features.enabled` is false
148158
* **groups-enabled** — rejects group endpoints when `org_features.groups_enabled` is false
149159
* **org-role-guard** — validates user context and org role for `/org/*` routes
160+
* **org-permission** (`requireOrgRole(minRole)`) — enforces org-level UOA role (`owner > admin > member`). Reads `OrgMember.role` for the authenticated user in the target org. Returns 403 if not a member or role is insufficient. Used on org management endpoints and admin panel org routes. See `api-changes-rebac.md §4`.
161+
* **team-permission** (`requireTeamRole(minRole)`) — enforces team-level UOA role with org-level fallback inheritance. Checks `TeamMember.role` first; if not a direct member, falls back to the user's `OrgMember.role` for the parent org. Returns 403 if neither check passes. See `api-changes-rebac.md §4`.
150162
* **error-handler** — catches all errors. Returns generic message to user. Logs specifics internally
151163

152164
### Services (fat)

Docs/Requirements/feature-flags.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Status: confirmed, in scope
44

5-
Both the feature flag service and the role flag matrix are **optional services**. Neither is mandatory for a consuming app that just needs identity and authentication. They are enabled or disabled per organisation in the UOA admin panel.
5+
Both the feature flag service and the role flag matrix are **optional services**. Neither is mandatory for a consuming app that just needs identity and authentication. They are enabled or disabled **per App** via the `feature_flags_enabled` and `role_flag_matrix_enabled` fields on the App model (see `apps.md`). A system admin or org admin toggles these fields from the admin panel.
66

77
---
88

@@ -53,6 +53,8 @@ The global missing-flag default means consuming apps never get an error for an u
5353
GET /apps/:appId/flags?userId=user_123[&teamId=team_xyz]
5454
```
5555

56+
**Auth:** Domain-hash auth (consuming app calling server-side). The `userId` param must correspond to a real user in the calling app's org — the server validates this. When called from a client SDK context, use the `/apps/startup` endpoint instead (which accepts an `X-UOA-Access-Token` header and derives `userId` from it).
57+
5658
`teamId` is optional. When omitted and the user has a single team membership relevant to this App, that team's role is used. When the user has multiple team memberships, `teamId` must be provided or the multi-team fallback rule (see resolution order above) applies.
5759

5860
Returns the fully resolved flag map for that user in that App:
@@ -120,7 +122,7 @@ Example: a `viewer` who needs temporary `beta_access` gets a per-user override o
120122

121123
## Service enablement
122124

123-
Services are enabled per organisation. A system admin can toggle them from the admin panel. The consuming app does not need to change any code — if flags are not enabled, the query endpoint returns an empty object and the token contains no `flags` field.
125+
Services are enabled **per App** (not per org). Two boolean fields on the App model control availability — `feature_flags_enabled` and `role_flag_matrix_enabled`. A system admin or org admin toggles these from the admin panel on a per-App basis. The consuming app does not need to change any code — if flags are not enabled, the query endpoint returns an empty object and the token contains no `flags` field.
124126

125127
| Service | When disabled | When enabled |
126128
|---|---|---|

Docs/Requirements/roles-and-acl.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ SCIM endpoints are authenticated with the per-org SCIM bearer token (see above).
287287

288288
**SCIM authentication:** `Authorization: Bearer <token>` header (RFC 7523 standard). Missing or invalid token returns HTTP 401 with SCIM error schema. Token scope is validated against the org identified in the token hash lookup — requests targeting a different org's resources return HTTP 403.
289289

290+
**SCIM `GET /scim/v2/Users` pagination:** Uses SCIM standard `startIndex` (1-based, default 1) and `count` (page size, default 100, max 200) params. Supports `filter=userName eq "alice@acme.com"` and `filter=externalId eq "<idp-id>"` per RFC 7644 §3.4.2.2. Response includes `totalResults`, `startIndex`, `itemsPerPage`, and a `Resources` array of User objects.
291+
290292
**SCIM `GET /scim/v2/Groups` pagination:** Uses SCIM standard `startIndex` (1-based, default 1) and `count` (page size, default 100, max 200) params. Supports `filter=displayName eq "Engineering"` per RFC 7644 §3.4.2.2. Response includes `totalResults`, `startIndex`, `itemsPerPage`.
291293

292294
**SCIM bearer token management endpoints** (system admin auth required):

Docs/Research/api-changes-rebac.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,17 @@ for (const rule of matchingRules) {
194194
where: { orgId: rule.orgId, isDefault: true }
195195
});
196196
if (defaultTeam) {
197+
// Look up default customRole for this team (if role_flag_matrix_enabled)
198+
const defaultCustomRole = await prisma.teamCustomRole.findFirst({
199+
where: { teamId: defaultTeam.id, isDefault: true }
200+
});
197201
await prisma.teamMember.create({
198-
data: { teamId: defaultTeam.id, userId: user.id, role: 'member' }
202+
data: {
203+
teamId: defaultTeam.id,
204+
userId: user.id,
205+
role: 'member',
206+
customRole: defaultCustomRole?.name ?? null
207+
}
199208
});
200209
}
201210
}

0 commit comments

Comments
 (0)