CU-02: Auth lifecycle: register, email verification, login, forgot/reset password, delete account
Priority: P0 Accounts/sessions: Fresh throwaway account created during the run via the plus-alias ravikantguptaofficial+auth-<RUNID>@gmail.com. The agent must ALSO already be signed into Gmail in the browser as ravikantguptaofficial@gmail.com (plus-aliases deliver to that single inbox) so it can read the verification and reset emails. No other Calendo session is required — this suite creates and tears down its own account. Parallel-safe: Yes — runs against a brand-new throwaway account scoped by RUNID; touches no shared host data, event types, or availability. Exclusive (rewrites global host availability?): No. Estimated time: ~22 minutes. L3 reality checks: Yes — (1) the real email-verification email arriving in Gmail and its link working; (2) the real password-reset email arriving in Gmail and its link working. No calendar L3 in this suite.
Goal
This suite proves the complete email/password identity lifecycle that every self-serve Calendo user depends on: a new person can register with email + password, is gated until they verify their email via a real email they receive, can log in and is correctly rejected on a wrong password, can recover access through a real "forgot password" email and set a new password (with old sessions/passwords invalidated), and can finally delete their account and lose access. If any link in this chain is broken, users either cannot get in, cannot get back in, or cannot leave — all of which are launch-blocking. Because Calendo is a public SaaS where anyone signs up, this is the highest-trust path in the product.
Preconditions
- The browser must be logged into Gmail as
ravikantguptaofficial@gmail.comat https://mail.google.com so the agent can open and read the verification + reset emails. If Gmail is NOT logged in, STOP and flag this as a precondition failure — do NOT attempt a cold Gmail password login. See00-setup-preconditions.md. - No existing Calendo account may exist for
ravikantguptaofficial+auth-<RUNID>@gmail.com. Because RUNID is unique per run this should always be true; if registration returns an "already" error (Step 4), the RUNID collided — pick a new RUNID and restart. - Production https://calendo.dev must be reachable and email delivery (Resend) operational. If the verification or reset email never arrives within the wait window, flag as an environment/precondition failure rather than retrying indefinitely (see
00-setup-preconditions.md). - This suite must NOT use the P1 logged-in Calendo session. It deliberately works with a throwaway account. If the browser is already logged into Calendo as P1 in the same tab, open the registration page in a context where that session does not auto-redirect you (a fresh tab/window navigated directly to
/auth/register.html); registering creates a NEW session cookie for the throwaway account.
Test data
- RUNID: pick a fresh UTC token at execution time, e.g.
20260601-1530. Use the SAME RUNID for every value below. - Name:
Auth Test <RUNID> - Email (account):
ravikantguptaofficial+auth-<RUNID>@gmail.com - Initial password:
AuthInit-<RUNID>!9(≥8 chars) - New password (after reset):
AuthReset-<RUNID>!9(≥8 chars, different from the initial) - Record the chosen RUNID, both passwords, and the full email address at the top of the results report — later steps and the human reviewer need them.
Steps
- Action: Open a fresh browser tab and navigate to the registration page (https://calendo.dev/auth/register.html). Expect: The "Create your account" card renders with a Full name field (
#name), Email field (#email), Password field (#password), a "Create account" button (#submitBtn), and OAuth buttons "Sign up with Google" (.oauth-google) and "Sign up with Microsoft" (.oauth-microsoft). [L1]
- Action: Confirm the OAuth buttons are present (
.oauth-google,.oauth-microsoft) and that the "or" divider (.divider) is visible — this verifies OAuth is configured in production. Do NOT click them. Expect: Both OAuth buttons visible. [L1] Capture screenshot:cu02-01-register-form
- Action: Type the name
Auth Test <RUNID>into Full name (#name),ravikantguptaofficial+auth-<RUNID>@gmail.cominto Email (#email), andAuthInit-<RUNID>!9into Password (#password). Expect: Fields hold the typed values; no inline validation error. [L1]
- Action: Click "Create account" (
#submitBtn). Expect: No red error box (#error) appears. The card content is REPLACED in place with a verification gate that reads "Before continuing, we need to verify your email address.", "Please check your inbox for a confirmation link.", the email address...+auth-<RUNID>@gmail.comshown in bold, and a "resend it to you" link (#resendLink). (Perpublic/auth/register.html, on success the page does NOT redirect to the dashboard — it swaps in this gate. A session cookie IS set behind the scenes.) [L1]- If instead a red
#errorappears containing "already": the RUNID collided with an existing account — STOP, pick a new RUNID, restart at Step 1. Capture screenshot:cu02-02-verify-gate
- If instead a red
- Action: Confirm the new account's session is active by opening https://calendo.dev/dashboard/ in the same tab. Expect: Because the email is not yet verified, the dashboard's init guard force-redirects to the standalone verification page https://calendo.dev/auth/verify.html (per dashboard
init()atif (!currentUser.email_verified) → /auth/verify.html). That page shows the heading "Before continuing, we need to verify your email address.", the account email in bold (#userEmail), a "resend it to you" link (#resendLink), and a status line "Checking for verification…" (#pollStatus) that polls every 3 seconds. This confirms (a) the session exists and (b) unverified users are correctly gated. [L1][L2] Capture screenshot:cu02-03-verify-page-gated
- Action: On https://calendo.dev/auth/verify.html, click the "resend it to you" link (
#resendLink). Expect: A confirmation message appears (#resendMsg) reading "Verification email resent! Check your inbox." in green. (This callsPOST /api/auth/resend-verification.) [L1]
- Action: L3 — verify the email actually arrived and the link works. See L3 reality checks → Verification email below for the exact Gmail search and link-click sub-steps. Expect: After clicking the verification link, the browser lands on the worker-rendered "Email Verified" page (a checkmark, heading "Email Verified", text "Your email address has been verified successfully. You're all set!", a "Continue Setup" button to
/onboarding/, and "Redirecting in 2 seconds…"), then auto-redirects to https://calendo.dev/onboarding/. [L3] Capture screenshot:cu02-04-email-verified
- Action: Return to https://calendo.dev/dashboard/ (if onboarding appears, you may dismiss/skip it; the focus here is auth). Expect: The dashboard now loads fully (it no longer bounces to verify.html), the welcome header (
#welcomeHeader) shows the nameAuth Test <RUNID>, and the amber email-verification banner (#emailVerificationBanner, text "Please verify your email address to get the most out of Calendo.") is NOT displayed. This proves verification persisted server-side. [L2] Capture screenshot:cu02-05-dashboard-verified-no-banner
- Action: Capture the throwaway account's host slug for the cleanup/records: on the dashboard, read the booking-link value element (
#bookingLinkValue) and note the portion afteruser=. Expect: A non-empty slug string. Record it. [L2]
- Action: Log out. Click the logout control (
#logoutBtn) in the dashboard. Expect: The browser redirects to the login page https://calendo.dev/auth/login.html. [L1]
- Action: Confirm the session is truly cleared by navigating directly to https://calendo.dev/dashboard/. Expect: You are redirected back to https://calendo.dev/auth/login.html (no dashboard content). [L2]
- Action: Log back in with the correct credentials. On https://calendo.dev/auth/login.html, type
ravikantguptaofficial+auth-<RUNID>@gmail.cominto Email (#email) andAuthInit-<RUNID>!9into Password (#password), then click "Sign in" (#submitBtn). Expect: No error box (#error); the browser navigates to https://calendo.dev/dashboard/ and the welcome header (#welcomeHeader) showsAuth Test <RUNID>. [L1] Capture screenshot:cu02-06-login-success
- Action: Log out again (
#logoutBtn) to return to a clean login state for the negative test. Expect: Redirected to https://calendo.dev/auth/login.html. [L1]
- Action: Attempt a login with the WRONG password. On https://calendo.dev/auth/login.html, type the correct email
ravikantguptaofficial+auth-<RUNID>@gmail.com(#email) and a deliberately wrong passwordWrongPass-<RUNID>!9(#password), then click "Sign in" (#submitBtn). Expect: A red error box (#error) becomes visible and non-empty (the message is the generic "Invalid email or password"), and the URL stays on/auth/login.html— you are NOT logged in. [L1] Capture screenshot:cu02-07-wrong-password-error
- Action: Start the forgot-password flow. Navigate to https://calendo.dev/auth/reset-password.html (or click "Forgot your password?" on the login page, which links there). Expect: The "Forgot your password?" card shows with an Email field (
#email) and a "Send reset link" button (#forgotBtn). [L1]
- Action: Type the account email
ravikantguptaofficial+auth-<RUNID>@gmail.cominto Email (#email) and click "Send reset link" (#forgotBtn). Expect: A green success message (#success) appears (text contains "reset link" / "Check your email for a reset link.") and the request form is hidden. (Note: per anti-enumeration design, the same success shows even for non-existent emails, so this UI message alone is NOT proof the email went out — the L3 check below is.) [L1] Capture screenshot:cu02-08-reset-requested
- Action: L3 — verify the reset email actually arrived and open its link. See L3 reality checks → Reset email below for the exact Gmail search and link-click sub-steps. After clicking the reset link you should land on https://calendo.dev/auth/reset-password.html?token=... showing the "Set a new password" form. Expect: The reset form renders with "New password" (
#password) and "Confirm new password" (#confirmPassword) fields and a "Reset password" button (#resetBtn). [L3]
- Action: In the "Set a new password" form, type
AuthReset-<RUNID>!9into both "New password" (#password) and "Confirm new password" (#confirmPassword), then click "Reset password" (#resetBtn). Expect: A green success message (#success) appears (text contains "reset successfully"), the form hides, and after ~2.5 seconds the page auto-redirects to https://calendo.dev/auth/login.html. [L1] Capture screenshot:cu02-09-password-reset-success
- Action: Confirm the OLD password no longer works. On https://calendo.dev/auth/login.html, enter the account email (
#email) and the OLD passwordAuthInit-<RUNID>!9(#password), click "Sign in" (#submitBtn). Expect: Red error box (#error) "Invalid email or password"; URL stays on/auth/login.html; not logged in. This proves the password was actually changed. [L2] Capture screenshot:cu02-10-old-password-rejected
- Action: Confirm the NEW password works. On https://calendo.dev/auth/login.html, enter the account email (
#email) and the NEW passwordAuthReset-<RUNID>!9(#password), click "Sign in" (#submitBtn). Expect: No error; navigates to https://calendo.dev/dashboard/; welcome header (#welcomeHeader) showsAuth Test <RUNID>. [L2] Capture screenshot:cu02-11-new-password-login
- Action: (Session-invalidation check — best-effort, observable.) The password-reset endpoint invalidates all prior sessions for the user. We already proved logout cleared the session before reset, so to observe invalidation directly is not strictly available in a single browser. Record an explicit note in the results report that this property is covered by
tests/unit/worker/password-reset.test.js("should invalidate all sessions after password reset") and that the in-browser behavior consistent with it is: the OLD-password login failing (Step 19) and a NEW login being required (Step 20). Expect: Note recorded; no separate UI assertion. [L2]
- Action: Now delete the account. From the dashboard, open Settings — click the sidebar Settings tab (
.sidebar-nav a[data-tab="settings"]). Scroll to the "Danger Zone" card (.settings-danger-card, red, titled "Danger Zone"). Expect: A red "Delete My Account" button (#deleteAccountBtn) is visible. [L1] Capture screenshot:cu02-12-danger-zone
- Action: Click "Delete My Account" (
#deleteAccountBtn). Expect: A first confirmation dialog appears titled "Delete Account" warning that this permanently deletes the account, event types, bookings, availability, calendar connections, and booking pages, with a confirm button labeled "Delete Everything". [L1]
- Action: Confirm the first dialog ("Delete Everything"). Expect: A second confirmation dialog appears titled "Are you absolutely sure?" whose body shows "Type your email to confirm: ravikantguptaofficial+auth-<RUNID>@gmail.com" with a confirm button "Yes, Delete My Account". (Per the current implementation this second dialog is a confirm prompt, not a free-text email-entry field — there is no text input to type into; clicking the confirm button proceeds.) [L1] Capture screenshot:
cu02-13-delete-confirm
- Action: Click "Yes, Delete My Account". Expect: The browser navigates to https://calendo.dev/auth/login.html?deleted=1 (the
?deleted=1query indicates the deletion succeeded). The account session is gone. [L1] Capture screenshot:cu02-14-deleted-redirect
- Action: Confirm the deleted account can no longer log in. On https://calendo.dev/auth/login.html, enter the account email (
#email) and the NEW passwordAuthReset-<RUNID>!9(#password), click "Sign in" (#submitBtn). Expect: Red error box (#error) "Invalid email or password"; URL stays on/auth/login.html. The account is truly gone. [L2] Capture screenshot:cu02-15-deleted-login-fails
- Action: Confirm the deleted account's dashboard is inaccessible by navigating directly to https://calendo.dev/dashboard/. Expect: Redirected to https://calendo.dev/auth/login.html (no dashboard). [L2]
L3 reality checks
Verification email (used in Step 7)
- Open a new tab to https://mail.google.com (already logged in as
ravikantguptaofficial@gmail.com). - In the Gmail search box, search exactly:
subject:(Verify your Calendo email address) to:(+auth-<RUNID>)— or more simplyauth-<RUNID>to find any Calendo mail for this run. - Confirm: An email arrives (allow up to ~3 minutes; refresh) from Calendo with subject "Verify your Calendo email address", addressed to
ravikantguptaofficial+auth-<RUNID>@gmail.com, containing a "Verify Email" button/link pointing tohttps://calendo.dev/api/auth/verify-email?token=.... Capture screenshot:cu02-L3a-verification-email - Click the "Verify Email" link in the email. Confirm: It opens the "Email Verified" success page on calendo.dev (Step 7 expectation) and redirects to
/onboarding/. If two verification emails exist (one from registration, one from the resend in Step 6), use the most recent one; an older link may report "already used"/"Invalid" if the newer was clicked first — that is acceptable as long as one link verifies the account.
Reset email (used in Step 17)
- In Gmail (
ravikantguptaofficial@gmail.com), search exactly:subject:(Reset your Calendo password) to:(+auth-<RUNID>)— orauth-<RUNID>and pick the password reset message. - Confirm: An email arrives (allow up to ~3 minutes) from Calendo with subject "Reset your Calendo password", addressed to
ravikantguptaofficial+auth-<RUNID>@gmail.com, containing a "Reset Password" link pointing tohttps://calendo.dev/auth/reset-password.html?token=.... Capture screenshot:cu02-L3b-reset-email - Click the "Reset Password" link. Confirm: It opens https://calendo.dev/auth/reset-password.html with a
token=query param and the "Set a new password" form (Step 17 expectation).
Cleanup
- The account is deleted in Steps 22–25 as part of the test itself, so cleanup is built in. After the run, verify cleanliness:
- Confirm Step 26 (login fails) and Step 27 (dashboard redirects to login) passed — that is the cleanup verification.
- No event types, availability changes, bookings, polls, routing forms, or calendar events are created by this suite, so nothing else needs removal.
- Gmail hygiene (optional, recommended): in
ravikantguptaofficial@gmail.com, searchauth-<RUNID>and delete the verification and reset emails for this run so the inbox stays uncluttered for future runs. Do NOT delete mail from other RUNIDs. - If the test ABORTS before Step 25 (account not deleted), perform manual cleanup: log in with whichever password is currently active (
AuthReset-<RUNID>!9if reset completed, elseAuthInit-<RUNID>!9), go to Settings → Danger Zone → Delete My Account, and complete both confirmations. If you cannot log in at all, flag for the human to delete the row forravikantguptaofficial+auth-<RUNID>@gmail.comfrom theuserstable.
Pass/Fail criteria
The run PASSES only if ALL of the following are true:
- Registration with email/password succeeds and shows the verification gate, not an error (Step 4).
- An unverified user is gated (redirected to
/auth/verify.html, dashboard not accessible) (Step 5). - The verification email actually arrives in Gmail and its link verifies the account, after which the dashboard loads, shows the user's name, and shows NO verification banner (Steps 7–8). [L3 + L2]
- Logout clears the session; direct dashboard access redirects to login (Steps 10–11).
- Login with correct credentials succeeds (Step 12).
- Login with a wrong password fails with a visible error and stays on the login page (Step 14).
- The forgot-password request shows success AND the reset email actually arrives in Gmail with a working reset link (Steps 16–17). [L3]
- Setting a new password succeeds (Step 18); the OLD password is then rejected (Step 19) and the NEW password works (Step 20). [L2]
- Account deletion completes through both confirmation dialogs and redirects to
/auth/login.html?deleted=1(Steps 23–25). - After deletion, login fails and the dashboard is inaccessible (Steps 26–27). [L2]
The run FAILS if any L3 email does not arrive, any link is broken, a wrong password is ever accepted, the old password still works after reset, the deleted account can still log in, or any step's Expect is not met.
Evidence to capture
- Screenshots listed inline:
cu02-01-register-form,cu02-02-verify-gate,cu02-03-verify-page-gated,cu02-04-email-verified,cu02-05-dashboard-verified-no-banner,cu02-06-login-success,cu02-07-wrong-password-error,cu02-08-reset-requested,cu02-09-password-reset-success,cu02-10-old-password-rejected,cu02-11-new-password-login,cu02-12-danger-zone,cu02-13-delete-confirm,cu02-14-deleted-redirect,cu02-15-deleted-login-fails, plus the L3 shotscu02-L3a-verification-emailandcu02-L3b-reset-email. - A header note recording the RUNID, the account email, the initial and new passwords, and the captured host slug (Step 9).
- The exact Gmail subject lines observed for the verification and reset emails.
- A note for Step 21 documenting that session-invalidation-on-reset is covered by unit tests and observed indirectly via Steps 19–20.
Manual residue / cannot-verify
- Verification banner discrepancy (#emailVerificationBanner): The dashboard contains an amber "Please verify your email address…" banner with a "Resend Verification Email" button (
#resendVerificationBtn), but the dashboardinit()force-redirects any unverified user to/auth/verify.htmlBEFORE rendering, so a normal new user never sees that in-dashboard banner — they see the standalone/auth/verify.htmlpage instead. This suite tests the path that actually happens (verify.html). A human should decide whether the in-dashboard banner is dead code or intended for an edge case (e.g., OAuth-then-downgraded accounts); the agent cannot reach it in-browser via the normal flow. - Second delete confirmation "type your email" UX: The second dialog says "Type your email to confirm: <email>" but the current implementation uses a yes/no confirm dialog with no actual text-entry field — clicking "Yes, Delete My Account" proceeds without typing anything. The agent cannot enforce a real typed-email gate because the UI does not implement one. Hand off to the human to confirm whether a true typed-confirmation is desired.
- Anti-enumeration on forgot-password: The forgot-password endpoint returns identical success for non-existent emails, so the in-UI message is not proof of delivery; only the L3 Gmail check proves it. The agent cannot verify the no-token-created behavior for non-existent emails from the browser (that is unit-tested in
tests/unit/worker/password-reset.test.js). - Token expiry / reuse / used-token rejection (expired verification or reset tokens returning 400, reuse blocked): these depend on time passing or DB token state and are covered by unit tests; the agent does not verify them in-browser in this run.
- Server-side data deletion completeness: After account deletion the agent confirms login fails, but cannot confirm from the browser that ALL related rows (event types, bookings, availability, calendar connections, booking pages) were actually purged from D1. Hand off to the human if a deep deletion audit is required.
- OAuth (Google/Microsoft) registration/login is out of scope for this suite (covered elsewhere); only the presence of the buttons is checked here.
Manual residue
None beyond the "Manual residue / cannot-verify" section above.