Verify sign-in
This is a procedure for the lead, not a record of completed acceptance. Local SQLite checks and stubbed frontend/Cypress responses prove local behavior only. They do not prove real email delivery, provider callback success or Cloudflare configuration. Do not run these steps without authority for the selected deployment and test identities.
Before starting
Section titled “Before starting”- Use an authorized acceptance deployment and provisioned test identities. Keep an independently verified superadmin recovery method available.
- Confirm required Worker bindings are present using the deployment’s approved secret-presence process. Record only presence, never values. Confirm enabled methods on the Installation page, under Sign-in methods.
- Confirm frontend/backend origins and provider callback registrations match the deployment:
BACKEND_URL+/api/auth/callback/googleor/api/auth/callback/github. - Use separate browser profiles for two sessions of the target user, a second active superadmin, and an unknown identity. Remove Access cookies from the session-only revocation profiles so Access cannot independently authenticate a revoked session.
- Run
frontend/cypress/e2e/journey-account-security.cy.tsin the existing local rig. The lead owns execution and live visual review at 390×844 and 1440×900: loading, empty, linked, error, long email, confirmation, refusal, pending, success and focus return. Passing assertions alone are not visual acceptance.
Real-provider gates
Section titled “Real-provider gates”| Gate | Procedure | Required result |
|---|---|---|
| Resend delivery | Request a magic link for a known active identity with magic link enabled. Receive it at the authorized mailbox and follow it. | The email arrives, the user signs in to the expected CMS account, and a protected request succeeds. A provider HTTP acknowledgment alone does not prove delivery. |
| Invitation delivery and address proof | From Settings → People, invite an authorized mailbox. Open the emailed link, type that address, and enter the emailed code. Separately, forward the link to a second authorized mailbox and try to accept it there. Retry a used, resent and cancelled link. | Both emails arrive, acceptance grants exactly the invited role on that site, and the new password signs in. The forwarded link cannot accept without the code from the invited mailbox. Used, replaced and cancelled links are refused alike. |
| Magic-link replay and expiry | Reopen the consumed link. Request two links, then try the older one. Separately retain an unused link beyond its 15-minute lifetime and try it. | Consumed, replaced and expired links cannot create another session. Requesting for an unknown address has the same public response and creates no account. |
| Google callback | Sign in with a real Google identity whose verified email matches a provisioned CMS account. Try a different unknown email and an unverified address if the provider permits an authorized fixture. | The user signs in to the matching existing CMS account, with no duplicate account or signup for an unknown email. Unverified proof is refused. If a real unverified fixture is unavailable, leave that subcase unverified. Do not substitute a stub and mark it accepted. |
| GitHub callback | Use a real GitHub identity whose primary verified email matches a provisioned CMS account. Exercise hidden-public-email behavior and a primary-email mismatch using authorized identities. | Primary verified address from GitHub is used. A secondary matching address alone cannot claim the CMS identity. Unknown/mismatched identity is refused. |
| OAuth replay/invalid state | Revisit a consumed callback and an expired or invalid state through the browser, without saving callback URLs or authorization codes in evidence. | No session or provider link is created from reused, invalid or expired proof. |
| Link-time revocation and token discard | Sign in through a newly linked provider while an older CMS session exists in another profile. Verify the older session against a protected API. Perform an approved boolean-only database check for provider token presence. | Older session is refused. Provider access, refresh and ID token columns remain null. Never export those columns or their values. |
| Unlink and session revocation | Establish two CMS sessions for the same user. Open Account security, cancel once, then confirm Google unlink. Repeat independently for GitHub. Verify both old sessions with a protected request. | Cancel changes nothing. Confirmation removes only the selected connection owned by that user, signs the user out in the initiating UI, and makes both old sessions fail. Other users’ links/sessions remain valid. Use a fresh sign-in to inspect remaining links. |
| Ordinary last-provider unlink | Use an ordinary user with only one OAuth connection while a separate active superadmin retains a usable method. | Unlink succeeds and all target-user sessions end. Recovery or later same-email provider sign-in is a separate step. |
| Last usable superadmin | In an authorized isolated acceptance deployment, make the target provider the only usable method across all active superadmins, retaining an approved operator recovery path. Attempt unlink. Then establish another active superadmin with a verified usable method and repeat. | First attempt returns 409 would_lock_out_superadmins. Account and sessions stay intact. Second attempt can succeed even if the target retains no personal method. Disabled, incomplete or unlinked alternatives do not count. |
| Passkey on a real device | With passkeys switched on, sign in another way and add a passkey under Account security on a laptop (Touch ID or Windows Hello) and on a phone. Sign out, then use Sign in with a passkey on each. Rename one and remove one. Try adding again from a session older than 24 hours. | Each device signs in the expected CMS identity with no address typed. A removed passkey no longer signs in. The old session is asked to sign in again before adding. |
| Passkey lost-device recovery | Using an account whose only other method is a password, remove access to the device holding its passkey. Sign in with the password, remove the lost passkey, then try it again. With mail and email links on, repeat with a passkey-only account using an email sign-in link instead of the password. Separately, deactivate an account that has a passkey and try to sign in with it. | Password sign-in still works and the removed passkey is refused. The last remaining method cannot be removed. The deactivated account gets no session. |
| First-admin claim, when accepting a fresh install | On separately authorized fresh instances, use a bound claim with each opted-in method. Try wrong-email, consumed and expired claims. Never reset an existing deployment for this check. | Only the bound email can create the first superadmin. Replay/expiry fails. Ordinary sign-in never creates users. |
Safe evidence
Section titled “Safe evidence”Record deployment label/version, date, method, scenario, expected/observed outcome, response status/reason, and pass/fail/unverified. Screenshots may show UI state with personal addresses redacted. Record boolean/count comparisons for account/session invariants only through authorized access.
Do not retain cookies, session tokens, passwords, claim links, email links, OAuth codes/state, provider identity ids, provider tokens or secret values. Do not retain full database dumps or unredacted HAR/network exports. Browser profiles holding acceptance sessions are credential-bearing and should be closed or signed out after the exercise.
Gates that remain open
Section titled “Gates that remain open”- Lead-run focused Cypress and live visual review at both viewports.
- Actual Resend (magic link and invitation), Google, GitHub and real-device passkey acceptance, including the applicable negative/replay/expiry cases above.
- Live Cloudflare secret-presence confirmation.
- Docs-site deployment after the normal owner gate.
Record results separately when performed. This procedure makes no deployment or provider-success claim.
Unreleased / pre-MVP; no stable version
Source revision: fe23db481c422b40e317b92fc98116b4c0c26a87 (modified checkout)
Build date (UTC): 2026-10-18T09:22:34.630Z
Upgrade target: unavailable: modified checkout. Release notes