API Auth - Documentazione โ
Questo documento descrive la struttura delle API di autenticazione basata sulle best practice di React Router con validazione Zod.
Struttura dei file โ
app/
โโโ api/
โ โโโ auth/
โ โโโ auth.api.ts # Funzioni API per autenticazione
โโโ schemas/
โ โโโ api.schema.ts # Schema Zod base per API
โ โโโ auth/
โ โโโ auth.schema.ts # Schema Zod per autenticazione
โโโ lib/
โ โโโ api.ts # Utility API (apiRequest, ApiError)
โ โโโ storage.ts # Utility localStorage
โ โโโ auth/
โ โโโ auth.ts # Servizio di autenticazione
โ โโโ loaders.ts # Factory functions
โโโ store/
โ โโโ auth/
โ โโโ auth.store.ts # Zustand store
โโโ hooks/
โโโ api/
โโโ auth/
โโโ useAuthApi.ts # TanStack Query hooksSchemi Zod โ
Gli schemi definiscono e validano i dati inviati e ricevuti dalle API:
LoginRequestSchema โ
{
username: string,
password: string,
recaptcha?: string
}LoginResponseSchema โ
{
success: boolean,
message: string,
context: string,
data: {
redirect: "main" | "module" | "confirm_login" | "companies_list" | "password_change" | "contract" | "dealer",
module?: string,
debug_cookie_data?: any
}
}CompanySchema โ
{
id: number,
name: string
}SetCompanyRequestSchema โ
{
id: number // >= 0 (usa 0 per resettare la ditta attiva)
}UserSettingsSchema โ
{
success: boolean,
message: string,
context: string,
data: {
id_company: number,
d_company: string,
d_user: string,
multiple_companies: boolean,
commoncart_btn_fastlabel: boolean,
commoncart_btn_offline: boolean,
modules: Array<{
id: number,
name: string,
path: string,
target?: string
}>
}
}Funzioni API โ
Tutte le funzioni in app/api/auth/auth.api.ts:
login(credentials: LoginRequest): Promise<LoginResponse> โ
Esegue il login e restituisce i dati di redirect.
Esempio:
import { login } from "@/api/auth/auth.api";
const response = await login({
username: "mario",
password: "rossi123"
});
console.log(response.data.redirect); // "main", "module", ecc.checkAuth(): Promise<SessionData> โ
Verifica lo stato di autenticazione e restituisce i dati di sessione.
Esempio:
import { checkAuth } from "@/api/auth/auth.api";
const sessionData = await checkAuth();
console.log(sessionData.data);logout(): Promise<ApiResponse> โ
Esegue il logout invalidando la sessione sul server.
Esempio:
import { logout } from "@/api/auth/auth.api";
await logout();getCompaniesList(): Promise<CompaniesListResponse> โ
Ottiene la lista delle aziende disponibili per l'utente.
Esempio:
import { getCompaniesList } from "@/api/auth/auth.api";
const response = await getCompaniesList();
response.data.forEach(company => {
console.log(`${company.id}: ${company.name}`);
});setActiveCompany(companyData: SetCompanyRequest): Promise<ApiResponse> โ
Imposta l'azienda attiva per la sessione. Usa id: 0 per resettare la ditta attiva e tornare all'elenco.
Esempio:
import { setActiveCompany } from "@/api/auth/auth.api";
// Imposta una ditta specifica
await setActiveCompany({ id: 42 });
// Reset per tornare all'elenco ditte
await setActiveCompany({ id: 0 });getConfirmLoginData(): Promise<ConfirmLoginData> โ
Ottiene i dati di un altro utente giร autenticato (per conferma).
Esempio:
import { getConfirmLoginData } from "@/api/auth/auth.api";
const data = await getConfirmLoginData();
console.log(`Browser: ${data.data.browser}, Piattaforma: ${data.data.platform}`);confirmLogin(): Promise<LoginResponse> โ
Conferma l'accesso sostituendo la sessione esistente.
Esempio:
import { confirmLogin } from "@/api/auth/auth.api";
const response = await confirmLogin();
// Gestisci il redirect come nel logingetUserSettings(): Promise<UserSettings> โ
Ottiene le impostazioni dell'utente inclusi i moduli disponibili.
Esempio:
import { getUserSettings } from "@/api/auth/auth.api";
const settings = await getUserSettings();
console.log(`Azienda: ${settings.data.d_company}`);
console.log(`Moduli:`, settings.data.modules);Gestione errori โ
Tutte le funzioni API lanciano ApiError in caso di errore:
import { ApiError } from "@/lib/api";
try {
await login({ username: "test", password: "wrong" });
} catch (error) {
if (error instanceof ApiError) {
console.error(`Errore API: ${error.message}`);
console.error(`Status: ${error.status}`);
console.error(`Context: ${error.context}`);
}
}Gestione automatica errori 401 (Unauthorized) โ
Quando una chiamata API restituisce un errore 401 e l'utente รจ autenticato:
- Alert automatico: Viene mostrato un alert con il messaggio di errore
- Logout automatico: Lo store Zustand viene pulito
- Broadcast logout: Notifica alle altre tab tramite BroadcastChannel
- Redirect al login: L'utente viene reindirizzato alla pagina di login
Questo comportamento รจ gestito automaticamente dalla funzione apiRequest in app/lib/api.ts e non richiede gestione manuale nelle singole chiamate API.
Esempio di comportamento:
// Se l'utente รจ autenticato e la sua sessione รจ scaduta
try {
await getCompaniesList();
} catch (error) {
// Prima che il catch venga eseguito:
// 1. Viene mostrato un alert con "Credenziali non valide o sessione scaduta"
// 2. Lo store viene pulito (logout)
// 3. L'utente viene reindirizzato a "/"
}TanStack Query Hooks โ
Per le chiamate API usa i TanStack Query hooks:
import { useCompaniesList } from "@/hooks/api/auth/useAuthApi";
function MyComponent() {
const { data, isLoading, error } = useCompaniesList();
if (isLoading) return <div>Caricamento...</div>;
if (error) return <div>Errore: {error.message}</div>;
if (!data) return null;
return (
<ul>
{data.data.map(company => (
<li key={company.id}>{company.name}</li>
))}
</ul>
);
}AuthService โ
Il servizio centralizzato in app/lib/auth.ts usa le API internamente:
import { authService } from "@/lib/auth/auth";
// Login
const response = await authService.login(username, password);
if (response) {
// Gestisci redirect in base a response.data.redirect
}
// Logout
await authService.logout();
// Check auth
const isAuth = authService.isAuthenticated();Storage utility โ
Funzioni helper in app/lib/storage.ts:
import { getLoginData, clearLoginData, isAuthenticated } from "@/lib/auth/storage";
// Recupera dati di login salvati
const loginData = getLoginData();
if (loginData) {
console.log(`Tipo redirect: ${loginData.redirect}`);
}
// Pulisci dati
clearLoginData();
// Verifica autenticazione
if (isAuthenticated()) {
console.log("Utente autenticato");
}Validazione con Zod โ
I dati sono automaticamente validati da Zod. In caso di dati non validi, viene lanciato un errore:
import { LoginRequestSchema } from "@/schemas/auth/auth.schema";
try {
// Valida manualmente se necessario
const validData = LoginRequestSchema.parse({
username: "test",
password: "test123"
});
} catch (error) {
if (error instanceof z.ZodError) {
console.error(error.errors);
}
}Configurazione โ
L'URL base dell'API รจ definito in app/api/auth.api.ts:
const API_BASE_URL = "https://api.local.daisy/erp_auth";Tutte le richieste includono automaticamente:
credentials: "include"per gestire i cookie di sessioneContent-Type: application/jsonheader- Validazione Zod dei dati inviati e ricevuti
Best Practices โ
Usa sempre le funzioni API invece di fetch diretto
typescript// โ Non fare fetch("/erp_auth/login", { ... }) // โ Fai cosรฌ import { login } from "@/api/auth/auth.api"; await login({ username, password });Gestisci sempre gli errori
typescripttry { const response = await login({ username, password }); } catch (error) { if (error instanceof ApiError) { // Gestisci errore API } }Usa gli schemi Zod per validare dati custom
typescriptimport { LoginRequestSchema } from "@/schemas/auth/auth.schema"; const result = LoginRequestSchema.safeParse(formData); if (!result.success) { // Mostra errori validazione }Usa TanStack Query hooks per componenti React
typescriptconst { data, isLoading, error } = useUserSettings();
Flusso di autenticazione completo โ
// 1. Login
const response = await authService.login(username, password);
if (!response) {
// Errore login
return;
}
// 2. Gestisci redirect
switch (response.data.redirect) {
case "companies_list":
// Mostra lista aziende
const companies = await getCompaniesList();
// Utente sceglie azienda
await setActiveCompany({ id: selectedId });
// Poi ottieni settings
break;
case "main":
case "module":
// Vai direttamente alla app
const settings = await getUserSettings();
// Mostra moduli disponibili
break;
case "confirm_login":
// Altro utente loggato
const otherUser = await getConfirmLoginData();
// Mostra conferma
await confirmLogin();
break;
// ... altri casi
}