You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-[x]**Worker smoke:**`503` when env/DB not configured for `/api/users/me/email-change` (+ confirm), alongside other session routes.
41
41
-[x]**DB integration (template Postgres):**`account-flows.integration.test.ts` (renamed from `email-change.integration.test.ts`). See [Phase 3 test coverage (detail)](#phase-3-test-coverage-detail) for the per-case list.
42
42
@@ -76,7 +76,20 @@ Integration tests use `describe.skipIf` when `DATABASE_URL` (and admin URL for `
76
76
-[x]`POST /api/users/me/email-change` — `performUserEmailChangeRequest`: `oldLoginHash` + `newEmail`; **403** demo, **400** bad password or “email already in use” (global `email_hash` match, same as legacy); sets `encrypted_new_email` + 6-digit `email_verification_code`; Resend (subject/body like legacy) or **200**`{ "emailVerificationCode" }` when `SEND_EMAILS=false`; **204** when emailed.
77
77
-[x]`POST /api/users/me/email-change/confirm` — `performUserEmailChangeConfirm`: one call (WS two-step collapsed); `oldLoginHash`, `emailVerificationCode` (6 digits), `newLoginHash`, `userEncrypted*Keyring` (b64, same as register/password); verifies code + password; applies new `encrypted_email` / `email_hash`, clears pending fields, PHC + rewrapped keyrings, invalidates **all**`sessions`, **204** + `buildClearSessionCookies`; optional `updateStripeCustomerEmail` in worker (matches legacy `customers.update` after commit, errors non-fatal).
78
78
-[x]**`decryptUserEmail`** in `@deepnotes/session` for confirm; **`sendEmailChangeVerificationEmail`** (Resend); OpenAPI + [docs/TRPC_REST_MAP.md](./docs/TRPC_REST_MAP.md) updated.
79
-
-[ ]**2FA (HTTP surface)** — `POST /api/users/me/2fa/enable/request|finish`, `GET /api/users/me/2fa`, `POST /api/users/me/2fa/recovery-codes`, `POST /api/users/me/2fa/devices/forget`, `POST /api/users/me/2fa/disable` ([docs/TRPC_REST_MAP.md](./docs/TRPC_REST_MAP.md)). **Note:**`@deepnotes/session` already implements TOTP/recovery verification for **`POST /api/sessions/login`**; these routes expose enable/disable/load for the SPA.
79
+
-[x]**2FA (HTTP surface)** — Hono + OpenAPI: `user-two-factor-settings.ts` (`encryptUserAuthenticatorSecret` in `session-crypto`). Routes: [2FA HTTP routes](#2fa-http-routes-phase-3). `load` is **`POST /api/users/me/2fa/load`** (password in JSON, not a `GET` — [TRPC_REST_MAP](./docs/TRPC_REST_MAP.md) footnote). **Not yet in integration template DB:** 2FA enable → login with TOTP (optional follow-up; login path already uses `assertTwoFactorOk` in [two-factor.ts](./packages/session/src/two-factor.ts)).
|**`@deepnotes/api`**| Zod + OpenAPI |`openapi.test.ts` (health + route registry); **`schemas/users.test.ts`** (email/password change bodies, 6-digit code) | Schemas for sessions + remaining routes; optional **snapshot** of OpenAPI fragment for drift|
136
-
|**`@deepnotes/api-worker`**| Hono on Worker |`index.test.ts`: health, OpenAPI JSON, **503**when secrets/DB not bound (incl. email-change paths) |**200-path tests** with test`SessionEnv` + Hyperdrive stub + template DB (heavier CI job) |
148
+
|**`@deepnotes/api`**| Zod + OpenAPI |`openapi.test.ts` (health + session + 2FA paths); **`schemas/users.test.ts`** (email/password change, 2fa finish) | Schemas for pages/groups when they land; optional OpenAPI **snapshot**|
149
+
|**`@deepnotes/api-worker`**| Hono on Worker |`index.test.ts`: 503 when env missing — includes **2FA**routes in matrix |**200**tests with stub`SessionEnv` + template DB (heavier) |
137
150
|**`@deepnotes/web`**| SPA |`app.test.ts` (mount `App.vue`) | Auth UI + API client as in §5.8 |
138
151
139
152
**Principle:** keep **fast unit tests** on pure crypto, Zod, and mail/HTTP branches; add **Postgres-backed** flows incrementally (same template pattern as `@deepnotes/db`) so Phase 3 routes do not regress silently.
@@ -166,14 +179,15 @@ Cross-cutting work so the new SPA does not repeat **legacy `apps/client`** patte
166
179
167
180
## Phase 3 working order (suggested)
168
181
169
-
Use this when resuming: **(done)** account HTTP surface through email change including password change. **(next)** 2FA CRUD on `/api/users/me/2fa*`, reusing session crypto already used at login. **(then)** pages + groupsfrom [TRPC_REST_MAP](./docs/TRPC_REST_MAP.md) (user prefs, CRUD, group privacy/password). **(then)**long pole: **realtime + collab** (protocol, Worker/DO, no key rotation) and **Stripe**webhook + billing routes + wire `updateStripeCustomerEmail` / `deleteStripeCustomer` from account flows where applicable.
182
+
Use this when resuming: **(done)** account HTTP through 2FA (incl. `load` as POST, see map). **(next)**`users.pages` + `groups` + `pages` REST from [TRPC_REST_MAP](./docs/TRPC_REST_MAP.md). **(then)****realtime + collab** (no key rotation) and **Stripe**+ wire billing hooks on account routes.
170
183
171
184
---
172
185
173
186
## Short log (newest first)
174
187
175
188
| Date | Change |
176
189
|------|--------|
190
+
| 2026-04-27 |**2FA account HTTP:**`user-two-factor-settings.ts`, `encryptUserAuthenticatorSecret` in `session-crypto`, Zod + OpenAPI + Hono for `/api/users/me/2fa/*` (6 routes); [TRPC_REST_MAP](./docs/TRPC_REST_MAP.md) — `load` is POST not GET; see [2FA HTTP routes](#2fa-http-routes-phase-3) below. |
177
191
| 2026-04-27 |**Integration tests:** expanded `account-flows.integration.test.ts` (email wrong code; password change PHC + keyring unwrap with salt from PHC; `sessions` invalidation; wrong old password). Renamed from `email-change.integration.test.ts`. PLAN_PROGRESS: detailed Phase 3 test table + matrix gaps. |
0 commit comments