fix(web): Ever ID "verify your e-mail" instead of AccessDenied + "Login with Ever ID" button
Ever ID: "verify your e-mail" instead of AccessDenied, and the "Login with Ever ID" button
Description
The bug. On 2026-10-17 at 20:27Z and 20:29Z an Ever ID sign-in on app.ever.team ended on
/auth/error?error=AccessDenied. The web logged ever_id.signin outcome=rejected status=401 twice; the API
(POST /api/auth/zitadel/token) answered 401.
The refusal reason (checked, not guessed). The Ever ID account had been created at 20:26:24Z and its e-mail address
was not verified (Ever ID user record, read-only lookup). Gauzy's ZitadelSigninService.decide() returns
email_unverified first thing when the ID token's email_verified is not true, and the token route answers that with
the same bare 401 it uses for a bad token. The audience was NOT the cause: the web's client id is the only entry of the
API's ZITADEL_ALLOWED_AUDIENCES (prod and stage), and the client has id_token_userinfo_assertion = true, so the ID
token carries email / email_verified.
(Why an unverified person gets a token at all: Ever ID's hosted login does not enforce e-mail verification. That is an
Ever ID setting, outside this repo; every product has to handle the case anyway.)
What Was Changed
Major Changes
- 401 + unverified e-mail →
/auth/error?error=EverIdEmailUnverified: "Verify your Ever ID e-mail", with
"Verify my e-mail on Ever ID" and "Sign in with Ever ID again" (and "Sign in another way"). Triggered when the verified
ID token'semail_verifiedis not true, or when the API answers{ code: 'email_unverified' }(a matching Gauzy
change adds that code; this PR does not need it). GET /api/auth/ever-id/verify-emailsends the browser to Ever ID's verification form
(<issuer>/ui/v2/login/verify?userId=<sub>), where the person enters the code Ever ID e-mailed them or has a new one
sent. The subject comes from the sealed httpOnly cookie the refused sign-in set (new sealed stepverify_email, same
AES-256-GCM cookie as the confirm / sign-up steps), never from a URL of this app. No cookie → back to the sign-in.
404 while Ever ID is off.- Log line:
ever_id.signin outcome=email_unverified, and the remaining rejections carry a fixed
reason=no_id_token|token_refused|id_token_required. Still no token, subject or e-mail in any line (tests pin it). - "Login with Ever ID" button, per the Ever brand kit (
ever-co/platformpackages/brand, PR #37): a row of its
own above the other providers, as wide as the form's primary "Continue" button, 40 px, radius 8. The kit's
rotating star (the ever.works logo star, one turn every 9 s, no spin with prefers-reduced-motion), "Login with",
and the kit's "ever® ID" wordmark. Light:#0A0A0Awith white text (hover#262626); dark:#EDEDEDwith
#0A0A0Atext (hover#FFF); focus ring 2 px (#231645/#CC66FF). Written as Tailwind classes so it follows
this app's theme switch (the kit's global CSS sets:roottokens). Screen readers read "Login with Ever ID".
Login (passcode / password) and register pages, since they share the component.
Minor Changes
- One message per language for the button with the wordmark as a rich-text tag (word order kept, e.g. German
"Mit Ever ID anmelden"); the project's next-intl type override now declarest.rich. - Strings for the new page in all 13 locales.
- The architecture guard
feature-surface-preservedcounts the new route (134 route files, 180 handlers). - Review fixes:
id_token_requiredwins over the e-mail claim (CodeRabbit), light-theme contrast on the verify page
(Copilot), wordmark renderer hoisted (SonarCloud S6478).
How to Test This PR
- Unit:
yarn --cwd apps/web test(new:app/api/auth/ever-id/verify-email/route.test.ts; extended:
o-auth.ever-id.test.ts,handoff.test.ts,log.test.ts,social-logins-buttons.test.tsx). Locally the full web
suite passed at194ee59(134 suites, 1365 tests). - On stage (stage.ever.team, Ever ID on): the login and register pages show the full-width button; with an Ever ID
whose e-mail is not verified the sign-in ends on "Verify your Ever ID e-mail" and the link opens Ever ID's verify form.
Screenshots
Added after the stage rollout (light + dark, narrow + wide).
🤖 Generated with Claude Code
Summary by CodeRabbit
- New Features
- Added a dedicated Ever ID login option with branded styling and accessible labels.
- If your Ever ID email isn’t verified, you’ll see guidance to verify it, sign in again, or return to the app.
- Added a secure redirect to Ever ID’s email-verification page, with localized guidance and actions available in 13 languages.