# AutoLuxe Atelier · securitate

Document pentru echipa care publică și întreține aplicația. Descrie ce protejează aplicația, cum anume, ce rămâne în grija serverului și ce trebuie verificat înainte de lansare.

## Ce protejăm

Datele personale ale clienților (nume, telefon, e-mail, adresă, numere de înmatriculare, VIN, fotografii ale mașinilor, semnături), datele financiare (plăți, prețuri) și integritatea dovezilor (raportul de stare, procesele-verbale semnate).

## Măsuri implementate

**Acces și sesiuni**
- Parolele se salvează cu `password_hash` (bcrypt, cu re-hash automat când se schimbă algoritmul implicit). Lungimea minimă e de 10 caractere, iar parolele foarte comune sunt refuzate.
- Conturile create de administrator primesc o parolă temporară, cu schimbare obligatorie la prima intrare. Până la schimbare, API-ul răspunde 423 la orice altă cerere.
- Autentificarea e limitată: 6 încercări greșite pe cont și 20 pe IP în 15 minute. Verificarea durează la fel și pentru conturile inexistente.
- Cookie-ul de sesiune e `HttpOnly`, `Secure` (pe HTTPS), `SameSite=Strict`, cu `use_strict_mode` și ID de 48 de caractere. ID-ul se regenerează la autentificare și la schimbarea parolei.
- Sesiunea expiră după 4 ore de inactivitate și după 14 ore în total (configurabil).
- Schimbarea parolei, dezactivarea contului sau schimbarea rolului închid imediat toate sesiunile deschise ale acelui cont.
- Sesiunile se salvează în `data/sessions` (0700), nu în dosarul temporar comun al serverului.
- Trebuie să rămână mereu cel puțin un administrator activ, iar un administrator nu își poate retrage singur drepturile.

**Autorizare**
- Fiecare rută din API declară permisiunea necesară (`app/api.php`, `app/lib/auth.php`). Verificarea se face pe server.
- Tehnicianul nu primește sume în niciun răspuns, nici prin API, nu doar în interfață.

**CSRF și origine**
- Orice cerere POST cere antetul `X-CSRF-Token`, legat de sesiune și comparat cu `hash_equals`. Autentificarea cere și ea token, ca protecție împotriva login-CSRF.
- Antetele `Origin`/`Referer`, când există, trebuie să fie chiar site-ul.
- API-ul acceptă doar GET și POST.

