Add displayName to auth response and configurable display name claim for OIDC
Some checks failed
CI / build (push) Successful in 1m11s
CI / docker (push) Successful in 49s
CI / deploy (push) Failing after 2m9s

- Add displayName field to AuthTokenResponse so the UI shows human-readable
  names instead of internal JWT subjects (e.g. user:oidc:<hash>)
- Add displayNameClaim to OIDC config (default: "name") allowing admins to
  configure which ID token claim contains the user's display name
- Support dot-separated claim paths (e.g. profile.display_name) like rolesClaim
- Add admin UI field for Display Name Claim on the OIDC config page
- ClickHouse migration: ALTER TABLE adds display_name_claim column

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-14 16:09:24 +01:00
parent 6676e209c7
commit 463cab1196
18 changed files with 96 additions and 32 deletions

View File

@@ -64,13 +64,14 @@ export const useAuthStore = create<AuthState>((set, get) => ({
if (error || !data) {
throw new Error('Invalid credentials');
}
const { accessToken, refreshToken } = data;
const { accessToken, refreshToken, displayName } = data;
localStorage.removeItem('cameleer-oidc-end-session');
persistTokens(accessToken, refreshToken, username);
const name = displayName ?? username;
persistTokens(accessToken, refreshToken, name);
set({
accessToken,
refreshToken,
username,
username: name,
roles: parseRolesFromJwt(accessToken),
isAuthenticated: true,
loading: false,
@@ -92,9 +93,8 @@ export const useAuthStore = create<AuthState>((set, get) => ({
if (error || !data) {
throw new Error('OIDC login failed');
}
const { accessToken, refreshToken } = data;
const payload = JSON.parse(atob(accessToken.split('.')[1]));
const username = payload.sub ?? 'oidc-user';
const { accessToken, refreshToken, displayName } = data;
const username = displayName ?? 'oidc-user';
persistTokens(accessToken, refreshToken, username);
set({
accessToken,
@@ -120,11 +120,12 @@ export const useAuthStore = create<AuthState>((set, get) => ({
body: { refreshToken },
});
if (error || !data) return false;
const username = get().username ?? '';
const username = data.displayName ?? get().username ?? '';
persistTokens(data.accessToken, data.refreshToken, username);
set({
accessToken: data.accessToken,
refreshToken: data.refreshToken,
username,
roles: parseRolesFromJwt(data.accessToken),
isAuthenticated: true,
});