Skip to content

Commit c64f681

Browse files
committed
docs(bb-auth-cognito): document the auth.admin opt-in surface in README
Add an 'Admin surface' section: opt-in via the admin options object, the compile-time gate, actions scoping the IAM grant (not the typed method set), the not-an-access-boundary caveat, group + lifecycle method tables, and the session-freshness note (revokeUserSessions for immediate effect). Update the generic-narrowing section for the const O type parameter (inline literals narrow without as const).
1 parent a77aa90 commit c64f681

1 file changed

Lines changed: 52 additions & 1 deletion

File tree

packages/bb-auth-cognito/README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,49 @@ Requires `enablePasskeys: true` and a WebAuthn-configured pool (`webAuthnRelying
151151
| `listPasskeys(context)` | `Promise<PasskeyDescription[]>` | List the signed-in user's registered passkeys (paginates internally). |
152152
| `deletePasskey(context, credentialId)` | `Promise<void>` | Remove a registered passkey by `credentialId`. |
153153

154+
## Admin surface (`auth.admin`)
155+
156+
Server-side admin operations — group membership and user lifecycle — that act on **any** user by `username` (unlike the client methods above, which act on the signed-in user via `context`). These are **opt-in**: pass an `admin` options object to enable the `auth.admin` handle and grant the matching `Admin*` / `List*` IAM. A pool that never opts in has no admin grant — its synthesized role is identical to today.
157+
158+
```typescript
159+
const auth = new AuthCognito(scope, 'auth', {
160+
groups: ['admins'],
161+
admin: { actions: ['groups'] }, // enable auth.admin; grant only group Admin* actions
162+
});
163+
164+
// Always gate admin routes behind requireRole — these methods do NOT self-gate.
165+
await auth.requireRole(ctx, 'admins');
166+
await auth.admin.addUserToGroup('alice', 'admins'); // group narrowed via GroupOf<O>
167+
```
168+
169+
- **Compile-time gate.** Without an `admin` options object, `auth.admin` is typed `AdminDisabled` and any access is a compile error whose message names the fix (`construct AuthCognito with { admin: {} }`). The getter also throws at runtime for untyped JS callers.
170+
- **`actions` scopes the IAM grant, not the method set.** `actions: ['groups']` grants only the group `Admin*` actions; `['lifecycle']` only the user-lifecycle actions; omitted grants both. The **typed surface is always the full set** — narrowing the method types by `actions` is not possible without breaking `AuthCognito<O>` variance, so calling a method whose action wasn't granted fails at runtime with an IAM `AccessDenied`.
171+
- **Not an access boundary.** Like every block on the shared backend Lambda, client and admin run under one role. Separation is by API surface + lint, not IAM — gate every admin route with `requireRole`.
172+
173+
**Group membership** (`actions: 'groups'`):
174+
175+
| Method | Returns | Description |
176+
|---|---|---|
177+
| `admin.addUserToGroup(username, group)` | `Promise<void>` | Add a user to a seeded group. `group` narrows to `GroupOf<O>`. Throws `GroupNotFound` for an unseeded group, `UserNotFound` for a missing user. |
178+
| `admin.removeUserFromGroup(username, group)` | `Promise<void>` | Remove a user from a group. |
179+
| `admin.listGroupsForUser(username)` | `Promise<GroupOf<O>[]>` | Groups the user belongs to. |
180+
| `admin.listUsersInGroup(group)` | `Promise<AdminUser[]>` | Members of a group (paginates internally). |
181+
182+
**User lifecycle** (`actions: 'lifecycle'`):
183+
184+
| Method | Returns | Description |
185+
|---|---|---|
186+
| `admin.createUser(username, init?)` | `Promise<AdminUser>` | Create a user. `init`: `{ temporaryPassword?, attributes?, suppressInvite? }`. Conflicts with `UserAlreadyExists`. |
187+
| `admin.deleteUser(username)` | `Promise<void>` | Delete a user (also strips them from every group). |
188+
| `admin.disableUser(username)` / `admin.enableUser(username)` | `Promise<void>` | Toggle sign-in. A disabled user's `signIn` is rejected with `NotAuthorizedException`. |
189+
| `admin.resetUserPassword(username)` | `Promise<void>` | Force the user to set a new password on next sign-in. |
190+
| `admin.setUserPassword(username, password, permanent)` | `Promise<void>` | Set a password. `permanent: true` clears the force-change flag. |
191+
| `admin.getUser(username)` | `Promise<AdminUser \| null>` | Look up a user, or `null` if absent. |
192+
| `admin.scan()` | `AsyncIterable<AdminUser>` | Enumerate all users (paginates internally). |
193+
| `admin.revokeUserSessions(username)` | `Promise<void>` | Revoke all the user's sessions (AWS: `AdminUserGlobalSignOut`) — forces immediate re-authentication. |
194+
195+
> **Session freshness.** A group change does not affect a user's **existing** session until their token refreshes — `requireRole` reads the `cognito:groups` claim, not live state. This is inherent Cognito behavior. The change applies on the next sign-in or `fetchAuthSession({ forceRefresh: true })`; for immediate effect, call `admin.revokeUserSessions(username)` to force re-auth.
196+
154197
## Options
155198

156199
```typescript
@@ -206,7 +249,15 @@ The `signInWith` option controls what end users sign in with. It maps to Cognito
206249
207250
## Using AuthCognito generically (literal-narrowing with `as const`)
208251

209-
`AuthCognito<O extends AuthCognitoOptions>` is generic on its options literal. When you pass the options object `as const`, TypeScript narrows method signatures to the exact values you configured — typos on group names, custom attributes, and MFA factors become compile errors instead of runtime surprises.
252+
`AuthCognito<const O extends AuthCognitoOptions>` is generic on its options literal. Because the generic is a **`const` type parameter**, TypeScript narrows method signatures to the exact values you configured for **options passed inline** — typos on group names, custom attributes, and MFA factors become compile errors instead of runtime surprises, with no `as const` needed:
253+
254+
```typescript
255+
const auth = new AuthCognito(scope, 'auth', { groups: ['admins', 'readers'] });
256+
await auth.requireRole(ctx, 'admins'); // ✅ narrowed inline — no `as const`
257+
await auth.requireRole(ctx, 'admin'); // ❌ compile error (typo)
258+
```
259+
260+
For an options object **declared separately** in a variable, add `as const` so its literals don't widen before reaching the constructor. The example below uses `as const` for that reason; the narrowing it produces is identical.
210261

211262
```typescript
212263
const options = {

0 commit comments

Comments
 (0)