e-bon
e-bon.ro
Referință API

Endpoint-uri de autentificare

Endpoint-uri REST sub /api/v1/auth — înregistrare, autentificare, parolă uitată, reîmprospătare și deconectare — care emit și revocă perechea de jetoane JWT de acces + reîmprospătare folosită de Portalul e-bon și de aplicația mobilă E-BON.

Endpoint-uri de autentificare

API-ul de autentificare expune cele cinci endpoint-uri sub /api/v1/auth care alimentează fluxul JWT de înregistrare / autentificare / reîmprospătare / deconectare folosit de Portalul e-bon și de aplicația mobilă E-BON. Sunt singurele endpoint-uri din API pe care un apelant nou-venit le poate folosi fără să dețină deja credențiale — register, login, forgot-password și refresh sunt publice; logout cere un JWT valid din Portal pentru ca serverul să știe al cui jeton de reîmprospătare să-l revoce.

Pentru o introducere conceptuală în cele două moduri de autentificare (cheie API vs JWT din Portal), cum trimiți jetonul de acces și cum funcționează permisiunile, vezi Autentificare. Pagina pe care te afli este referința la nivel de protocol pentru cele cinci rute /api/v1/auth/*.

Toată suprafața /api/v1/auth este protejată cu o limită de rată mai strictă decât restul API-ului. Vezi Durata jetoanelor și limite de rată mai jos pentru cifrele exacte. Plicul de eroare, regulile de idempotență și convențiile de paginare sunt documentate o singură dată pe Prezentarea API-ului; pe această pagină listăm doar codurile de eroare specifice fiecărui endpoint.

POST /api/v1/auth/register

Creează o organizație nou-nouță și utilizatorul Owner care o deține: provizionează un utilizator Firebase Auth, scrie documentele organizations/{orgId} și organizations/{orgId}/users/{uid}, opțional populează o primă locație din adresa ANAF furnizată, setează custom claims Firebase (orgId, role) pentru regulile de securitate Firestore și returnează o pereche proaspătă de jetoane de acces + reîmprospătare.

Autentificare: Public — nu necesită autentificare.

Corpul cererii

CâmpTipObligatoriuNote
emailstringdaAdresă de email validă. Devine credențialul de autentificare Firebase Auth.
passwordstringdaMinimum 8 caractere.
companyNamestringda1–255 caractere. Folosit atât ca nume al organizației, cât și ca displayName în Firebase Auth.
cuistringdaCod fiscal românesc, max 20 caractere. Trebuie să respecte ^(RO)?\d{2,10}$ (ex. RO12345678 sau 12345678).
addressstringnuPână la 500 caractere. Când este furnizată, serverul o parsează prin parseAnafAddress și populează atât billingAddress, cât și o primă locație.

Răspuns 201

{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "eyJhbGciOi...",
  "userId": "user_xyz",
  "orgId": "acme_corp"
}

Noul utilizator este creat cu rolul owner. Folosește accessToken imediat ca Authorization: Bearer <jwt> la apelurile ulterioare; persistă refreshToken în siguranță ca să poți apela POST /api/v1/auth/refresh odată ce jetonul de acces expiră.

Erori

  • VALIDATION_ERROR (400) — corpul nu a trecut validarea Zod (câmp lipsă, parolă mai scurtă de 8 caractere, cui malformat, câmp prea lung).
  • CONFLICT (409) — An account with this email already exists — Firebase Auth a refuzat cu auth/email-already-exists.
  • RATE_LIMIT_EXCEEDED (429) — fereastra limitei de rată pentru autentificare a fost atinsă. Reîncearcă după secundele indicate de Retry-After.
  • INTERNAL_ERROR (500) — Failed to create user account (eroare Firebase Auth neașteptată) sau Failed to create organization (scrierea în lot Firestore a eșuat; utilizatorul Firebase Auth creat parțial este derulat înapoi automat).

Catalogul HTTP complet este pe Prezentare API › Catalogul codurilor de eroare HTTP iar pașii de recuperare per cod se află pe referința de erori.

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "owner@acme.example",
    "password": "o-parola-puternica",
    "companyName": "Acme Corp SRL",
    "cui": "RO12345678",
    "address": "Str. Lipscani 12, sector 3, București"
  }'

POST /api/v1/auth/login

Autentifică prin email + parolă față de Firebase Auth și returnează o pereche proaspătă de jetoane de acces + reîmprospătare. Handler-ul rezolvă orgId și role din custom claims-urile Firebase ale utilizatorului; dacă acelea lipsesc, cade înapoi pe o interogare collectionGroup('users') și scrie leneș claims-urile rezolvate înapoi, astfel încât autentificarea următoare să fie mai rapidă.

Autentificare: Public — nu necesită autentificare.

Corpul cererii

CâmpTipObligatoriuNote
emailstringdaAdresă de email validă.
passwordstringdaNu poate fi gol.

Răspuns 200

{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "eyJhbGciOi...",
  "userId": "user_xyz",
  "orgId": "acme_corp"
}

Jetonul de reîmprospătare este stocat pe server ca hash SHA-256 sub organizations/{orgId}/users/{uid}/refreshTokens ca să poată fi ulterior revocat de /auth/logout sau rotit de /auth/refresh.

Erori

  • VALIDATION_ERROR (400) — corpul nu a trecut validarea Zod.
  • UNAUTHORIZED (401) — Invalid email or password (returnat uniform pentru email necunoscut, parolă greșită sau un utilizator Firebase Auth fără apartenență Firestore — răspunsul nu dezvăluie niciodată care caz se aplică).
  • RATE_LIMIT_EXCEEDED (429) — fereastra limitei de rată pentru autentificare a fost atinsă.
  • INTERNAL_ERROR (500) — Authentication service misconfigured când serverului îi lipsește FIREBASE_WEB_API_KEY și nu este îndreptat către emulatorul Firebase Auth.

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "owner@acme.example",
    "password": "o-parola-puternica"
  }'

POST /api/v1/auth/forgot-password

Cere serviciului Firebase Identity Toolkit să trimită emailul de resetare a parolei pentru adresa dată. Handler-ul returnează întotdeauna 200 — chiar și când emailul nu există sau apelul Firebase din amonte eșuează — astfel încât apelanții să nu poată enumera conturile sondând acest endpoint.

Autentificare: Public — nu necesită autentificare.

Corpul cererii

CâmpTipObligatoriuNote
emailstringdaAdresă de email validă.

Răspuns 200

{
  "message": "If an account with that email exists, a password reset link has been sent."
}

Același corp de răspuns este returnat indiferent dacă emailul a corespuns sau nu unui utilizator Firebase Auth real; eșecurile din amonte sunt înregistrate pe server, dar suprimate din răspuns.

Erori

  • VALIDATION_ERROR (400) — corpul nu a trecut validarea Zod (câmp email lipsă sau malformat).
  • RATE_LIMIT_EXCEEDED (429) — fereastra limitei de rată pentru autentificare a fost atinsă.
Dacă serverul API nu are configurat FIREBASE_WEB_API_KEY, cererea este ignorată în tăcere — clientul primește în continuare plicul 200 de mai sus, dar niciun email nu este expediat. Operatorii ar trebui să verifice jurnalele API pentru FIREBASE_WEB_API_KEY not configured — forgot-password request ignored dacă emailurile de resetare nu ajung niciodată.

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{ "email": "owner@acme.example" }'

POST /api/v1/auth/refresh

Schimbă un jeton de reîmprospătare încă valid pe o pereche nouă de jetoane de acces + reîmprospătare. Handler-ul verifică semnătura JWT, verifică dacă hash-ul SHA-256 al jetonului de reîmprospătare este încă prezent sub organizations/{orgId}/users/{uid}/refreshTokens (adică jetonul nu a fost revocat), apoi rotește jetonul de reîmprospătare: documentul Firestore anterior este șters și un hash nou este stocat. Jetonul de reîmprospătare vechi nu mai poate fi reutilizat după o reîmprospătare reușită.

Autentificare: Public — nu necesită autentificare (jetonul de reîmprospătare din corpul cererii este credențialul).

Corpul cererii

CâmpTipObligatoriuNote
refreshTokenstringdaNu poate fi gol. Jetonul de reîmprospătare de la /login, /register sau de la o reîmprospătare anterioară.

Răspuns 200

{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "eyJhbGciOi..."
}

De reținut că — spre deosebire de /login și /register — acest răspuns nu include userId sau orgId. Noul jeton de reîmprospătare îl înlocuiește pe cel pe care l-ai trimis; persistă-l și aruncă-l pe cel vechi.

Erori

  • VALIDATION_ERROR (400) — corpul nu a trecut validarea Zod (câmp refreshToken lipsă sau gol).
  • UNAUTHORIZED (401) — Invalid or expired refresh token (verificarea semnăturii, a emitentului sau a TTL-ului a eșuat) sau Refresh token has been revoked (hash-ul nu mai este în Firestore — de obicei pentru că /logout l-a șters sau pentru că o reîmprospătare anterioară l-a rotit deja).
  • RATE_LIMIT_EXCEEDED (429) — fereastra limitei de rată pentru autentificare a fost atinsă.

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "eyJhbGciOi..." }'

POST /api/v1/auth/logout

Revocă jetonul de reîmprospătare furnizat ștergând hash-ul SHA-256 corespunzător din organizations/{orgId}/users/{uid}/refreshTokens. Jetonul de acces emis în prezent nu este invalidat — rămâne valid până la expirarea TTL-ului său scurt; bazarea pe deconectare pentru o tăiere imediată a accesului necesită așteptarea acelei ferestre.

Autentificare: JWT din Portal, orice utilizator autentificat.

Corpul cererii

CâmpTipObligatoriuNote
refreshTokenstringdaNu poate fi gol. Jetonul de reîmprospătare de revocat.

Răspuns 200

{ "message": "Logged out successfully" }

Handler-ul returnează 200 chiar și când jetonul de reîmprospătare furnizat nu mai este în Firestore — deconectarea este idempotentă, deci o deconectare duplicată nu generează eroare. Trimiterea unui jeton de reîmprospătare care aparține altui utilizator este permisă de rută, dar inofensivă: doar documentele de sub calea {orgId}/{userId} a apelantului sunt scanate.

Erori

  • VALIDATION_ERROR (400) — corpul nu a trecut validarea Zod.
  • UNAUTHORIZED (401) — JWT din Portal lipsă sau invalid.
  • RATE_LIMIT_EXCEEDED (429) — fereastra limitei de rată pentru autentificare a fost atinsă.

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/auth/logout \
  -H "Authorization: Bearer <portal-jwt>" \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "eyJhbGciOi..." }'

Durata jetoanelor și limite de rată

JetonTTL implicitConfigurat prin
Jeton de acces15 minVariabila de mediu JWT_ACCESS_EXPIRES_IN.
Jeton de reîmprospătare7 zileVariabila de mediu JWT_REFRESH_EXPIRES_IN.

Ambele jetoane sunt JWT-uri semnate (iss: e-bon-api, sub: <userId>). Jetonul de acces folosește JWT_SECRET; jetonul de reîmprospătare folosește un JWT_REFRESH_SECRET separat. Jetoanele de reîmprospătare sunt în plus stocate pe server ca hash-uri SHA-256 ca să poată fi revocate de /logout și rotite de /refresh.

LimitatorMax implicitFereastrăCheieSe aplică la
/api/v1/auth/*3010 minIP clientToate cele cinci rute de mai sus.

Limita pentru autentificare este configurată de AUTH_RATE_LIMIT_MAX și AUTH_RATE_LIMIT_WINDOW_MS. Aceleași antete Retry-After și RateLimit-* documentate la Prezentare API › Limite de rată se aplică.

Fluxul de resetare a parolei

Endpoint-ul /auth/forgot-password nu resetează el însuși parola — doar declanșează emailul. Tot dus-întorsul este:

Clientul apelează POST /api/v1/auth/forgot-password cu emailul utilizatorului

API-ul redirecționează cererea către Firebase Identity Toolkit cu requestType: PASSWORD_RESET. Firebase decide dacă adresa aparține unui cont real; API-ul răspunde întotdeauna 200 cu același mesaj generic indiferent.

Link-ul indică către pagina de resetare găzduită de Firebase (sau către un URL personalizat actionCodeSettings când este configurat în consola Firebase). API-ul e-bon nu este implicat în acest pas.

Firebase verifică codul de acțiune și acceptă o parolă nouă

Odată ce parola nouă este salvată, jetoanele de reîmprospătare ale utilizatorului stocate în Firestore nu sunt revocate automat de Firebase. Pentru a forța deconectarea fiecărei sesiuni existente, utilizatorul (sau un Owner) ar trebui să apeleze explicit POST /api/v1/auth/logout pentru fiecare jeton de reîmprospătare cunoscut sau să aștepte expirarea TTL-ului de 7 zile al jetonului de reîmprospătare.

Utilizatorul se autentifică din nou cu POST /api/v1/auth/login

Noua autentificare emite o pereche proaspătă de jetoane de acces + reîmprospătare și stochează hash-ul jetonului de reîmprospătare pe calea Firestore standard.

Vezi și

  • Autentificare — prezentare conceptuală, formatul cheii API și cele nouă permisiuni.
  • API Utilizatori — odată autentificat, GET /api/v1/users/me returnează profilul autentificat și POST /api/v1/users/me/change-password permite unui utilizator conectat să-și rotească propria parolă.
  • API Organizații și Locații — rute cu JWT din Portal pentru profilul organizației, adresa de facturare, locații și abonații la notificări.
  • Prezentare API — URL de bază, plic de eroare, limite de rată, idempotență, paginare, catalog complet de coduri de eroare HTTP.
  • Referință erori — pași de recuperare per cod pentru fiecare cod de eroare HTTP returnat de API.