Skip to content

feat(server,ui): able sign-in follow-ups — whitelabel copy/i18n + assertion renewal (ABLE-2) - #362

Merged
eliotlim merged 8 commits into
mainfrom
feat/able2-refresh-whitelabel
Sep 8, 2026
Merged

feat(server,ui): able sign-in follow-ups — whitelabel copy/i18n + assertion renewal (ABLE-2)#362
eliotlim merged 8 commits into
mainfrom
feat/able2-refresh-whitelabel

Conversation

@eliotlim

@eliotlim eliotlim commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two gaps left by #361 (ABLE-1): the sign-in surface still shows account.book.pub-branded copy even when whitelabel able delegation is active (violating the silent-delegation directive), and bridged assertions expire after 1 hour with no renewal — users get signed out hourly despite the server holding (unused) refresh tokens. Board: ABLE-2.

Solution

  • Whitelabel copy + i18n (packages/ui): when the able probe succeeds, all sign-in copy switches to neutral OpenBook strings (account.signin.able.*, "OpenBook identity") via the typed i18n mechanism, translated in all four locales (en/de/ja/zh) with a leak-guard test asserting no account.book.pub/account.able.online appears in able copy. ManualCodeEntry (deep-link paste, inapplicable to the able flow) is hidden in able mode. Inactive-mode copy is untouched and test-pinned.
  • Token refresh (packages/server): POST /api/auth/oauth2/refresh/able — caller proves possession of the latest bridge assertion (EdDSA-verified against the bridge issuer's own key, ≤5-min post-expiry grace, jti-bound to the stored refresh token so a stolen older assertion cannot chain); the stored refresh token is atomically consumed (single-statement DELETE…RETURNING) before the IdP exchange, rotated on response, and a fresh 1-hour assertion is minted. DELETE on the same route clears the stored token on account removal (same assertion binding, header-auth, not CSRF-able). 30/min IP rate limiter checked before any crypto. Migration 0030 adds the assertion_jti column.
  • UI renewal (AccountProvider): silent auto-renew ~60s before expiry with rotation of the local assertion; just-expired recovery on activation; rejected (401/403) renewal surfaces the existing rejectedReauth error instead of dead-ending; failure falls back to the existing identityExpired re-sign-in path.
  • New deps: none.

Before / After

Behavior Before After
Sign-in copy, able mode active
"Continue with account.book.pub" + manual code entry

neutral OpenBook copy, manual entry hidden
Sign-in copy, able inactive existing copy unchanged (test-pinned)
Assertion at ~1h signed out, manual re-sign-in silently renewed ~60s before expiry (AccountProvider.test.tsx renewal suite)
Stolen older assertion → refresh n/a 401, zero IdP calls (ableOidc.test.ts jti-mismatch case)
Refresh replay after consume n/a 401 fail-closed, token not restored
Account removal local only; refresh token orphaned server-side authenticated DELETE clears stored refresh token; later refresh 401s

Test procedure

pnpm verify                                        # full gate — green (exit 0)
pnpm --filter @book.dev/server test ableOidc       # 22 refresh/security tests
pnpm --filter @book.dev/server test migrations     # 16 incl. 0030
pnpm --filter @book.dev/ui test AccountProvider    # renewal/rotation/fallback
pnpm --filter @book.dev/ui test i18n               # 4-locale keys + branding leak guard

All IdP interaction stubbed; no network in tests.

Operational notes

  • No new env vars; feature remains gated by the ABLE-1 ABLE_OAUTH_* pair. Migration 0030 is additive.
  • Deliberate security posture (per review): refresh binds to the newest assertion (jti); consumed-but-failed exchanges fail closed into re-sign-in; able-bridge subjects sit outside the OB-106 revocation set (revocation = remove the account or unset the env); behind a reverse proxy the rate limiter collapses to a shared bucket — fails closed (renewal DoS ⇒ re-sign-in prompt, never open access). Comments in code document all three.
  • Deferred (board ABLE-4): desktop deep-link flow, blocked on an able-team redirect contract.

Verify (exit 0): sdk 546/546 · ui 2319/2319 · app 7/7 · server 1326 passed + 6 skipped · mcp + e2e green
Reviews cleared: code (Quinn), security (Sasha — jti hardening applied), design (Devon — copy/i18n/UX)

@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
app.book.pub Ready Ready Preview Sep 8, 2026 5:52am UTC

Request Review

@eliotlim
eliotlim merged commit 55d258d into main Sep 8, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant