feat(cli): bgagent change-password + force-rotate on first login (#238) - #680
Open
scottschreckengaust wants to merge 1 commit into
Open
feat(cli): bgagent change-password + force-rotate on first login (#238)#680scottschreckengaust wants to merge 1 commit into
scottschreckengaust wants to merge 1 commit into
Conversation
Part 1 — `bgagent change-password`: - New `cli/src/commands/change-password.ts`, registered in bin/bgagent.ts. - Interactive: prompts current password + new password (twice, confirmed). - `auth.ts changePassword()` re-authenticates with the current password to mint an in-memory access token (ChangePassword requires an AccessToken, which the CLI never persists) then calls `ChangePasswordCommand`. Wrong current password → clear error before any change; weak new password → Cognito's `InvalidPasswordException` surfaced verbatim. Part 2 — force-rotate on first login: - `login()` now handles the `NEW_PASSWORD_REQUIRED` challenge: prompts for a new password, answers via `RespondToAuthChallengeCommand`, persists tokens. A non-interactive login (no prompt) surfaces a clear error instead of hanging. - Invites now issue a TEMPORARY password: `adminInviteUser` drops the `AdminSetUserPassword` (Permanent: true) call, leaving the user in FORCE_CHANGE_PASSWORD so the admin-shared credential stops being valid once the teammate logs in once. `admin reset-password` keeps its permanent-password path unchanged. Docs: USER_GUIDE + LINEAR_SETUP_GUIDE teammate-onboarding sections updated for the temp-password first-login flow; Starlight mirrors regenerated. Uses the AWS SDK's Cognito commands throughout — no hand-rolled crypto, token storage, or SRP. No passwords or tokens are logged. Closes #238 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 29, 2026
scottschreckengaust
marked this pull request as ready for review
July 29, 2026 01:32
Contributor
Author
✅ Acceptance summary (for the reviewer)#238 —
🔀 Merge guidance (for the reviewer)Cluster 🤖 orchestrator note (agent) — promotion is orchestrator-driven; merge remains a human action. |
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
Two related additions to the
bgagentCLI auth flow (see theClosesline below):bgagent change-password— a new interactive command that lets any logged-in user rotate their own Cognito password.bgagent loginhandles Cognito'sNEW_PASSWORD_REQUIREDchallenge so the teammate sets a permanent password only they know on first login.Closes #238
Root cause / context
loginhad no challenge handling.cli/src/auth.tsloginonly issued anInitiateAuthCommandand expectedAuthenticationResultdirectly — any Cognito challenge (likeNEW_PASSWORD_REQUIRED) fell through to the "Unexpected authentication response" error.adminInviteUser(cli/src/cognito-admin.ts) calledadminSetPermanentPassword→AdminSetUserPasswordCommand({ Permanent: true })(thePermanent: truelived atcli/src/cognito-admin.ts:151). So the admin-generated string the admin shared over Slack/email stayed a valid credential forever, and there was nochange-passwordcommand to rotate it.The fix
Part 1 —
change-password(cli/src/commands/change-password.ts, registered incli/src/bin/bgagent.ts):prompt-secrethelper).auth.ts::changePassword()re-authenticates with the current password to obtain a short-lived, in-memory access token.ChangePasswordCommandrequires anAccessToken, which the CLI deliberately does not persist (only the ID + refresh tokens the REST authorizer needs) — re-auth avoids widening the on-disk credential surface and doubles as verification of the current password (wrong one →NotAuthorizedException→ "Current password is incorrect." before any change is attempted). Cognito enforces the password policy server-side; a weak new password surfaces asInvalidPasswordException.Part 2 — force-rotate on first login:
auth.ts::login()now branches onresult.ChallengeName === NEW_PASSWORD_REQUIRED: it prompts for a new password (via a caller-supplied callback, keeping TTY concerns in the command layer), answers withRespondToAuthChallengeCommand, and persists the resulting tokens. A non-interactive login (no prompt callback, e.g.--passwordpassed) surfaces a clear error instead of hanging.adminInviteUserdrops the permanent-password step —adminCreateUseralready setsTemporaryPassword, which lands the user inFORCE_CHANGE_PASSWORD. That's exactly the state that triggers the first-login challenge.admin reset-passwordkeeps its permanent-password path unchanged (admin account recovery is out of scope).Why the SDK commands are the right call:
ChangePasswordCommandandRespondToAuthChallengeCommandare the AWS SDK's first-class primitives for exactly these flows — no hand-rolled crypto, password hashing, SRP, or token storage.@aws-sdk/client-cognito-identity-provideris already a CLI dependency (no new dependency introduced).Testing
MISE_EXPERIMENTAL=1 mise //cli:build→ 657 tests pass, compile clean, eslint clean;change-password.tsat 100% coverage.New/updated cases:
changePassword, reports success; access token never written to disk.ChangePassword.InvalidPasswordExceptionmessage.NEW_PASSWORD_REQUIRED— prompts, responds with the challengedUSERNAME/NEW_PASSWORD+ echoedSession, persists tokens; non-interactive path errors clearly; policy rejection surfaced.adminInviteUsernow issues exactly oneAdminCreateUserwithTemporaryPasswordand noPermanentflag (regression guard);UsernameExistsExceptionstill maps to aCliError.Security note
console.*ordebug()— only status strings and non-secret config fields (region, client_id, user_pool_id).ChangePasswordaccess token is minted in memory and discarded — never persisted to~/.bgagent/credentials.json.gitleaks git origin/main..HEAD→ no leaks). The 3 gitleaks findings onmise run security:secretsare a pre-existing full-history sweep of the shared store, not in this diff.Dependencies / related
cli/src/bin/bgagent.ts(UA command registration). This PR keeps itsbin/bgagent.tschange to a single import + a singleaddCommandline to minimize the rebase conflict; if feat(observability): solution attribution via native AWS_SDK_UA_APP_ID (#319, alt to #338) #345 merges first, this should rebase with a trivial 2-line conflict.Self-review notes
/review_prself-review complete (security/auth, silent-failures, test coverage, scope): approve-with-nits, zero code blocking. Fixed one blocking finding — theCloses #238line was previously backticked and did not auto-close; it is now unformatted (closingIssuesReferencesresolves to[238]).git diff origin/main..HEADare a stale-branch artifact — this branch is 1 commit behindorigin/main, which bumped several pinned action SHAs after the branch was cut. HEAD (f4737a6f) touches no.github/workflows/files; a merge/rebase ofmainat integration time resolves them automatically.🤖 Generated with Claude Code