- Update copilot-instructions.md with user model, API routes, and frontend patterns - Update README.md with RBAC details, user management API, and security sections - Add user management technical documentation to TECH-CHANGELOG.md - Bump version to 2025.1.0-alpha.13 with user management changelog entries
183 lines
4.2 KiB
TypeScript
183 lines
4.2 KiB
TypeScript
/**
|
|
* Authentication API client for the dashboard.
|
|
*
|
|
* Provides functions to interact with auth endpoints including login,
|
|
* logout, and fetching current user information.
|
|
*/
|
|
|
|
export interface User {
|
|
id: number;
|
|
username: string;
|
|
role: 'user' | 'editor' | 'admin' | 'superadmin';
|
|
is_active: boolean;
|
|
}
|
|
|
|
export interface LoginRequest {
|
|
username: string;
|
|
password: string;
|
|
}
|
|
|
|
export interface LoginResponse {
|
|
message: string;
|
|
user: {
|
|
id: number;
|
|
username: string;
|
|
role: string;
|
|
};
|
|
}
|
|
|
|
export interface AuthCheckResponse {
|
|
authenticated: boolean;
|
|
role?: string;
|
|
}
|
|
|
|
/**
|
|
* Change password for the currently authenticated user.
|
|
*/
|
|
export async function changePassword(currentPassword: string, newPassword: string): Promise<{ message: string }> {
|
|
const res = await fetch('/api/auth/change-password', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }),
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
throw new Error(data.error || 'Failed to change password');
|
|
}
|
|
|
|
return data as { message: string };
|
|
}
|
|
|
|
/**
|
|
* Authenticate a user with username and password.
|
|
*
|
|
* @param username - The user's username
|
|
* @param password - The user's password
|
|
* @returns Promise<LoginResponse>
|
|
* @throws Error if login fails
|
|
*/
|
|
export async function login(username: string, password: string): Promise<LoginResponse> {
|
|
const res = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include', // Important for session cookies
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok || data.error) {
|
|
throw new Error(data.error || 'Login failed');
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Log out the current user.
|
|
*
|
|
* @returns Promise<void>
|
|
* @throws Error if logout fails
|
|
*/
|
|
export async function logout(): Promise<void> {
|
|
const res = await fetch('/api/auth/logout', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok || data.error) {
|
|
throw new Error(data.error || 'Logout failed');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch the current authenticated user's information.
|
|
*
|
|
* @returns Promise<User>
|
|
* @throws Error if not authenticated or request fails
|
|
*/
|
|
export async function fetchCurrentUser(): Promise<User> {
|
|
const res = await fetch('/api/auth/me', {
|
|
method: 'GET',
|
|
credentials: 'include',
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok || data.error) {
|
|
throw new Error(data.error || 'Failed to fetch current user');
|
|
}
|
|
|
|
return data as User;
|
|
}
|
|
|
|
/**
|
|
* Quick check if user is authenticated (lighter than fetchCurrentUser).
|
|
*
|
|
* @returns Promise<AuthCheckResponse>
|
|
*/
|
|
export async function checkAuth(): Promise<AuthCheckResponse> {
|
|
const res = await fetch('/api/auth/check', {
|
|
method: 'GET',
|
|
credentials: 'include',
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
throw new Error('Failed to check authentication status');
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Helper function to check if a user has a specific role.
|
|
*
|
|
* @param user - The user object
|
|
* @param role - The role to check for
|
|
* @returns boolean
|
|
*/
|
|
export function hasRole(user: User | null, role: string): boolean {
|
|
if (!user) return false;
|
|
return user.role === role;
|
|
}
|
|
|
|
/**
|
|
* Helper function to check if a user has any of the specified roles.
|
|
*
|
|
* @param user - The user object
|
|
* @param roles - Array of roles to check for
|
|
* @returns boolean
|
|
*/
|
|
export function hasAnyRole(user: User | null, roles: string[]): boolean {
|
|
if (!user) return false;
|
|
return roles.includes(user.role);
|
|
}
|
|
|
|
/**
|
|
* Helper function to check if user is superadmin.
|
|
*/
|
|
export function isSuperadmin(user: User | null): boolean {
|
|
return hasRole(user, 'superadmin');
|
|
}
|
|
|
|
/**
|
|
* Helper function to check if user is admin or higher.
|
|
*/
|
|
export function isAdminOrHigher(user: User | null): boolean {
|
|
return hasAnyRole(user, ['admin', 'superadmin']);
|
|
}
|
|
|
|
/**
|
|
* Helper function to check if user is editor or higher.
|
|
*/
|
|
export function isEditorOrHigher(user: User | null): boolean {
|
|
return hasAnyRole(user, ['editor', 'admin', 'superadmin']);
|
|
}
|