feat(auth): add opt-in password step to interactive login - #99
Merged
Conversation
`bd init` writes a local Dolt database and a credential key next to the repo. Neither belongs in version control; the tracker syncs through refs/dolt/data on the remote instead.
Production does not seal on the server: the SDKs seal client-side with a caller-supplied cookie password the API never sees, so the field is always null in a real response. The emulator's own sealing pushed authkit-nextjs session cookies past the 4096-byte browser cap (issue #93), and the @workos-inc/node refresh call, which sends the API key as client_secret, opted into it on every refresh.
The interactive login page asked for an email and nothing else, so a Playwright suite written against hosted AuthKit had no password field to fill and no wrong-password path to assert. The gap surfaced when a customer's CI kept reusing one real AuthKit test user, whose sessions piled up into the millions. Opt-in via --interactive-password rather than the default, because the one-step email page is what every existing interactive suite was written against. A verified password is remembered under a short-lived token across the organization page so the secret never sits in a hidden field, and the auth code records the method so the exchange emits authentication.password_* instead of claiming OAuth.
Greptile SummaryThe PR adds an opt-in password, email-verification, and MFA sequence to interactive AuthKit login while preserving the existing email-only mode. It also records the authentication method on authorization codes, returns
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
|
Store.setData is a plain Map.set, so clearing the one-time token with setData(key, undefined) kept the key allocated: a long-lived emulator grew by one entry per multi-organization password login. Expired tokens that are presented again are dropped the same way.
A token minted for an organization page that is never submitted was only checked for expiry when presented again, which an abandoned page never does. Each abandoned login therefore left one entry behind for the emulator's lifetime. Sweeping expired entries whenever a new one is minted bounds the store to the tokens from the last ten minutes.
A correct password on the interactive page minted a code for any account, so a browser suite could sign in a user the `password` grant would have stopped with email_verification_required or mfa_challenge, and the exchange then reported a Password login that production would never have completed. The gates the grant enforces now follow the password page in the order hosted AuthKit shows them: the emailed code for an unverified mailbox, then the authenticator code for an enrolled factor, each a page carrying the same login token so a POST cannot skip past an open gate. The exchange reports what the API's final grant would: the event of the gate cleared last, with the session and authentication_method still recording Password. Mirroring the grants keeps a webhook consumer's view of a login the same whether it came through the browser or the API, at the cost of one succeeded event per login rather than one per factor. Also covers two token checks the tests left unexercised, a token minted for another user and an expired one presented again, and asserts on the specific token key instead of a prefix delete that mutated the store and depended on test order.
The sweep of expired interactive login tokens, and the drop of an expired token presented again, removed only the token. The email_verification or authentication_challenge the login's open gate was waiting on stayed in its collection, so every abandoned gated login left one record behind for the emulator's lifetime. Both paths now delete the referenced record along with the token. Nothing else could have redeemed it: the API grants reach their records through a pending_authentication_token of their own, which never points at an interactive login's.
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
--interactive-password/interactiveAuth: { password: true }adds the step hosted AuthKit puts after the email: a user who has a password gets a password page before any organization selection. The interactive page was email-only, so a Playwright suite written against hosted AuthKit had no password field to fill and no wrong-password path to assert. Opt-in, because the one-step email page is what every existing interactive suite was written against; plain--interactiveis unchanged.authentication.password_failedwithinvalid_credentials, the same event thepasswordgrant emits. Nothing reaches the callback until the password is right.authentication.password_succeededand returns"authentication_method": "Password"instead of claiming OAuth for a login it verified with a password.organization_idwithout that token lands back on the password page, so the check cannot be skipped.sealed_sessionis now alwaysnullfrom/user_management/authenticate. Production never seals on the server; the SDKs seal client-side with a cookie password the API never sees. The emulator's own sealing pushed authkit-nextjs session cookies past the 4096-byte browser cap. Fixes authenticate returns sealed_session on every SDK call, pushing authkit-nextjs session cookies past the 4096-byte browser cap #93.Also ignores the local Dolt database and credential key that
bd initwrites.Usage
A user without a password skips the page. MFA challenges and the email-verification gate are not part of it. The README gains a "Requiring a password" section under Interactive Auth.