Repository navigation
Conversation
When the account service offers assisted recovery, the backup gets an atomic-assisted wrapper whose key the service hands to a recently signed-in account (the DEK and the secret stay in the browser). So: - A new account's backup is wrapped by the account alone: onboarding asks for no passkey and shows no recovery code. - The sign-in and restore steps unlock it by themselves after a recent sign-in; an old sign-in is asked to sign in again. - Backups made before this get the wrapper the next time a passkey or recovery code opens them. - Both steps offer "Continue with Google", straight back to the app. Without the endpoints (a standalone server), nothing changes.
|
Screenshots from a local stack (saas with a key file, portal, this branch's app, a node): a new account going straight to its workspace with an Generated by Claude Code |
The app's sign-in step offered Google and an agent secret, the portal offered Google, passkey and an email link, and its homepage panel had no passkey. Every account sign-in now renders AccountSignIn from @tomic/service-ui, so the options and their order are the same everywhere. The app does the passkey and email-link sign-in itself against the account service; builds without an account service show none of it and send no requests for it. Also names assisted recovery on the account page instead of falling back to "your recovery password".
|
New commit 42de79a: every account sign-in (the portal's sign-in page and homepage panel, the app's sign-in and restore steps) now renders one Tested on a local stack: a new device opened the account's identity from the app's sign-in step with only an email link, and again with only a passkey. Screenshots (portal sign-in, homepage panel, app sign-in in light, dark and phone widths) are kept out of the repo and posted in the project thread, under Generated by Claude Code |
An app that cannot hold the account cookie showed a device-link code instead of the sign-in options. It now shows the same AccountSignIn; each option opens the portal in the system browser at /link with that option (via=google, passkey or email) and this device's code, and the app continues once the code is approved there.
AccountSignIn gets a "How safe is each option?" link that opens a dialog: what a passkey, Google and an email link each trust, who learns what, and what signing in opens. With assisted recovery on it says plainly that Atomic keeps a key that, with its database, can open the identity backup.
Account recovery gets a 'Let Atomic help me recover' switch. Off removes the assisted wrapper, so only a passkey or recovery code opens the identity, and the app adds none in the background. A new assisted wrapper records the service key id; when an unlock says the key was rotated, the app re-wraps with the current key. The app shows an account's address and keeps the account id for backups and bindings.
…count opens With account ids the app shows the address from /api/me in the profile, the share dialog and onboarding. A backup that only signing in opens can now get a passkey from the recovery card: a recent sign-in reaches the DEK, as it does for unlocking.
The onboarding flow now shows the account's address; without it in the session mock the invite test fell through to signed out.
The sign-in step now shows the same account options as the portal, each finished in the system browser, so the separate "Forgot it? Restore from" button is gone there. The spec checks for the options instead.
|
On this commit the downstream atomic-saas check/clippy/test job is green. Locally, the e2e specs that failed on the earlier head pass: native-account-restore (fixed in 3e7861c), sign-in-without-data, search, second-device-load and canvas-live-update. Once Docker on Mancave is back, this needs a fresh dispatch of main.yml. Generated by Claude Code |
|
Run 4712 ( I don't think the cause is this PR:
I don't know of a fix to port. Once the run finishes (Downstream is still queued), I'll re-run the failed job once. GitHub refuses a re-run while the run is still in progress. Generated by Claude Code |
|
The re-run of run 4712 failed within seconds with Generated by Claude Code |
…roviders Provider buttons now read just the provider's name, side by side in one row, instead of "Continue with ..." stacked on top.
Side by side they need about 330px; on a phone the names would clip. Icons keep their size inside a narrow button.
|
Landed on develop through the batch #1946 (squash f2a07aa), tested together on Mancave. Closing this PR since its commits are in that batch. atomic-saas #164 is next. Generated by Claude Code |
Requested by Joep · project thread
Related Issues
Related: #277 (sign in with an existing account such as Google). Needs ontola/atomic-saas#164 for the endpoints; without them nothing changes.
Checklist
What this does
Before: on a new device, a hosted account holder signs in and is then asked for their passkey or recovery code to open their identity. New accounts must register a passkey and save a recovery code during onboarding.
After: when the account service offers assisted recovery, signing in is enough.
atomic-assistedwrapper only, so onboarding goes straight from the profile name to the workspace: no passkey prompt, no recovery code.How
The wrapper is one more envelope-v2 wrapper next to
webauthn-prfandrecovery-code. The browser asksPOST /api/recovery-secret/assisted-keyfor the key-encryption key for a salt and does the AES-GCM wrap and unwrap itself, exactly as with a passkey's PRF output, so the DEK and the agent secret never leave the browser.GET /api/auth/providerssays whether the service offers it (assisted_recovery) and Google (google). New code:helpers/managed/accountProviders.ts, the assisted functions inrecovery.ts,GoogleSignInButton.tsx, and anonBackupWithAccountprop onNewIdentitySection.Tests
recovery-assisted.test.ts: a backup built for the account opens with the account alone and nothing secret is sent; an old sign-in getsFreshSignInRequiredError.pnpm run -r lint.atomic-assistedwrapper, then a second browser that only signed in by email link was opened as the same agent and landed in the workspace. Screenshots in a comment.Generated by Claude Code