Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .beads/issues.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@
{"id":"ePDS-ipm.7","title":"Update recovery.test.ts to use dynamic OTP length instead of hardcoded 8","description":"## Files\n- packages/auth-service/src/__tests__/recovery.test.ts (modify)\n\n## What to do\n\nThere are two test blocks that hardcode OTP length to 8:\n\n### Block 1 (lines 122-128)\n```ts\ndescribe('Recovery flow: OTP pattern (8 digits)', () =\u003e {\n it('OTP sent by better-auth is 8 digits (configured in emailOTP plugin)', () =\u003e {\n const OTP_LENGTH = 8\n const pattern = new RegExp(`^[0-9]{${OTP_LENGTH}}$`)\n // ...\n })\n})\n```\n\n### Block 2 (lines 136-142)\n```ts\nit('OTP entry form uses maxlength=8 and pattern [0-9]{8}', () =\u003e {\n const maxlength = 8\n const pattern = '[0-9]{8}'\n expect(maxlength).toBe(8)\n expect(pattern).toContain('8')\n})\n```\n\n### Changes needed\n\nFor both blocks, read the OTP length from `process.env.OTP_LENGTH` with default 8:\n```ts\nconst OTP_LENGTH = parseInt(process.env.OTP_LENGTH ?? '8', 10)\n```\n\nUpdate the describe/it descriptions to not hardcode '8':\n- `'Recovery flow: OTP pattern'` (drop '8 digits')\n- `'OTP sent by better-auth matches configured length'`\n- `'OTP entry form uses configured maxlength and pattern'`\n\nUpdate Block 2 assertions to use the dynamic value:\n```ts\nconst maxlength = OTP_LENGTH\nconst pattern = `[0-9]{${OTP_LENGTH}}`\nexpect(maxlength).toBe(OTP_LENGTH)\nexpect(pattern).toContain(String(OTP_LENGTH))\n```\n\n## Don't\n- Don't change any other test files\n- Don't import from better-auth internals — just read process.env","acceptance_criteria":"1. No hardcoded 8 remains in OTP-related test assertions or descriptions\n2. Both test blocks read OTP_LENGTH from process.env with default 8\n3. Test descriptions are updated to not mention '8 digits'\n4. pnpm test passes with default OTP_LENGTH (unset = 8)","status":"closed","priority":2,"issue_type":"task","assignee":"karma.gainforest.id","owner":"karma.gainforest.id","estimated_minutes":20,"created_at":"2026-03-11T22:24:40.027924118+06:00","created_by":"karma.gainforest.id","updated_at":"2026-03-11T22:26:23.690810039+06:00","closed_at":"2026-03-11T22:26:23.690810039+06:00","close_reason":"627028c Use dynamic OTP_LENGTH in recovery tests instead of hardcoded 8","labels":["scope:trivial"],"dependencies":[{"issue_id":"ePDS-ipm.7","depends_on_id":"ePDS-ipm","type":"parent-child","created_at":"2026-03-11T22:24:40.029555266+06:00","created_by":"karma.gainforest.id"}]}
{"id":"ePDS-ipm.8","title":"Document OTP_LENGTH env var in .env.example","description":"## Files\n- packages/auth-service/.env.example (modify)\n\n## What to do\n\nAdd the OTP_LENGTH env var documentation. Place it in the '-- Auth-service only --' section, after the session settings block (after line 61, before the social login section):\n\n```\n# OTP code length — number of digits in the email verification code (default: 8)\n# Must be between 4 and 12. Applies to login, recovery, and account settings OTP flows.\n# OTP_LENGTH=8\n```\n\nThe variable is commented out (like other optional vars in this file) since the default of 8 is sensible.\n\n## Don't\n- Don't modify any other .env.example files (the root one, pds-core, or demo)\n- Don't change any existing lines","acceptance_criteria":"1. OTP_LENGTH is documented in packages/auth-service/.env.example\n2. It's in the auth-service-only section, near the session settings\n3. Comment explains the valid range (4-12) and default (8)\n4. The variable line is commented out (# OTP_LENGTH=8)\n5. No existing lines are modified","status":"closed","priority":3,"issue_type":"task","assignee":"karma.gainforest.id","owner":"karma.gainforest.id","estimated_minutes":10,"created_at":"2026-03-11T22:24:48.428248126+06:00","created_by":"karma.gainforest.id","updated_at":"2026-03-11T22:26:17.022688894+06:00","closed_at":"2026-03-11T22:26:17.022688894+06:00","close_reason":"b8d6f58 docs: document OTP_LENGTH env var in auth-service .env.example","labels":["scope:trivial"],"dependencies":[{"issue_id":"ePDS-ipm.8","depends_on_id":"ePDS-ipm","type":"parent-child","created_at":"2026-03-11T22:24:48.42974985+06:00","created_by":"karma.gainforest.id"}]}
{"id":"ePDS-ix2","title":"Fix: recovery .link-btn has padding:0 — touch target too small on mobile (from ePDS-aq2.5)","description":"Review of ePDS-aq2.5 found: The .link-btn CSS rule in recovery.ts (line 636) sets padding: 0, which means the touch target for 'Back to sign in' and 'Resend Code' links is only the text height (~20px). WCAG 2.5.5 recommends a minimum 44×44px touch target.\n\nEvidence:\n- recovery.ts line 636: padding: 0; (inside .link-btn rule)\n- The same issue exists in login-page.ts (line 565: padding: 0)\n\nThe .link-btn elements appear in:\n1. .form-actions row: 'Back to sign in' link (line 246)\n2. .otp-links row: 'Resend Code' button and 'Back to sign in' link (lines 319, 321)\n\nThese are the only interactive elements in those rows, so small touch targets are a real usability issue on mobile.\n\nFix: Add padding: 8px 0 (or similar) to .link-btn to increase the touch target height to at least 36px, while keeping the visual appearance the same. This fix should be applied to both recovery.ts and login-page.ts for consistency. Note: this is a pre-existing issue in login-page.ts that was copied into recovery.ts.","status":"open","priority":3,"issue_type":"bug","owner":"karma.gainforest.id","created_at":"2026-03-04T15:59:41.944209123+06:00","created_by":"karma.gainforest.id","updated_at":"2026-03-04T15:59:41.944209123+06:00","dependencies":[{"issue_id":"ePDS-ix2","depends_on_id":"ePDS-aq2.5","type":"discovered-from","created_at":"2026-03-04T15:59:42.002820695+06:00","created_by":"karma.gainforest.id"}]}
{"id":"ePDS-j448","title":"Epic: E2E coverage for consent and chooser enrichment","description":"Add end-to-end coverage for the user-facing enrichment introduced on this branch. Success means real OAuth consent screens assert identity enrichment in random and picker modes, and the session-reuse chooser profile asserts non-random chooser rows keep handle and email visible. Constraints: do not add preview-route e2e coverage; do not add multi-account/exact-matching e2e coverage in this scope; avoid coupling random consent coverage to random account provisioning risk. Known risks: consent DOM is upstream-owned and can be fragile; keep assertions anchored on visible role/text and ePDS stable classes.","status":"open","priority":2,"issue_type":"epic","owner":"kzoepa@gmail.com","created_at":"2026-05-04T17:17:28.800992481+06:00","created_by":"kzoeps","updated_at":"2026-05-04T17:17:28.800992481+06:00","labels":["scope:medium"]}
{"id":"ePDS-j825","title":"Fix: renderNoAccountPage() response missing HTTP status code (from ePDS-01p.3)","description":"Review of ePDS-01p.3 found: In account-settings.ts line 69, when no PDS account is found for the session email, the response is sent as 'res.type(\"html\").send(renderNoAccountPage())' without setting an HTTP status code. This defaults to 200 OK, which is semantically incorrect for an error page and may confuse clients, monitoring tools, or browser caching. The 503 branch (PDS unavailable) correctly uses res.status(503), but the no-account branch silently returns 200. Fix: add an appropriate status code — either 403 Forbidden (the session is valid but the user has no account) or 404 Not Found. Suggested: res.status(403).type(\"html\").send(renderNoAccountPage()). Evidence: account-settings.ts line 69 — no .status() call before .type('html').send(...).","status":"open","priority":2,"issue_type":"bug","owner":"kzoepa@gmail.com","created_at":"2026-03-19T16:12:47.885869077+06:00","created_by":"kzoeps","updated_at":"2026-03-19T16:12:47.885869077+06:00","dependencies":[{"issue_id":"ePDS-j825","depends_on_id":"ePDS-01p.3","type":"discovered-from","created_at":"2026-03-19T16:13:23.934385443+06:00","created_by":"kzoeps"}]}
{"id":"ePDS-jbs","title":"Fix: dark mode body background overridden by light-mode rule ordering (from ePDS-wae.2)","description":"The CSS has two body background rules: (1) body { background: var(--color-contrast-0); } at line ~364 and (2) @media (prefers-color-scheme: light) { body { background: white; } } at line ~371. In dark mode, rule (1) correctly uses --color-contrast-0 which resolves to hsl(265 20% 6%) — a near-black. However, when AUTH_BACKGROUND_COLOR is set, the bgColorStyle injects a \u003cstyle\u003ebody { background: ... !important; }\u003c/style\u003e block. The !important correctly overrides both rules. But if a client's background_color is set via OAuth metadata (b.background_color), it is NOT injected into the bgColorStyle block — it is only set via --color-panel (which only affects the panel, not body). The body background_color from client OAuth metadata is silently ignored. Document this limitation or implement it.","status":"open","priority":2,"issue_type":"bug","owner":"karma.gainforest.id","created_at":"2026-03-03T20:16:17.449655765+06:00","created_by":"karma.gainforest.id","updated_at":"2026-03-03T20:16:17.449655765+06:00","dependencies":[{"issue_id":"ePDS-jbs","depends_on_id":"ePDS-wae.2","type":"discovered-from","created_at":"2026-03-03T20:17:56.359797404+06:00","created_by":"karma.gainforest.id"}]}
{"id":"ePDS-jk0","title":"Bug: recovery GET requires request_uri query param but plan says cookie should be sufficient","description":"## Finding\n\n`packages/auth-service/src/routes/recovery.ts` lines 42-44 return HTTP 400 if `request_uri` is absent from the query string:\n\n```ts\nif (!requestUri) {\n res.status(400).send(renderError('Missing request_uri parameter'))\n return\n}\n```\n\nThe login page recovery link at `login-page.ts:368` currently passes `request_uri` and `client_id` as query params to work around this:\n\n```ts\n\u003ca href=\"/auth/recover?request_uri=${...}\\\u0026client_id=${...}\" ...\u003e\n```\n\nThis is the current working state, but it means:\n\n1. The recovery page is reachable only via the login page link (which includes the params). Direct navigation or a stale link without params returns 400.\n2. The plan to simplify the recovery link to just `/auth/recover` (change #5) would break the GET handler unless change #4 (read from cookie instead) is implemented first. These two changes are tightly coupled and must be implemented atomically.\n3. The fallback at lines 47-51 (`getAuthFlowByRequestUri`) only runs when `clientId` is missing but `requestUri` is present — it never fires when `requestUri` itself is absent.\n\n## Not a regression — this is the current design\n\nThis is not a regression from any recent change. It is a pre-existing coupling that the refactoring plan must address carefully. The plan correctly identifies this (change #4), but the implementation must ensure the GET handler is updated before or simultaneously with simplifying the login page link (change #5). If done in two separate commits, the intermediate state would break recovery.","status":"open","priority":2,"issue_type":"bug","owner":"karma.gainforest.id","created_at":"2026-03-18T17:11:00.440621594+06:00","created_by":"karma.gainforest.id","updated_at":"2026-03-18T17:11:00.440621594+06:00"}
Expand Down
9 changes: 0 additions & 9 deletions .changeset/clean-exit-on-expired-signin.md
Comment thread
Kzoeps marked this conversation as resolved.
Outdated

