Skip to content

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 hooks

Schemi Zod โ€‹

Gli schemi definiscono e validano i dati inviati e ricevuti dalle API:

LoginRequestSchema โ€‹

typescript
{
  username: string,
  password: string,
  recaptcha?: string
}

LoginResponseSchema โ€‹

typescript
{
  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 โ€‹

typescript
{
  id: number,
  name: string
}

SetCompanyRequestSchema โ€‹

typescript
{
  id: number  // >= 0 (usa 0 per resettare la ditta attiva)
}

UserSettingsSchema โ€‹

typescript
{
  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:

typescript
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:

typescript
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:

typescript
import { logout } from "@/api/auth/auth.api";

await logout();

getCompaniesList(): Promise<CompaniesListResponse> โ€‹

Ottiene la lista delle aziende disponibili per l'utente.

Esempio:

typescript
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:

typescript
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:

typescript
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:

typescript
import { confirmLogin } from "@/api/auth/auth.api";

const response = await confirmLogin();
// Gestisci il redirect come nel login

getUserSettings(): Promise<UserSettings> โ€‹

Ottiene le impostazioni dell'utente inclusi i moduli disponibili.

Esempio:

typescript
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:

typescript
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:

  1. Alert automatico: Viene mostrato un alert con il messaggio di errore
  2. Logout automatico: Lo store Zustand viene pulito
  3. Broadcast logout: Notifica alle altre tab tramite BroadcastChannel
  4. 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:

typescript
// 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:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
const API_BASE_URL = "https://api.local.daisy/erp_auth";

Tutte le richieste includono automaticamente:

  • credentials: "include" per gestire i cookie di sessione
  • Content-Type: application/json header
  • Validazione Zod dei dati inviati e ricevuti

Best Practices โ€‹

  1. 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 });
  2. Gestisci sempre gli errori

    typescript
    try {
      const response = await login({ username, password });
    } catch (error) {
      if (error instanceof ApiError) {
        // Gestisci errore API
      }
    }
  3. Usa gli schemi Zod per validare dati custom

    typescript
    import { LoginRequestSchema } from "@/schemas/auth/auth.schema";
    
    const result = LoginRequestSchema.safeParse(formData);
    if (!result.success) {
      // Mostra errori validazione
    }
  4. Usa TanStack Query hooks per componenti React

    typescript
    const { data, isLoading, error } = useUserSettings();

Flusso di autenticazione completo โ€‹

typescript
// 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
}

Documentazione Elerama Frontend