Temă
Authentication
Two separate kinds of account, two cookies:
| Customers (site) | Staff (Backoffice + CRM) | |
|---|---|---|
| Cookie | taraba_session | taraba_staff (shared by both apps → single sign-on) |
| Lifetime | 1 day, or 30 days with "remember me"; sliding | 12 hours; sliding |
| Login | email + password | email + password, then a 6-digit TOTP code (mandatory) |
| GraphQL | register, login, logout, viewer, verifyEmail, resendVerificationEmail, requestPasswordReset, resetPassword, changePassword | staffLogin, verifyStaffTotp, staffLogout, staffViewer |
How it works
- Server-side sessions in the database (
auth_sessions). The cookie holds a random 256-bit token (HttpOnly,SameSite=Lax,SecurewhenCOOKIE_SECURE=true). Only the token's SHA-256 is stored, so a database dump cannot be replayed. Logging out, suspending an account or changing a password takes effect immediately. - Password hashing (
app/Modules/Auth/Domain/Passwords.php): bcrypt, cost 12, over the Base64 SHA-256 of the password. bcrypt alone reads only 72 bytes, so long passwords starting the same would be equivalent; the digest keeps all 128 allowed characters significant. Unknown emails are checked against a dummy hash so response times do not reveal which accounts exist; login errors are the same for both cases. - Throttling: 5 failed logins on an email lock it for 15 minutes (
TOO_MANY_ATTEMPTS, withretryAfterMinutes). - Two-step for staff (RFC 6238 TOTP) (
Totp.php), verified against the RFC test vectors and against the previous API's codes. ±30 s clock drift accepted; a code works only once (totp_step_claims, taken in a locked transaction, so two simultaneous requests cannot both use it); 5 wrong codes end the challenge. A member without two-step getsTOTP_SETUP_REQUIREDwith a secret and anotpauth://URI (QR code) and enables it with the first valid code. "Remember this device" stores a separate 30-day device cookie. - Email links (verification 24 h, password reset 60 min) carry single-use tokens (
one_time_tokens, stored hashed). A password reset ends every session of the account and sends a notification email. - Registration creates the account, the CRM profile, the marketing consent proof and the acceptance of the legal documents in one transaction.
- Authorisation: every staff operation starts with
$this->current->requireStaff(Permission::RefundOrders)(or the permission it needs). Role → permissions lives inapp/Modules/Auth/Domain/Roles.php(single source of truth); the role is read from the database on each request, so a role change applies at once. - Audit: staff logins and two-step activation are written to
audit_logs.
Error codes (extensions.code)
UNAUTHENTICATED, FORBIDDEN, VALIDATION_FAILED (with fields), INVALID_CREDENTIALS, ACCOUNT_SUSPENDED, ACCOUNT_DISABLED, TOO_MANY_ATTEMPTS, INVALID_TOTP (with attemptsLeft), CHALLENGE_EXPIRED, INVALID_TOKEN, EMAIL_TAKEN, WEAK_PASSWORD (with rules). In production any other error is reported as INTERNAL_SERVER_ERROR without details.
Demo logins
Password Taraba2026! for everyone. Staff TOTP secret KRAXEQSBJ5EUKQ2PKJCVKRCF (add it to Google Authenticator, Microsoft Authenticator, 1Password…). Sorin Dragomir and Iulian Costache have no two-step yet: Sorin is asked to set it up at first login; Iulian is still "invited" and cannot log in.
Tests
node tests/e2e/run-all.mjs --only auth — 40 end-to-end checks (cookies, throttling, email links through Mailpit, TOTP setup, replay, trusted device, audit), on fresh demo data. pnpm api:test covers TOTP, the password policy and password hashing directly.