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
|**2FA finish, wrong code**|`EnableRequest` then `EnableFinish` with `"000000"`|**400**`BAD_REQUEST` (“Authenticator token is incorrect.”). |
62
+
|**2FA login without MFA**| After finish, `performSessionLogin` without `authenticatorToken`|**401** “Requires two-factor authentication.” (untrusted device). |
63
+
|**2FA login, bad TOTP**|`authenticatorToken: "111111"`|**401** “Invalid authenticator token.” |
60
64
61
65
**`@deepnotes/db` real Postgres (`template-db.test.ts`):**
62
66
63
67
| Test case | Exercises | Assertions |
64
68
|-----------|-----------|------------|
65
69
| Clone + insert user | Template clone, `users` insert | Isolated DB starts with **0** users; insert + select by `id`. |
66
70
|**Sessions FK**|`INSERT sessions` without parent `users` / `devices`| Insert **rejects** (FK violation) for orphan `user_id` / `device_id`. |
71
+
|**Devices FK**|`INSERT devices` with non-existent `user_id`| Insert **rejects** (FK to `users`). |
72
+
|**Pages → groups FK**|`INSERT pages` with unknown `group_id`| Insert **rejects** (FK to `groups`). |
67
73
68
-
**Not yet in integration:** Redis failed-login with real `ioredis`/Upstash against `performSessionLogin`; **2FA** enable → `performSessionLogin` with TOTP (HTTP + `user-two-factor-settings` exist; DB path not covered in template suite yet); `performSessionRefresh` edge cases (expired JWT, `loggedIn` false, replayed refresh token invalidation).
74
+
**Not yet in integration:** Redis failed-login with real `ioredis`/Upstash against `performSessionLogin` (unit tests cover rate-limit helpers); `performSessionRefresh` edge cases (expired JWT, `loggedIn` false, replayed refresh token invalidation); **2FA recovery-code login** path against real Postgres (logic exists in `two-factor.ts` + `login.ts`).
69
75
70
76
### Sessions + account (current)
71
77
@@ -88,7 +94,7 @@ Integration tests use `describe.skipIf` when `DATABASE_URL` (and admin URL for `
88
94
-[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.
89
95
-[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).
90
96
-[x]**`decryptUserEmail`** in `@deepnotes/session` for confirm; **`sendEmailChangeVerificationEmail`** (Resend); OpenAPI + [docs/TRPC_REST_MAP.md](./docs/TRPC_REST_MAP.md) updated.
91
-
-[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). **Postgres integration:**not yet — follow-up: enable/finish 2FA then `performSessionLogin` with TOTP (`assertTwoFactorOk` in [two-factor.ts](./packages/session/src/two-factor.ts)).
97
+
-[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). **Postgres integration:**[account-flows.integration.test.ts](./packages/session/src/account-flows.integration.test.ts) — enable/finish + `performSessionLogin` with TOTP; wrong finish token; missing MFA; invalid TOTP (`assertTwoFactorOk` in [two-factor.ts](./packages/session/src/two-factor.ts)).
92
98
93
99
### 2FA HTTP routes (Phase 3)
94
100
@@ -155,8 +161,8 @@ Cross-cutting work so the new SPA does not repeat **legacy `apps/client`** patte
155
161
156
162
| Package / app | Role | What runs today | Gaps (highest value next) |
|**`@deepnotes/db`**| Drizzle + migrations |`template-db.test.ts`: clone template, empty `users`, **FK rejection** on orphan `sessions` row | More composite FK paths (e.g. `group_members`) when groups work lands|
|**`@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**|
161
167
|**`@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) |
162
168
|**`@deepnotes/web`**| SPA |`app.test.ts` (mount `App.vue`) | Auth UI + API client as in §5.8 |
@@ -180,7 +186,7 @@ Cross-cutting work so the new SPA does not repeat **legacy `apps/client`** patte
180
186
-[ ] Cold API dev start under **2 s** (no `inspect-brk` by default) — validate on a typical laptop.
181
187
-[ ] Collab + realtime: at least one integration test each (Redis + deps).
-[x] No tRPC / superjson / RevenueCat / key-rotation in **this** tree (keep absent); product sign-off for IAP/Stripe when billing ships.
185
191
-[x] Client: zero undocumented forks, or a short owned exception list — see [docs/CLIENT_FORKS.md](./docs/CLIENT_FORKS.md).
186
192
-[ ] Cloudflare: deploy runbook; Hyperdrive + Postgres + Redis proven in staging; collab/realtime topology chosen and load-tested.
@@ -191,15 +197,15 @@ Cross-cutting work so the new SPA does not repeat **legacy `apps/client`** patte
191
197
192
198
## Phase 3 working order (suggested)
193
199
194
-
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.
200
+
Use this when resuming: **(done)** account HTTP through 2FA (incl. `load` as POST, see map); **Postgres** coverage for 2FA service layer + extra **`@deepnotes/db` FK** tests. **(next)**`users.pages` + `groups` + `pages` REST from [TRPC_REST_MAP](./docs/TRPC_REST_MAP.md) — each slice should add **template DB** tests for new FKs and happy paths where SQL risk is high. **(then)****realtime + collab** (no key rotation) and **Stripe** + wire billing hooks on account routes.
| 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. |
204
210
| 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