feat(auth): Ever ID token route answers email_unverified; "Login with Ever ID" button

Merged
EV
10d ago
+387-20

PR


⚠️ develop → stage only. This changes the shared gauzy-api image (also the Ever Teams API). Production
(main/master, app.gauzy.co / api.gauzy.co / api.ever.team) is the owner's call; Ever ID stays off in Gauzy prod.

Why

On 2026-10-17 at 20:27Z / 20:29Z a person who had just created their Ever ID tried "Ever ID" on app.ever.team and got
AccessDenied. The API (POST /api/auth/zitadel/token) answered a bare 401. Checked: the token's audience was right
(the Teams web client is the only ZITADEL_ALLOWED_AUDIENCES entry); the Ever ID account was minutes old with an
unverified e-mail
, and ZitadelSigninService.decide() returns email_unverified first. The token route answered
that with the same bare 401 it uses for a forged token, and logged nothing above debug, so neither the client nor the
operator could tell why.

What

Token route (packages/plugins/auth-zitadel)

  • A valid ID token whose e-mail is not verified → 401 { code: 'email_unverified' }. Every refusal of the token itself
    (audience, issuer, signature, expiry) stays a bare 401, so the route is no oracle for token validity; the
    email_verified claim is in the token the caller already holds.
  • The OIDC error code of a refused token is logged at warn (was debug). Codes only, never the token.
  • Ever Teams already handles both answers (ever-co/ever-teams#4645 shows "Verify your Ever ID e-mail"); it does not
    depend on this change.

"Login with Ever ID" button (packages/ui-auth, ngx-social-links: login, magic-login, register)

  • Per the Ever brand kit (ever-co/platform packages/brand, PR #37): the kit's rotating star (one turn every 9 s,
    no spin with prefers-reduced-motion), "Login with", and the kit's "ever® ID" wordmark. The form's full width,
    40 px (the login/register primary button is ~40 px too), radius 8, padding 0 16px, 14/20 px at 500. Light themes:
    #0A0A0A / white text (hover #262626); dark themes (gauzy-dark, dark, cosmic, material-dark): #EDEDED / #0A0A0A
    (hover #FFF); focus ring 2 px (#231645 / #CC66FF). Same button as Ever Teams (ever-co/ever-teams#4645).
  • New LOGIN_PAGE.LOGIN_WITH_EVER_ID (Login with {{ everId }}) in 13 languages; the label component splits it at the
    wordmark so word order is kept (German "Mit Ever ID anmelden"). SIGN_IN_WITH_EVER_ID stays.

Tests

  • zitadel-token.e2e.spec.ts: the unverified case now expects code: 'email_unverified'; the other refusals expect no
    code; a token for another audience with email_verified: false still gets a bare 401.
  • social-links.component.spec.ts: word-order split for the new label, fallback when the translation is missing.
  • ⚠️ Not run locally (no Gauzy install on this workstation) and Gauzy CI does not run jest: please run
    yarn nx test plugin-auth-zitadel / yarn nx test ui-auth or rely on the reviewer. The code change is small
    (exception body, log level, a template branch).

Verify on stage

app-stage / apistage (Ever ID on there): login and register pages show the full-width "Login with Ever ID" button in
light and dark; an Ever ID with an unverified e-mail gets 401 {"code":"email_unverified"} from
POST /api/auth/zitadel/token.

🤖 Generated with Claude Code