You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add an opt-in `auth.admin` handle to `AuthCognito` for server-side group-membership and user-lifecycle administration.
6
+
7
+
Enable it by passing an `admin` options object; `admin.actions` scopes both the granted `Admin*` / `List*` IAM **and** the compile-time method surface. Without it, `auth.admin` is a compile error and no admin IAM is granted (unchanged default).
The admin surface is fully typed by the pool config `O`:
15
+
16
+
-**Action gating:** calling a method whose action group wasn't granted (e.g. `deleteUser` under `actions: ['groups']`) is a compile error, and fast-fails at runtime with a clear message instead of a cryptic AWS `AccessDenied`.
17
+
-**Typed reads:**`getUser` / `scan` / `listUsersInGroup` return `AdminUser<O>` — `groups` narrows to the configured group union and `attributes` keys to the declared attributes, matching the client-side `CognitoUser`.
18
+
-**Typed writes:**`createUser`'s `attributes` narrow to the declared keys (catches typos like `signUp` does).
19
+
-**`scan(filter?)`** accepts a server-side `AdminUserFilter` mapped to Cognito's `ListUsers``Filter`.
20
+
-**`setUserPassword(username, password, { permanent })`** takes a named options object instead of a bare boolean.
21
+
22
+
The `AuthCognito` class generic is now a `const` type parameter, so inline options literals narrow without `as const`.
23
+
24
+
Note: `const O` narrows the params of `requireRole`, `updateUserAttribute`, and `updateMFAPreference` for inline-literal options. Callers passing widened `string` variables to these may need a cast or literal arguments.
Regenerate the API report to match the current `BedrockModels` source (the committed `API.md` had drifted from the model-id constants). No source or runtime change.
Re-export the `auth.admin` types (`AdminOptions`, `AdminUser`, `AdminCreateInit`, `GroupAdmin`, `LifecycleAdmin`, `AdminSurface`, `AdminGetterOf`, `AdminDisabled`) from the umbrella package so consumers using `@aws-blocks/blocks` can name the values `auth.admin` returns and annotate `admin` options.
> **⚠️ SUPERSEDED.** The original design proposed a separate `@aws-blocks/bb-auth-cognito-admin` package. We instead ship the admin surface as an **opt-in `auth.admin` handle** on the existing `AuthCognito` class.
4
+
>
5
+
> The authoritative design and implementation plan is **[BB-auth-cognito-admin-implementation-plan.md](./BB-auth-cognito-admin-implementation-plan.md)**. For the shipped API and usage, see the [`bb-auth-cognito` README](../../packages/bb-auth-cognito/README.md) (*Admin surface*) and [DESIGN.md](../../packages/bb-auth-cognito/DESIGN.md).
6
+
7
+
## Why the direction changed
8
+
9
+
The separate-package design carried a second construct, a `/internal` subpath for sharing mock state across instances, and cross-construct wiring — all to preserve an admin/client separation that, on the shared backend-Lambda model, is enforced by API surface + lint rather than IAM anyway. The in-package handle preserves that same separation (plus a compile-time opt-in gate) at a fraction of the cost. The full trade study, type-safety analysis (including the `AuthCognito<O>` variance constraint that makes `admin.actions` scope the IAM grant rather than the typed method set), task list, and verification plan live in the implementation-plan doc.
10
+
11
+
The original 755-line design doc (API surface, error model, mock-parity research) was retired here to avoid drift: its code snippets predated the rebrand and its package/wiring model no longer matches shipped code. Its still-relevant content was folded into the implementation plan and the package README/DESIGN.
// Warning: (ae-incompatible-release-tags) The symbol "AttrOf" is marked as @public, but its signature references "CustomAttrNames" which is marked as @internal
16
75
//
17
76
// @public
@@ -20,8 +79,9 @@ export type AttrOf<O extends AuthCognitoOptions> = O extends {
Copy file name to clipboardExpand all lines: packages/bb-auth-cognito/DESIGN.md
+12-2Lines changed: 12 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,10 +18,10 @@ Design document. For usage, see [README.md](./README.md).
18
18
- Device tracking (list, forget).
19
19
- Password reset via verification code.
20
20
- Provider-agnostic state machine driving the same `<Authenticator>` UI as every other Blocks auth BB.
21
+
-**Admin user-lifecycle + group-membership APIs** (`createUser`, `deleteUser`, `addUserToGroup`, `listUsersInGroup`, etc.) — exposed as an **opt-in `auth.admin` handle** on this class, gated by the `admin` options object. Off by default (no handle, no `Admin*` IAM). See *Admin surface* below and [BB-auth-cognito-admin-implementation-plan.md](../../docs/tech-design/BB-auth-cognito-admin-implementation-plan.md) for the rationale.
21
22
22
23
**Out (for this BB):**
23
24
24
-
-**Admin user-lifecycle APIs** (`createUser`, `deleteUser`, `addUserToGroup`, `listUsers`, etc.) — deliberately excluded. Those operate on *any* user and can't self-gate from a `context`, so they don't belong on the same class that exposes `signIn`/`signUp`. They are provided by a separate admin Building Block.
25
25
-**HostedUI + federated sign-in** (Google / Facebook / LoginWithAmazon / Apple / OIDC / SAML) — not supported.
26
26
-**Cognito Lambda triggers** (`preSignUp`, `postConfirmation`, `preAuthentication`, …) — customers who need them can attach against the underlying `userPool` construct directly.
27
27
-**Auth flows other than `USER_PASSWORD_AUTH`** — widened typing only.
@@ -47,7 +47,7 @@ Creates under the construct's scope:
47
47
-**Nested `AppSetting(this, 'session-secret', { secret: true })`** — SSM SecureString HMAC for signing session-ID cookies. `AppSetting` handles the custom-resource wiring (CF can't create SecureString natively) and grants `ssm:GetParameter` + `kms:Decrypt` to `this.handler` automatically. Both the CDK layer and AWS runtime instantiate an `AppSetting` at the same scope path, so each side derives the same SSM parameter name without a dedicated env var.
The Lambda handler receives env vars `BLOCKS_AUTH_COGNITO_<UPPER_FULLID>_{USER_POOL_ID, CLIENT_ID, REGION}` so the AWS runtime can discover the pool/client IDs without CfnOutput round-trips. IAM grants are a **single** policy statement scoped to the pool ARN — the 17 client-facing Cognito actions (sign-up, sign-in, MFA, devices, self-service password/attribute mutations, `GlobalSignOut`). No `cognito-idp:Admin*`or`cognito-idp:List*` actions are granted; those belong to a separate admin Building Block.
50
+
The Lambda handler receives env vars `BLOCKS_AUTH_COGNITO_<UPPER_FULLID>_{USER_POOL_ID, CLIENT_ID, REGION}` so the AWS runtime can discover the pool/client IDs without CfnOutput round-trips. IAM grants are scoped to the pool ARN: a base policy statement covering the 17 client-facing Cognito actions (sign-up, sign-in, MFA, devices, self-service password/attribute mutations, `GlobalSignOut`) is always attached. `cognito-idp:Admin*`/`cognito-idp:List*` actions are granted**only** when the `admin` option is set — a second statement whose action set is scoped by `admin.actions` (`'groups'` and/or `'lifecycle'`). Omit `admin` and the synthesized role is byte-identical to the client-only grant (least privilege by default). See *Admin surface*.
51
51
52
52
`userPoolName` is `this.fullId`; the construct throws at synth time if the ID exceeds Cognito's 128-char limit.
53
53
@@ -81,6 +81,15 @@ Everything else (`username`, `userSub`, `groups`, `attributes`) is derived on re
81
81
82
82
**Challenge envelopes.** The challenge token returned by `signIn` on MFA / `NEW_PASSWORD_REQUIRED` is a small HMAC-signed envelope the client echoes back in `confirmSignIn`. Cognito's raw session token never reaches the browser; the envelope carries enough state to reconstruct `ChallengeResponses` on the server.
83
83
84
+
## Admin surface (`auth.admin`)
85
+
86
+
Server-side group-membership and user-lifecycle administration is exposed as an **opt-in handle on this class**, not a separate Building Block. Enable it by passing an `admin` options object; the handle grants the matching `Admin*` / `List*` IAM (scoped by `admin.actions`). This keeps the admin/client API distinction structural while avoiding a second package, its `/internal` mock-state-sharing port, and cross-construct wiring — the handle mutates the same live `state`/pool the client methods use. See [BB-auth-cognito-admin-implementation-plan.md](../../docs/tech-design/BB-auth-cognito-admin-implementation-plan.md) for the full rationale and the package-vs-handle trade study.
87
+
88
+
-**Compile-time gate.** The getter's type is `AdminGetterOf<O> = O extends { admin: object } ? AdminSurface<O> : AdminDisabled`. Without an `admin` object, `auth.admin` is `AdminDisabled` and any access is a compile error whose message names the fix; the getter also throws at runtime for untyped JS callers. Group names on the methods narrow via `GroupOf<O>`.
89
+
-**`actions` scopes both the IAM grant and the compile-time method surface.** Each method on `GroupAdmin<O>` / `LifecycleAdmin<O>` carries a trailing `...gate: AdminActionGate<O, A>` parameter: when `actions` grants action group `A` the gate is an empty tuple (callable normally), otherwise it demands an extra `never` argument, so an ungranted call is a **compile error**. Crucially the gate lives in a *parameter* position, not a conditional over the surface *shape* — so it does **not** make `AuthCognito<O>` invariant (a shape-narrowing conditional over `O` did, breaking assignability across the codebase — verified). `actions` therefore scopes both the CDK grant and the typed surface. Untyped JS callers that reach past the gate additionally fast-fail at runtime with a clear error.
90
+
-**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 behind `requireRole`.
91
+
-**Session freshness.**`revokeUserSessions` revokes Cognito refresh tokens (AWS: `AdminUserGlobalSignOut`; mock: deletes session records). On AWS an already-issued access token stays valid until it expires, so `checkAuth` does not flip immediately — a known mock-vs-AWS parity difference. Group changes likewise apply on the next sign-in / `fetchAuthSession({ forceRefresh: true })`, not to a live session.
92
+
84
93
## Options Split: `AuthCognitoOptions` vs `AuthCognitoMockOptions`
85
94
86
95
`AuthCognitoOptions` is the cross-runtime option type — CDK, AWS, and mock all accept it. The mock entry accepts a widened type:
@@ -134,3 +143,4 @@ Resolved by the MFA_SETUP + USER_AUTH work:
134
143
4.**Mock + AWS runtimes share the session/cookie code path.**`SessionStore`, cookie helpers, JWT decode — all shared. The only divergence is that mock issues placeholder-signature JWTs and never talks to Cognito.
135
144
5.**Error constants mirror Cognito's wire-format exception names.** Every value equals the name Cognito puts on the wire, so `isBlocksError(e, AuthCognitoErrors.X)` works identically for errors thrown by the mock (matched name directly) and errors propagated from the AWS SDK (name preserved when re-thrown as `ApiError`).
136
145
6.**`GroupNotFound` maps to `ResourceNotFoundException`.** Real Cognito returns `ResourceNotFoundException` (not the intuitive `GroupNotFoundException`) for a missing group. The constant name preserves the semantic label while the value matches what Cognito actually returns.
146
+
7.**Admin surface as an opt-in `auth.admin` handle, not a separate Building Block.** The admin/client distinction is preserved by the handle (and the `admin` opt-in gate) rather than by a second package. This removes a whole package, an `/internal` subpath for sharing mock state, and cross-construct wiring, at the cost of independent versioning (not needed). `admin.actions` scopes both the IAM grant and the compile-time method surface via a parameter-position `AdminActionGate` (ungranted calls are compile errors); the gate is a method *parameter* rather than a conditional over the surface shape, so `AuthCognito<O>` stays covariant in `O`. Full trade study in [BB-auth-cognito-admin-implementation-plan.md](../../docs/tech-design/BB-auth-cognito-admin-implementation-plan.md).
0 commit comments