feat(auth): pair passkeys with existing account and manage them#29
Conversation
- POST /auth/register/begin with JWT adds a passkey without email (same as magic-linked accounts pairing a device). - Track passkey metadata on the user row; backfill on successful login. - GET /auth/passkeys and DELETE ?id= for authenticated management. - Account sheet UI: list, add on this device, remove. - Omit passkeys from /auth/me JSON; allow CORS DELETE. Co-authored-by: Jakub Doboš <kubo6472@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughAdds passkey management: users can list, add, and remove passkeys. Changes span worker endpoints and storage, client API/auth logic, a new account-passkeys UI module, event wiring in main, styling, and minor HTML copy updates. ChangesPasskey Management Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/styles/main.css`:
- Line 524: In .passkeys-row-sub replace the deprecated declaration "word-break:
break-word" with the modern overflow-wrap property; update the rule for the
class .passkeys-row-sub to remove word-break: break-word and add "overflow-wrap:
break-word" (or "overflow-wrap: anywhere" if you want more aggressive breaking)
so long credential IDs and transport names wrap correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c02dbaab-634b-4f98-b0a3-5da7fccb99e4
📒 Files selected for processing (13)
index.htmlsrc/api.tssrc/auth/passkey.tssrc/main.tssrc/styles/main.csssrc/types.tssrc/ui/account-passkeys.tsvite.config.tsworker/src/auth/passkey.tsworker/src/index.tsworker/src/lib/http.tsworker/src/lib/kv.tsworker/src/types.ts
Co-authored-by: Jakub Doboš <kubo6472@users.noreply.github.com>
Co-authored-by: Jakub Doboš <kubo6472@users.noreply.github.com>
Summary
Users who sign in with a magic link (or any session) can now add a passkey without re-entering email, see which passkeys are on the account, and remove ones they no longer trust or use.
Listing passkeys by email without authentication is intentionally not exposed (that would be a credential-enumeration / privacy risk). Management is only for the signed-in user.
Backend
POST /auth/register/begin: ifAuthorization: Bearer <jwt>is present, starts WebAuthn registration for that user (no JSON body). Without JWT, behaviour is unchanged:{ email }required; same email still resolves to one account viaupsertUserByEmail.POST /auth/register/finish: unchanged contract; after storingcred:*, appends metadata touser.passkeys[].POST /auth/login/finish: after a successful assertion, backfillsuser.passkeysif this credential was missing (covers accounts created before this index existed).GET /auth/passkeys: returns{ passkeys: PasskeyMeta[] }for the JWT subject.DELETE /auth/passkeys?id=<credentialId>: deletescred:*and removes the entry fromuser.passkeyswhen it belongs to the caller.GET /auth/me: stripspasskeysfrom the JSON payload so the field is only returned from the dedicated endpoint.CORS:
DELETEallowed on preflight.Frontend
registerWithPasskey()can be called withoutemailwhen the API client already has a bearer token.Types
PasskeyMeta:id,createdAt,transports(mirrors worker).Summary by CodeRabbit