feat(bb-auth-cognito): opt-in auth.admin handle (group + user-lifecycle admin)#38
Merged
harsh62 merged 27 commits intoJul 23, 2026
Merged
Conversation
harsh62
force-pushed
the
docs/bb-auth-cognito-admin-design
branch
from
June 29, 2026 21:33
5b8215f to
b9c225b
Compare
Implementation-ready design for the AuthCognitoAdmin building block — the server-side admin surface (user lifecycle + group membership) that AuthCognito deliberately omits from its self-gating client-facing class. Covers API surface, error model, per-runtime implementation, and mock parity. Migrated from private staging repo (squashed). Internal cross-references to docs not present in this repo have been de-linked.
…Admin Add implementation plan for the in-package `auth.admin` handle approach (opt-in via `admin` options object) instead of a separate `@aws-blocks/bb-auth-cognito-admin` package, per the reviewed counter-proposal. Mark the original separate-package design as superseded; retain it for the API-surface/error-model/mock-parity research the handle reuses.
… design Detail the full type-safety design for the in-package admin handle: the AdminOptions/AdminSurface/AdminGetterOf gate, GroupAdmin/LifecycleAdmin interfaces, the single safe getter cast, and per-runtime + CDK + const-O migration steps. Every conditional type was compiled under tsc --strict with positive and @ts-expect-error negative cases (Appendices A/B); the proof lands in-repo as admin.types-test.ts.
… auth.admin plan 11 ordered, independently-committable tasks (T1-T11) with dependencies, plus a verify list spanning type tests, mock behavior, CDK grant assertions, and a sandbox-gated integration suite (incl. negative least-privilege).
…l runtimes) Adds the server-side admin surface as an opt-in handle on AuthCognito (T1-T4, T6-T7): admin types + compile-time gate, mock runtime (group/lifecycle mutators on live state), AWS runtime (Admin* SDK commands), browser stub getter, and the CDK IAM grant scoped by admin.actions. Includes admin.types-test.ts. Type-safety finding: narrowing the method set by actions via a conditional over O forces AuthCognito<O> invariant and regressed 14 existing call sites. Resolved by always exposing the full surface (group names still narrow via GroupOf<O>); actions scopes the IAM grant only. Full package build + existing 117 tests green. Remaining: T5 const O, T8/T9 unit tests, T10 integration, T11 README.
Change AuthCognito<O> to AuthCognito<const O> across all four entries so inline options literals narrow without `as const`. This narrows five projections by default — GroupOf (requireRole), AttrOf (updateUserAttribute family), ReadAttrOf (fetchUserAttributes), MfaTypeOf (confirmSignIn/updateMFAPreference), and CustomAttrNames. Fixes the call sites that pass wire strings to now-narrowed params by casting at the boundary (runtime validates regardless): comprehensive authCRequireRole + authCMfaUpdateMFAPreference, native-bindings cognitoRequireRole. Tightens admin.types-test case 5 to assert group narrowing without `as const`. BREAKING CHANGE: callers passing widened variables to requireRole / updateUserAttribute / updateMFAPreference now need a cast or literal args. Acceptable pre-release.
… grant)
admin.test.ts (13): runtime gate throw, group add/remove + GroupNotFound +
UserNotFound, listGroups/listUsersInGroup, lifecycle create/get/delete (+ group
cleanup), disable/enable (asserts existing signIn disabled-guard), setUserPassword,
scan, revokeUserSessions.
index.cdk.test.ts (+4): no-admin grants zero Admin* actions (least privilege);
admin:{} grants both slices; actions:['groups'|'lifecycle'] grant only that slice.
Full package suite: 224 tests, 0 fail.
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).
Wire authC with admin: {} plus authCAdmin* routes (createUser, setPassword,
addToGroup, listGroupsForUser, disable/enable, delete, revokeSessions), and add
sandbox-admin-surface-e2e.ts driving them through the deployed Lambda over JSON-RPC.
Covers: createUser, setUserPassword→signIn, addUserToGroup→requireRole on a fresh
token, revokeUserSessions drops the live session, disable/enable gates signIn,
deleteUser. Requires a deployed bb-test-* sandbox (sandbox-gated; not in the unit
run). Typechecks clean.
Convert the admin e2e to the (getApi) => void suite pattern wired into e2e.test.ts (auth-cognito-admin-sandbox.test.ts), replacing the standalone script so it runs under the unified deploy/teardown harness. Verified live against real Cognito (us-west-2): createUser→setPassword→signIn, addUserToGroup→requireRole on a fresh token, disable/enable gates signIn, deleteUser, and revokeUserSessions all pass (5/5). Live run surfaced a real mock-vs-AWS parity point: AdminUserGlobalSignOut revokes refresh tokens but does NOT invalidate an already-issued access token, so checkAuth does not flip immediately on AWS (the mock deletes the session record and does). Corrected the test assertion and the README accordingly.
Regenerate API.md to include the admin surface (AdminOptions, AdminSurface, GroupAdmin/LifecycleAdmin, AdminGetterOf, AdminUser, AdminCreateInit) and the const O class generic. Add a minor changeset documenting the feature + the const O breaking change.
bb-agent's committed API.md is out of date with its BedrockModels source on main (model-id renames + DEFAULT/BALANCED/SMART reordering). The repo-wide check:api gate regenerates and diffs ALL packages, so this pre-existing staleness blocks any PR. Regenerated to match source — no source change. Isolated in its own commit so it can be dropped if handled separately.
harsh62
force-pushed
the
docs/bb-auth-cognito-admin-design
branch
from
June 30, 2026 15:40
f9a0232 to
7941c3e
Compare
The repo-wide changeset-coverage gate requires a changeset for any package with file changes; the bb-agent API.md regen (stale-on-main fix) needs a patch entry.
- DESIGN.md (must-fix): move the admin surface from Out to In (opt-in auth.admin handle), update the IAM section to the opt-in grant model, add an Admin surface section + Key Design Decision aws-devtools-labs#7, cross-link the implementation plan. Resolves the shipped-doc-vs-code contradiction. - @aws-blocks/blocks (should-fix): re-export the admin types so umbrella consumers can name AdminOptions/AdminUser/GroupAdmin/etc.; regenerate blocks API.md; add changeset. - Superseded design doc (minor): reduce the 755-line BB-auth-cognito-admin.md to a redirect stub, removing KitContext/KIT_*/isKitError rebrand residue. - AWS admin scan() (minor): pin Limit: 60 to match the documented page-size contract and the other paginators. Full package suite: 224 pass, 0 fail.
…ndings 'email' is a built-in Cognito standard attribute; declaring it in userAttributes is invalid per DESIGN.md (built-ins are implicit). Harmless under the old wide generic, but once AuthCognito<const O> narrowed the literal, CognitoUser.attributes emitted BOTH 'email' and 'custom:email' in the generated OpenRPC spec — which the Dart codegen turned into a duplicate field / colon-in- identifier, breaking 'dart analyze' with error-level diagnostics (Native SDK E2E / Dart E2E). Removing the redundant declaration restores the open-map attributes shape. Root cause of the Dart E2E regression since the const-O commit; Swift was unaffected because its generator maps the shape to a Map. Verified locally: regenerated spec has 0 custom:email; dart analyze on the regenerated client reports 0 errors (infos only, which analyze tolerates).
Addresses reviewer-identified gaps in the admin surface:
- Gap 1 (typed reads): AdminUser is now AdminUser<O> — getUser/scan/listUsersInGroup
return narrowed groups (GroupOf<O>) and attributes (ReadAttrOf<O>), matching the
client-side CognitoUser. createUser attributes narrow via AttrOf<O> too, catching
typos the way signUp does.
- Gap 3 (ungranted-action feedback): a new AdminActionGate rest-param makes calling
an ungranted method (e.g. deleteUser under actions:['groups']) a COMPILE error —
variance-safe because the gate is in parameter position, not the surface shape
(verified against the call sites the earlier shape-narrowing attempt regressed).
A runtime guard also fast-fails with a clear message instead of AWS AccessDenied.
- Gap 4 (scan filter): scan(filter?) accepts an AdminUserFilter mapped to Cognito's
ListUsers Filter (startsWith/equals); mock filters in memory.
- Gap 5 (readability): setUserPassword(username, password, { permanent }) uses a
named options object instead of a bare boolean.
Umbrella @aws-blocks/blocks re-exports the new public types. API reports + changeset
updated. Package suite: 228 pass, 0 fail; full type-test incl. gate + variance guard.
…AWS parity)
Live sandbox e2e for the new admin runtime paths surfaced two mock-vs-AWS
parity bugs the unit tests couldn't:
- AWS admin.createUser passed custom attributes unprefixed, so Cognito rejected
them ('attribute department is not defined in schema'). Now runs them through
prefixCustomAttrs like signUp and the mock do.
- AWS admin.getUser never populated AdminUser.groups (AdminGetUser doesn't return
memberships), so the typed groups field was always empty on AWS. Now fetches
them via AdminListGroupsForUser, matching the mock.
Adds sandbox e2e coverage for the new surface: getUser attribute+group round-trip
(Gap 1) and scan() with a Cognito ListUsers Filter (Gap 4), plus the backend
routes they drive. Verified live (us-west-2): all 7 admin e2e tests pass.
…up read
getUser is lifecycle-gated but reports group memberships via
AdminListGroupsForUser, which was only in the 'groups' IAM slice. A pool with
admin: { actions: ['lifecycle'] } would grant AdminGetUser but not
AdminListGroupsForUser, so getUser 500'd with IAM AccessDenied only in the
deployed AWS runtime (mock reads groups from memory; the admin:{} sandbox e2e
grants everything — both hid it).
- Add AdminListGroupsForUser to the lifecycle IAM slice (shared with groups).
- Make getUser's group fetch best-effort: swallow AccessDenied and return
groups: undefined rather than failing the whole read under a hand-narrowed policy.
- Regression tests: CDK lifecycle-only self-sufficiency (AdminGetUser +
AdminListGroupsForUser both granted) and a mock lifecycle-only getUser path.
Caught in PR review; a self-inflicted gap from the earlier getUser groups fix.
pranavosu
previously approved these changes
Jul 22, 2026
Reserve minor bumps for real API changes during preview; the const O breaking-change condition is unlikely to hit in practice (only literal-variable call sites, 2 found repo-wide).
🦋 Changeset detectedLatest commit: 90f0340 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
… gate The shipped AdminActionGate (types.ts) gates each admin method at compile time via a trailing rest parameter, so an ungranted call is a compile error (proven by admin.types-test.ts) — not merely a runtime AccessDenied. The parameter-position gate keeps AuthCognito<O> covariant, so the 'not possible without breaking variance' framing is also wrong. Update README:170 + DESIGN admin-surface bullet and Key Design Decision aws-devtools-labs#7 accordingly, and fix the stale setUserPassword signature (options object, not a bare permanent boolean).
mattcreaser
approved these changes
Jul 22, 2026
pranavosu
approved these changes
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the opt-in
auth.adminhandle onAuthCognito— server-side group-membership and user-lifecycle admin operations — adopting the in-package counter-proposal instead of a separate@aws-blocks/bb-auth-cognito-adminpackage.Without an
adminoptions object,auth.adminis a compile error and the pool gets noAdmin*IAM grant (least privilege, byte-identical to today's role).What's included
types.ts):AdminOptions,AdminSurface,AdminGetterOf,GroupAdmin/LifecycleAdmin, plusadmin?onAuthCognitoOptions.flushToDisk), AWS (Admin*SDK commands), browser stub, CDK (secondPolicyStatementscoped byactions).const Oclass generic so inline option literals narrow withoutas const(separate commit — see breaking-change note).Design decision surfaced during implementation
The compiler caught a variance regression: narrowing the method set by
actionsvia a conditional overOforcedAuthCognito<O>invariant and broke 14 existing call sites. Resolved:actionsscopes the IAM grant only; the typed surface is always the full set (group names still narrow). An ungranted call fails at runtime with IAMAccessDenied.Verification
tsc --buildclean across all entries;conditional-exportsparity 20/20.AdminUserGlobalSignOutrevokes refresh tokens but does not invalidate an already-issued access token, socheckAuthdoes not flip immediately on AWS (mock deletes the session record and does). Test + README corrected.const OnarrowsrequireRole/updateUserAttribute/updateMFAPreferenceparams for inline-literal options. Callers passing widenedstringvariables now need a cast or literal args (fixed the 2 affected test-app call sites here).Unrelated pre-existing issue noted
The full
test:e2e:sandboxrun hitsWebSocket is not definedinbb-realtimeon Node 20 (needs ≥22 or a polyfill) — independent of this change; worth a separate ticket.