Tau.Acuvim/portal/frontend/src/api/auth.ts
Diseri Pearson e9143f8c27 Portal: self-service My profile + secondaryColor on the sidebar
Closes the last two outstanding items from FrontEndPrompt.txt's
original phase list.

Frontend
- New MyProfilePage at /profile, any authenticated user. Two cards:
  read-only email + roles + editable displayName; current/new/confirm
  password change with client-side AntD policy validation matching
  Identity's backend rules.
- AppLayout: replace the inline displayName + Sign out with a Dropdown
  on a user-icon button. Items are "My profile" → /profile and
  "Sign out". Header is cleaner; logout still one click.
- AppLayout: Sider now uses branding.secondaryColor with Menu
  theme="dark" + transparent background so the brand colour shows
  through. Sidebar finally reflects the secondary brand colour like
  the header reflects primary.
- useAuth exposes setUser so a successful profile save updates the
  header name without a /auth/me round-trip (PUT already returns
  the updated CurrentUserResponse).

Backend
- PUT /api/auth/me — update own displayName. Email and roles stay
  immutable here (admin-only for role changes). Returns the updated
  CurrentUserResponse.
- POST /api/auth/me/change-password — requires both current and new
  password. Wraps UserManager.ChangePasswordAsync so Identity's
  password policy (8+, upper, lower, digit) runs server-side and the
  current password is verified before any change.

Curl-verified
- PUT /me with new displayName → 200 with updated user.
- change-password with wrong current → 400 "Incorrect password."
- change-password with weak new → 400 listing each policy violation.
- change-password valid + revert → 204 / 204.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:04:18 +02:00

44 lines
1.2 KiB
TypeScript

import { api } from './client';
export interface CurrentUser {
email: string;
displayName: string;
roles: string[];
}
export async function login(email: string, password: string): Promise<CurrentUser> {
const { data } = await api.post<CurrentUser>('/auth/login', { email, password });
return data;
}
export async function logout(): Promise<void> {
await api.post('/auth/logout');
}
export async function fetchCurrentUser(): Promise<CurrentUser | null> {
try {
const { data } = await api.get<CurrentUser>('/auth/me');
return data;
} catch (err: unknown) {
if (axiosStatus(err) === 401) return null;
throw err;
}
}
export async function updateMyProfile(displayName: string): Promise<CurrentUser> {
const { data } = await api.put<CurrentUser>('/auth/me', { displayName });
return data;
}
export async function changeMyPassword(currentPassword: string, newPassword: string): Promise<void> {
await api.post('/auth/me/change-password', { currentPassword, newPassword });
}
function axiosStatus(err: unknown): number | undefined {
if (typeof err === 'object' && err !== null && 'response' in err) {
const resp = (err as { response?: { status?: number } }).response;
return resp?.status;
}
return undefined;
}