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>
44 lines
1.2 KiB
TypeScript
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;
|
|
}
|