Add user authentication system with API key management (#50)
- Add User, Session, AuthSettings models with bcrypt password hashing - Add auth endpoints: login, logout, change-password, me - Add API key CRUD: create (orch_xxx format), list, revoke - Add admin user management: list, create, update, reset-password - Create default admin user on startup (admin/admin) - Add frontend: Login page, API Keys page, Admin Users page - Add AuthContext for session state management - Add user menu to Layout header with login/logout/settings - Add 15 integration tests for auth system - Add migration 006_auth_tables.sql
This commit is contained in:
87
frontend/src/contexts/AuthContext.tsx
Normal file
87
frontend/src/contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
|
||||
import { User } from '../types';
|
||||
import { getCurrentUser, login as apiLogin, logout as apiLogout } from '../api';
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
login: (username: string, password: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: AuthProviderProps) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Check session on initial load
|
||||
useEffect(() => {
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const currentUser = await getCurrentUser();
|
||||
setUser(currentUser);
|
||||
} catch {
|
||||
setUser(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
const login = useCallback(async (username: string, password: string) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const loggedInUser = await apiLogin({ username, password });
|
||||
setUser(loggedInUser);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Login failed';
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
await apiLogout();
|
||||
setUser(null);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Logout failed';
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const clearError = useCallback(() => {
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, loading, error, login, logout, clearError }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user