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
}