feat: dashboard setup with passkey authentication
Scaffold Vite + React + TypeScript project with Tailwind CSS dark theme. Add Axios API client with JWT interceptor and auto-refresh, WebAuthn passkey auth flow (register/login), protected route wrapper, and React Router with public and protected routes.
This commit is contained in:
parent
f218865872
commit
f121f376ae
20 changed files with 5274 additions and 0 deletions
75
dashboard/src/hooks/useAuth.ts
Normal file
75
dashboard/src/hooks/useAuth.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { startRegistration, startAuthentication } from '@simplewebauthn/browser';
|
||||
import {
|
||||
registerBegin,
|
||||
registerComplete,
|
||||
loginBegin,
|
||||
loginComplete,
|
||||
} from '../api/auth';
|
||||
|
||||
export interface UseAuthReturn {
|
||||
isAuthenticated: boolean;
|
||||
login: (username: string) => Promise<void>;
|
||||
register: (username: string) => Promise<void>;
|
||||
logout: () => void;
|
||||
error: string | null;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export function useAuth(): UseAuthReturn {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [token, setToken] = useState<string | null>(() =>
|
||||
localStorage.getItem('access_token')
|
||||
);
|
||||
|
||||
const isAuthenticated = useMemo(() => !!token, [token]);
|
||||
|
||||
const register = useCallback(async (username: string) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const { options } = await registerBegin(username);
|
||||
const attestation = await startRegistration({ optionsJSON: options as any });
|
||||
const tokens = await registerComplete(username, attestation);
|
||||
localStorage.setItem('access_token', tokens.access_token);
|
||||
localStorage.setItem('refresh_token', tokens.refresh_token);
|
||||
setToken(tokens.access_token);
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err?.response?.data?.detail || err?.message || 'Registration failed';
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const login = useCallback(async (username: string) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const { options } = await loginBegin(username);
|
||||
const assertion = await startAuthentication({ optionsJSON: options as any });
|
||||
const tokens = await loginComplete(username, assertion);
|
||||
localStorage.setItem('access_token', tokens.access_token);
|
||||
localStorage.setItem('refresh_token', tokens.refresh_token);
|
||||
setToken(tokens.access_token);
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err?.response?.data?.detail || err?.message || 'Login failed';
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
setToken(null);
|
||||
}, []);
|
||||
|
||||
return { isAuthenticated, login, register, logout, error, loading };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue