139 lines
3.5 KiB
TypeScript
139 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from "react";
|
|
|
|
import { API_URL } from "./config";
|
|
|
|
// Permission constants matching backend
|
|
export const Permission = {
|
|
VIEW_COUNTER: "view_counter",
|
|
INCREMENT_COUNTER: "increment_counter",
|
|
USE_SUM: "use_sum",
|
|
VIEW_AUDIT: "view_audit",
|
|
} as const;
|
|
|
|
export type PermissionType = typeof Permission[keyof typeof Permission];
|
|
|
|
interface User {
|
|
id: number;
|
|
email: string;
|
|
roles: string[];
|
|
permissions: string[];
|
|
}
|
|
|
|
interface AuthContextType {
|
|
user: User | null;
|
|
isLoading: boolean;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
register: (email: string, password: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
hasPermission: (permission: PermissionType) => boolean;
|
|
hasAnyPermission: (...permissions: PermissionType[]) => boolean;
|
|
hasRole: (role: string) => boolean;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
checkAuth();
|
|
}, []);
|
|
|
|
const checkAuth = async () => {
|
|
try {
|
|
const res = await fetch(`${API_URL}/api/auth/me`, {
|
|
credentials: "include",
|
|
});
|
|
if (res.ok) {
|
|
const userData = await res.json();
|
|
setUser(userData);
|
|
}
|
|
} catch {
|
|
// Not authenticated
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const login = async (email: string, password: string) => {
|
|
const res = await fetch(`${API_URL}/api/auth/login`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify({ email, password }),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const error = await res.json();
|
|
throw new Error(error.detail || "Login failed");
|
|
}
|
|
|
|
const userData = await res.json();
|
|
setUser(userData);
|
|
};
|
|
|
|
const register = async (email: string, password: string) => {
|
|
const res = await fetch(`${API_URL}/api/auth/register`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify({ email, password }),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const error = await res.json();
|
|
throw new Error(error.detail || "Registration failed");
|
|
}
|
|
|
|
const userData = await res.json();
|
|
setUser(userData);
|
|
};
|
|
|
|
const logout = async () => {
|
|
await fetch(`${API_URL}/api/auth/logout`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
});
|
|
setUser(null);
|
|
};
|
|
|
|
const hasPermission = useCallback((permission: PermissionType): boolean => {
|
|
return user?.permissions.includes(permission) ?? false;
|
|
}, [user]);
|
|
|
|
const hasAnyPermission = useCallback((...permissions: PermissionType[]): boolean => {
|
|
return permissions.some((p) => user?.permissions.includes(p) ?? false);
|
|
}, [user]);
|
|
|
|
const hasRole = useCallback((role: string): boolean => {
|
|
return user?.roles.includes(role) ?? false;
|
|
}, [user]);
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
user,
|
|
isLoading,
|
|
login,
|
|
register,
|
|
logout,
|
|
hasPermission,
|
|
hasAnyPermission,
|
|
hasRole,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error("useAuth must be used within an AuthProvider");
|
|
}
|
|
return context;
|
|
}
|