leistungsbilanz-ts/src/frontend/utils/auth-api.ts

95 lines
2.9 KiB
TypeScript

import {
WRITE_REQUEST_HEADER_NAME,
WRITE_REQUEST_HEADER_VALUE,
} from "../../shared/constants/request-security";
// Small, self-contained fetch helper for the auth/user endpoints - kept
// separate from the large domain api.ts so this feature doesn't have to
// touch that file's shared `request()` internals.
export type UserRole = "admin" | "user";
export interface AuthStatusDto {
signedIn: boolean;
mustChangePassword: boolean;
}
export interface AuthUserDto {
id: string;
username: string;
name: string;
role: UserRole;
mustChangePassword: boolean;
}
export interface UserDto {
id: string;
username: string;
name: string;
role: UserRole;
active: boolean;
mustChangePassword: boolean;
createdAt: string;
}
export class AuthApiError extends Error {
constructor(
message: string,
public readonly status: number
) {
super(message);
}
}
async function request<T>(url: string, init?: RequestInit): Promise<T> {
const response = await fetch(url, {
...init,
headers: {
"Content-Type": "application/json",
[WRITE_REQUEST_HEADER_NAME]: WRITE_REQUEST_HEADER_VALUE,
...init?.headers,
},
cache: "no-store",
});
if (!response.ok) {
let message = `Anfrage fehlgeschlagen (Status ${response.status})`;
try {
const body = (await response.json()) as { error?: unknown };
if (typeof body.error === "string") message = body.error;
} catch {
// Keep the generic message for non-JSON error responses.
}
throw new AuthApiError(message, response.status);
}
if (response.status === 204) return undefined as T;
return response.json() as Promise<T>;
}
export const authApi = {
status: () => request<AuthStatusDto>("/api/auth/status"),
login: (username: string, password: string) =>
request<AuthStatusDto>("/api/auth/login", {
method: "POST",
body: JSON.stringify({ username, password }),
}),
me: () => request<AuthUserDto>("/api/auth/me"),
changePassword: (currentPassword: string, newPassword: string) =>
request<{ changed: true }>("/api/auth/change-password", {
method: "POST",
body: JSON.stringify({ currentPassword, newPassword }),
}),
logout: () => request<{ signedOut: true }>("/api/auth/logout", { method: "POST" }),
};
export const userApi = {
list: () => request<UserDto[]>("/api/users"),
create: (input: { username: string; name: string; role: UserRole; initialPassword: string }) =>
request<UserDto>("/api/users", { method: "POST", body: JSON.stringify(input) }),
update: (userId: string, patch: { role?: UserRole; active?: boolean }) =>
request<UserDto>(`/api/users/${userId}`, { method: "PATCH", body: JSON.stringify(patch) }),
resetPassword: (userId: string, newPassword: string) =>
request<UserDto>(`/api/users/${userId}/reset-password`, {
method: "POST",
body: JSON.stringify({ newPassword }),
}),
};