**Injecții**
- SQL: toate interogările sunt parametrizate (PDO, `EMULATE_PREPARES=false`). Numele de tabele și coloane din `ins()`/`upd()` sunt validate cu o expresie strictă și vin doar din cod. La căutare, caracterele `LIKE` sunt escapate.
- XSS: interfața construiește HTML numai prin funcția `H`, care escapează automat orice valoare. Testele salvează intenționat `<img src=x onerror=…>` în nume și observații și verifică afișarea ca text.
- Content-Security-Policy strictă: `script-src 'self'`, fără scripturi inline, fără `eval`, `frame-ancestors 'none'`, `object-src 'none'`, `base-uri 'none'`. Fonturile sunt locale, iar aplicația nu încarcă nimic de pe alte domenii.
- Alte antete: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: same-origin`, `Cross-Origin-Opener-Policy`, `Cross-Origin-Resource-Policy`, `Permissions-Policy`, plus HSTS pe HTTPS.
- Toate câmpurile au validare de tip și lungime pe server. Caracterele de control sunt eliminate.

**Fișiere încărcate**
- Se acceptă doar JPG, PNG și WebP. Tipul se verifică după conținut (`finfo`), nu după extensie.
- Fiecare imagine e decodată și re-encodată cu GD. Se elimină astfel metadatele EXIF (inclusiv coordonatele GPS) și orice conținut ascuns în fișier (de exemplu PHP sau HTML deghizat). Imaginea se limitează la 2048 px.
- Dimensiunea e limitată (12 MB, 60 de megapixeli) ca protecție împotriva „bombelor” de decompresie. Încărcările sunt limitate la 120 la 10 minute per utilizator.
- Fișierele au nume aleatoare de 128 de biți și se salvează în afara dosarului public. Se servesc doar prin API, după autentificare, cu `nosniff` și `CSP: sandbox`. Calea e verificată cu `realpath`.
- Semnătura e acceptată doar ca PNG valid (sub 900 KB, maximum 2000×1000) și e re-encodată.

**Integritatea dovezilor**
- Procesul-verbal semnat nu se mai poate modifica.
- Raportul de stare (defecte, fotografii, kilometri, vopsea) se blochează când lucrarea e predată sau anulată.
- Numerele certificatelor de garanție se alocă o singură dată, la predare, dintr-un contor de server (`AL-G-2026-00001`), și nu se mai schimbă.
- Jurnalul de audit înregistrează autentificările, programările, etapele, predările, plățile (inclusiv ștergerile, cu valorile vechi), schimbările de preț, de drepturi și de setări, exporturile și anonimizările.

**Configuratorul (endpoint public)**
- Funcția e oprită implicit.
- În modul de la server la server, cheia API se compară prin amprentă SHA-256, cu `hash_equals`. Cheia completă e afișată o singură dată și nu se păstrează în baza de date.
- În modul din navigator: CORS doar pentru originile din listă și limita de 5 cereri pe oră per IP, plus 200 pe oră în total.
- Endpoint-ul are câmp-capcană pentru roboți, acceptă doar JSON, validează strict datele și refuză codurile de servicii necunoscute.

**GDPR**
- Clientul are un câmp de acord pentru mesajele de întreținere și oferte, vizibil la reamintiri.
- Administratorul poate exporta toate datele unui client (JSON), pentru dreptul de acces și portabilitate.
- Anonimizarea (dreptul la ștergere) elimină datele personale, mesajele, fotografiile și semnăturile, dar păstrează lucrările și plățile, fără date de identificare, pentru evidența contabilă.

**Erori**
- Utilizatorul nu vede niciodată detalii tehnice, doar un cod de referință. Detaliile ajung în `data/logs/app-AAAA-LL.log`.

## În grija serverului (de configurat la publicare)

- [ ] HTTPS activ, cu redirecționare de pe HTTP (`.htaccess` o face pe Apache)
- [ ] `app/` și `data/` în afara dosarului public. Altfel, verificați că `/data/autoluxe.sqlite` și `/app/config.php` răspund 403
- [ ] `app/config.php`: `debug => false`, `base_url` corect, `install_key` golit după instalare
- [ ] `install.php` șters după instalare
- [ ] Drepturi pe fișiere: cod `640`/`750`, proprietar diferit de utilizatorul PHP, dacă se poate. Doar `data/` permite scriere
- [ ] `upload_max_filesize ≥ 16M` și `post_max_size ≥ 20M` în PHP
- [ ] `expose_php = Off`, `display_errors = Off`
- [ ] Cron pentru `tools/backup.py` și sincronizarea copiilor în altă locație. Testați o restaurare înainte de lansare
- [ ] Doar în spatele unui proxy (Cloudflare, load balancer): completați `trusted_proxies`, ca limitările pe IP să vadă IP-ul real
- [ ] Opțional: o limită de rată și la nivel de server web pentru `/api.php?r=login` și `/intake.php` (exemplu în `deploy/nginx.conf.example`)
- [ ] Opțional: WAF sau fail2ban pe jurnalele de acces, pentru răspunsurile 401/429 repetate

## Riscuri rămase, asumate

- **Fără autentificare în doi pași.** E recomandată pentru conturile de administrator într-o versiune următoare (TOTP).
- **Un dispozitiv compromis** (telefonul atelierului deblocat, lăsat nesupravegheat) are acces pe durata sesiunii. Recomandare: blocare automată a ecranului și deconectare la sfârșitul zilei.
- **Copiile de siguranță conțin date personale.** Locația externă trebuie să fie criptată și cu acces restrâns.
- **Modul „din navigator” al configuratorului** poate fi folosit pentru spam de la IP-uri diferite, deoarece antetul `Origin` poate fi falsificat în afara navigatorului. Limita globală (200 pe oră) oprește volumul. Folosiți modul de la server la server.
- **SQLite** blochează scrierile pe rând. La utilizare foarte intensă pot apărea mesaje „Baza de date e ocupată”, care se rezolvă repetând acțiunea (vezi CITESTE.md, secțiunea 10).

## Verificări automate

`tools/test_api.py` conține 94 de verificări, între care: acces fără autentificare, CSRF lipsă sau greșit, origine străină, injecții în căutare, XSS stocat, fișier PHP deghizat în JPG, SVG cu script, acces la fișiere fără sesiune, drepturile tehnicianului, sesiune închisă la dezactivare, protecția ultimului administrator, cheie greșită și câmpul-capcană la configurator, suprapuneri și duminici blocate, blocarea dovezilor după predare.
