fix(web): resolve display name from userInfo, not just claims (#70) #89

Merged
maximus merged 1 commit from issue-70-harmonize-display-name into master 2026-05-30 18:52:23 +00:00
Owner

Fixes #70

Problème

Après connexion SSO sur liste.lacompagniemaximus.com, le user voyait son email au lieu de son nom ("Max"). La vitrine (lacompagniemaximus.com) affiche correctement le nom.

Cause

getAuthenticatedUser() (web/src/lib/auth.ts) ne lisait que context.claims. Le claim name est souvent absent de l'ID token (présent côté userInfo), donc le fallback retombait sur l'email.

Fix (web/src/lib/auth.ts, seul fichier modifié)

  • getLogtoContext(logtoConfig, { fetchUserInfo: true }) — récupère le userInfo endpoint.
  • Résolution du nom avec le même ordre que la vitrine (la-compagnie-maximus#80) : userInfo.name || userInfo.username || claims.name || claims.username, le || email final appliqué dans le layout (user.name || user.email || "").
  • email préfère désormais userInfo.email puis claims.email.

Décisions

  • Chaîne consolidée dans auth.ts (single source of truth — seul producteur de name, consommé uniquement par layout.tsx → Header). La vitrine calcule displayName au point d'usage car elle renvoie les claims bruts au client via une route API ; ici l'archi server-component fait d'auth.ts le point de résolution naturel.
  • layout.tsx : vérifié, inchangé — user.name || user.email || "" est déjà le tail || email de la chaîne, il reçoit maintenant le nom résolu.
  • Header.tsx : vérifié, prop userName: string correct, inchangé.

Vérification

  • tsc --noEmit : OK (typecheck de la chaîne + option fetchUserInfo)
  • eslint src/lib/auth.ts : clean (0 problème sur le fichier modifié)
  • smoke test racine (npm test) : OK (non-régression mobile)
  • Note : le lint de web/ a 3 erreurs pré-existantes dans d'autres fichiers (lists/[id]/page.tsx, api/lists/[id]/tasks/route.ts, ThemeToggle.tsx), hors scope de cette PR.
  • AC comportemental (user voit son nom) à confirmer au déploiement — pas de e2e dans web/.

Diff : 1 fichier, +14/-4.

Fixes #70 ## Problème Après connexion SSO sur `liste.lacompagniemaximus.com`, le user voyait son email au lieu de son nom ("Max"). La vitrine (`lacompagniemaximus.com`) affiche correctement le nom. ## Cause `getAuthenticatedUser()` (`web/src/lib/auth.ts`) ne lisait que `context.claims`. Le claim `name` est souvent absent de l'ID token (présent côté `userInfo`), donc le fallback retombait sur l'email. ## Fix (`web/src/lib/auth.ts`, seul fichier modifié) - `getLogtoContext(logtoConfig, { fetchUserInfo: true })` — récupère le userInfo endpoint. - Résolution du nom avec le même ordre que la vitrine (`la-compagnie-maximus#80`) : `userInfo.name || userInfo.username || claims.name || claims.username`, le `|| email` final appliqué dans le layout (`user.name || user.email || ""`). - `email` préfère désormais `userInfo.email` puis `claims.email`. ## Décisions - **Chaîne consolidée dans `auth.ts`** (single source of truth — seul producteur de `name`, consommé uniquement par `layout.tsx → Header`). La vitrine calcule `displayName` au point d'usage car elle renvoie les claims bruts au client via une route API ; ici l'archi server-component fait d'`auth.ts` le point de résolution naturel. - **`layout.tsx`** : vérifié, inchangé — `user.name || user.email || ""` est déjà le tail `|| email` de la chaîne, il reçoit maintenant le nom résolu. - **`Header.tsx`** : vérifié, prop `userName: string` correct, inchangé. ## Vérification - `tsc --noEmit` : OK (typecheck de la chaîne + option `fetchUserInfo`) - `eslint src/lib/auth.ts` : clean (0 problème sur le fichier modifié) - smoke test racine (`npm test`) : OK (non-régression mobile) - _Note_ : le lint de `web/` a 3 erreurs **pré-existantes** dans d'autres fichiers (`lists/[id]/page.tsx`, `api/lists/[id]/tasks/route.ts`, `ThemeToggle.tsx`), hors scope de cette PR. - AC comportemental (user voit son nom) à confirmer au déploiement — pas de e2e dans `web/`. Diff : 1 fichier, +14/-4.
maximus added 1 commit 2026-05-30 18:10:47 +00:00
getAuthenticatedUser only read ID token claims, where `name` is often
absent, so the web app showed the user's email instead of their name
after SSO. Fetch the userInfo endpoint and resolve the display name with
the same fallback order as the vitrine (la-compagnie-maximus#80):
userInfo.name -> userInfo.username -> claims.name -> claims.username,
with the email fallback applied in the layout. Email now also prefers
userInfo over claims.

Single source of truth in auth.ts (only producer of `name`, consumed
solely by layout.tsx -> Header). layout.tsx and Header.tsx verified,
left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
maximus added the
status:review
type:bug
labels 2026-05-30 18:11:03 +00:00
Author
Owner

/pr-review — APPROVE

Summary — Correct, minimal, type-safe fix. The display-name resolution is moved to the userInfo endpoint with the ID-token claims preserved as fallback; the change is purely additive and well-scoped (1 file, +14/-4).

Verified

  • Scope — only web/src/lib/auth.ts modified, matching the PR body. Conventional commit + Fixes #70.
  • Type safetytsc --noEmit passes clean. Confirmed against the Logto types: UserInfoResponse = IdTokenClaims & {…} and IdTokenClaims declares name?, username?, email? (all Nullable<string>); fetchUserInfo is a valid key on the getLogtoContext parameter subset. Every field access in the diff is real and typed.
  • Single source of truthuser.name is consumed in exactly one place (layout.tsx:40, user.name || user.email || ""). The three other consumers (page.tsx, lists/[id]/page.tsx, api.ts) read only user.userId. So a possibly-undefined name is safely absorbed by the layout's || email || "" tail — the consolidation claim holds.
  • Security — no secrets, no injection surface, no new .env. The extra userinfo call sits inside the existing try/catch (errors → null → redirect to /auth).
  • No regression — claims fallback preserved; email now prefers userInfo?.email then claims.email.

Non-blocking note

  • PerffetchUserInfo: true now triggers a userinfo HTTP round-trip on every getAuthenticatedUser() call, which includes every authenticated API request via requireAuth() (web cookie path). The cache() wrapper dedupes within a single request, so the cost is one extra upstream call per request — acceptable, just worth tracking if the userinfo endpoint ever becomes a latency concern.

The behavioral AC (user sees "Max") remains to confirm at deploy, as noted in the PR.

## /pr-review — APPROVE **Summary** — Correct, minimal, type-safe fix. The display-name resolution is moved to the userInfo endpoint with the ID-token claims preserved as fallback; the change is purely additive and well-scoped (1 file, +14/-4). ### Verified - **Scope** — only `web/src/lib/auth.ts` modified, matching the PR body. Conventional commit + `Fixes #70`. - **Type safety** — `tsc --noEmit` passes clean. Confirmed against the Logto types: `UserInfoResponse = IdTokenClaims & {…}` and `IdTokenClaims` declares `name?`, `username?`, `email?` (all `Nullable<string>`); `fetchUserInfo` is a valid key on the `getLogtoContext` parameter subset. Every field access in the diff is real and typed. - **Single source of truth** — `user.name` is consumed in exactly one place (`layout.tsx:40`, `user.name || user.email || ""`). The three other consumers (`page.tsx`, `lists/[id]/page.tsx`, `api.ts`) read only `user.userId`. So a possibly-`undefined` `name` is safely absorbed by the layout's `|| email || ""` tail — the consolidation claim holds. - **Security** — no secrets, no injection surface, no new `.env`. The extra userinfo call sits inside the existing try/catch (errors → `null` → redirect to `/auth`). - **No regression** — claims fallback preserved; `email` now prefers `userInfo?.email` then `claims.email`. ### Non-blocking note - **Perf** — `fetchUserInfo: true` now triggers a userinfo HTTP round-trip on *every* `getAuthenticatedUser()` call, which includes every authenticated API request via `requireAuth()` (web cookie path). The `cache()` wrapper dedupes within a single request, so the cost is one extra upstream call per request — acceptable, just worth tracking if the userinfo endpoint ever becomes a latency concern. The behavioral AC (user sees "Max") remains to confirm at deploy, as noted in the PR.
maximus merged commit 7343f993ee into master 2026-05-30 18:52:23 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/simpl-liste#89
No description provided.