This file was deleted.

9 changes: 0 additions & 9 deletions .changeset/par-heartbeat-keeps-slow-signins-alive.md

This file was deleted.

15 changes: 15 additions & 0 deletions .changeset/sign-in-account-presentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'ePDS': patch
---

Sign-in screens now show your email more consistently and wait until they are ready before accepting clicks.

**Affects:** End users, Client app developers

**End users:**

- Sign-in, app approval, account chooser, and account-management screens now use email as the primary identifier for accounts with generated handles, while still showing the public handle where it helps explain the account.
- The final approval step no longer briefly exposes a generated random handle when an app asked to show email first.
- The email sign-in button now waits until the page is ready before accepting clicks, so an early tap does not start a broken sign-in attempt.

**Client app developers:** `epds_handle_mode` is now applied consistently to approval and account-chooser pages from either the current `/oauth/authorize` query parameter or OAuth client metadata, including pushed authorization requests where the browser URL only contains `request_uri`. Explicit query parameters still take precedence over client metadata. Valid modes stored for an auth flow are preserved through `/oauth/epds-callback`; invalid callback values are ignored rather than forwarded; metadata lookup failures fall back to the existing default behavior.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Comment thread
Kzoeps marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# ePDS

## 0.6.2

### Who should read this release

- **End users:**
- [Sign-in pages no longer strand users on a "session expired" dead end, and Resend no longer offers codes that won't work.](#v0.6.2-sign-in-pages-no-longer-strand-users-on-a-session-expired)
- [Slow sign-ins are less likely to time out before you finish entering your code.](#v0.6.2-slow-sign-ins-are-less-likely-to-time-out-before-you-finish)

### Patch Changes

- <a id="v0.6.2-sign-in-pages-no-longer-strand-users-on-a-session-expired"></a> [#154](https://github.com/hypercerts-org/ePDS/pull/154) [`2e4d327`](https://github.com/hypercerts-org/ePDS/commit/2e4d327f01e6983ac8946cb7dff1e998edae8e34) Thanks [@aspiers](https://github.com/aspiers)! - Sign-in pages no longer strand users on a "session expired" dead end, and Resend no longer offers codes that won't work.

**Affects:** End users

**End users:** if your sign-in times out (you closed the tab and came back, or your wait was longer than the page can keep alive in the background), you are now taken back to the app you were signing in to so it can offer you a retry. The page also no longer offers Resend in the rare case where the new code wouldn't work — instead it tells you the sign-in has timed out and gives you a Start over button. No more typing a fresh code that fails. If for some reason the automatic return is not possible, the page shows a "Return to sign in" button so you can get back to the app yourself in one click.

- <a id="v0.6.2-slow-sign-ins-are-less-likely-to-time-out-before-you-finish"></a> [#154](https://github.com/hypercerts-org/ePDS/pull/154) [`b1fc940`](https://github.com/hypercerts-org/ePDS/commit/b1fc9400e55b661cfbd992d6f72261936ec1df5c) Thanks [@aspiers](https://github.com/aspiers)! - Slow sign-ins are less likely to time out before you finish entering your code.

**Affects:** End users

**End users:** if you take a few minutes to find your sign-in code in your inbox before entering it, you will no longer be bounced to a "session expired" page when you submit it. Closing the tab or walking away for a long stretch can still expire the flow, in which case the existing error pages still apply — but reading email at human speed should not.

## 0.6.1

### Who should read this release
Expand Down
Loading
Loading