Identity & Authentication (SRS-AUTH)
Purpose
Owns everything about who a user is: registration, login, verification, credentials, sessions and devices, profile data, the RBAC model, and account lifecycle (suspension, deletion, data export). It does not own vendor business verification (that is Vendor Onboarding) or per-module permissions logic beyond exposing roles/permissions for other modules to enforce.
One identity system serves all three user types — Customer, Vendor (staff of a vendor business), Admin — as a shared engine. A single person MAY hold both a customer account and a vendor role under the same email.
Actors
| Actor |
Description |
| Guest |
Unauthenticated visitor (can browse marketplace, cannot transact). |
| Customer |
Registered end user planning events. |
| Vendor user |
A person attached to a vendor business (Owner role at MVP; more roles at Scale). |
| Admin |
Yam3at staff with an admin role (Super Admin, Operations, Support, Finance, Content Moderator). |
| System |
Scheduled jobs (token cleanup, deletion grace expiry). |
User stories
- As a customer, I want to sign up with my phone number and an OTP, so that I can start planning without remembering another password.
- As a customer, I want to log in with Google or Apple, so that registration takes seconds.
- As a vendor owner, I want a separate vendor registration path, so that my business account is distinct from my personal customer account.
- As a user, I want to see and revoke my active sessions and devices, so that I can secure my account if my phone is lost.
- As a user, I want to reset a forgotten password via email or SMS, so that I am never locked out.
- As an admin, I want to suspend an account and force-logout all its sessions, so that abuse stops immediately.
- As a user, I want to export my data and delete my account, so that I control my personal information.
- As a security-conscious user, I want to enable 2FA, so that a stolen password alone cannot compromise my account. (Scale)
Functional requirements
Registration
| ID |
Requirement |
Phase |
| SRS-AUTH-001 |
The system MUST support customer registration with email + password (fields: first_name, last_name, email, password, optional phone, preferred_locale ar/en, mandatory acceptance of Terms & Privacy with version recorded). |
MVP |
| SRS-AUTH-002 |
The system MUST support customer registration/login with phone OTP only (passwordless): user enters a Kuwait mobile number (+965, 8 digits, starting 5/6/9), receives a 6-digit SMS OTP, and on first successful verification an account is created with phone_verified_at set. |
MVP |
| SRS-AUTH-003 |
The system MUST support Google Sign-In and Sign in with Apple (OIDC). On first social login, an account is created with the provider's verified email; email_verified_at is set immediately when the provider asserts a verified email. Apple private-relay emails MUST be accepted. |
MVP |
| SRS-AUTH-004 |
If a social login's email matches an existing email/password account, the system MUST link the provider to that account only after the user re-authenticates with their password (no silent account takeover). |
MVP |
| SRS-AUTH-005 |
Vendor registration MUST start with the same identity registration (any method above) and then branch into the vendor business application (SRS-VOS-001). Identity creation and business onboarding are separate steps with separate states. |
MVP |
| SRS-AUTH-006 |
Admin accounts MUST NOT be self-registered. They are created by a Super Admin only, require email verification, and password login (2FA mandatory for admins at Scale). |
MVP |
| SRS-AUTH-007 |
Registration MUST be idempotent per identifier: attempting to register an existing email/phone returns a generic "check your inbox/SMS" style response without disclosing whether the account exists (anti-enumeration). |
MVP |
Verification
| ID |
Requirement |
Phase |
| SRS-AUTH-010 |
Email verification: on email registration or email change, send a signed verification link valid 24 h, single-use. Unverified emails may log in but cannot transact (create RFQs, book, pay, publish listings). |
MVP |
| SRS-AUTH-011 |
Phone verification: 6-digit numeric OTP via SMS, valid 5 min, max 5 attempts per code, resend cooldown 60 s, max 5 sends per phone per hour and 10 per day. Codes are stored hashed. |
MVP |
| SRS-AUTH-012 |
A verified phone number is mandatory before a customer can submit an RFQ or booking, and before a vendor application can be submitted. |
MVP |
| SRS-AUTH-013 |
Changing email or phone requires verifying the new identifier before it replaces the old one; the old identifier receives a notification with a revert link valid 72 h. |
MVP |
Login & credentials
| ID |
Requirement |
Phase |
| SRS-AUTH-020 |
Password login with email; generic error "invalid credentials" on failure (no field-level disclosure). |
MVP |
| SRS-AUTH-021 |
Brute-force protection: after 5 failed attempts per account or per IP within 15 min, apply exponential backoff and CAPTCHA; after 10, lock login for 15 min and notify the account email. |
MVP |
| SRS-AUTH-022 |
Forgot password: email link (signed token, 60 min validity, single-use) or, if the account has a verified phone, SMS OTP path. Successful reset revokes all sessions except the one completing the reset, and sends a notification. |
MVP |
| SRS-AUTH-023 |
Password change (while logged in) requires the current password and revokes all other sessions. |
MVP |
| SRS-AUTH-024 |
Passwords hashed with Argon2id. Compromised-password screening against a k-anonymity breach corpus on set/change. |
MVP |
| SRS-AUTH-025 |
Mobile apps MUST offer biometric unlock (Face ID / fingerprint) as a device-local convenience wrapper around the refresh token — biometrics never replace server-side authentication. |
MVP |
| SRS-AUTH-026 |
2FA (TOTP app + SMS fallback): opt-in for customers/vendors, enforced for admins. Recovery codes (10, single-use) issued at enrollment. |
Scale |
Sessions & devices
| ID |
Requirement |
Phase |
| SRS-AUTH-030 |
Token model: short-lived access token (15 min) + rotating refresh token (60 days absolute, revocable). Web uses httpOnly secure cookies; mobile uses secure storage. Refresh token reuse detection revokes the whole token family. |
MVP |
| SRS-AUTH-031 |
Users MUST be able to list active sessions/devices (device name, platform, app version, IP-derived city, last active) and revoke any single session or all others. |
MVP |
| SRS-AUTH-032 |
Admin suspension of an account MUST revoke all its sessions and push tokens within 60 s. |
MVP |
| SRS-AUTH-033 |
Each device registers its FCM push token bound to the session; revoking the session detaches the token. |
MVP |
Profile management
| ID |
Requirement |
Phase |
| SRS-AUTH-040 |
Customer profile: first_name, last_name, email, phone, avatar (image ≤ 5 MB, JPEG/PNG/WebP, stored at 512×512), preferred_locale (ar/en), country (default KW), notification_preferences per channel per category. Civil ID is not collected from customers. |
MVP |
| SRS-AUTH-041 |
Locale preference controls UI language, notification language, and digit formatting; changeable at any time with immediate effect. |
MVP |
| SRS-AUTH-042 |
Vendor users see a profile switcher when they hold both a customer profile and one or more vendor memberships; the active context is explicit in the UI and in the JWT (active_context). |
MVP |
Roles & permissions (RBAC)
| ID |
Requirement |
Phase |
| SRS-AUTH-050 |
RBAC model: users ⇄ roles ⇄ permissions. Permissions are fine-grained strings (rfq.create, listing.publish, vendor.approve, refund.issue, …). Roles are named permission bundles. Enforcement is server-side on every endpoint; UI hiding is cosmetic only. |
MVP |
| SRS-AUTH-051 |
Built-in roles at MVP: customer, vendor_owner, admin_super, admin_operations, admin_support, admin_finance, admin_moderator. Vendor team roles (vendor_manager, vendor_staff) are Scale (SRS-VOS-060). |
MVP |
| SRS-AUTH-052 |
Vendor permissions are scoped to a vendor_id (membership table), and at Scale additionally to branch_id. A vendor user can never act on another vendor's data. |
MVP |
| SRS-AUTH-053 |
All role/permission grants and revocations are audit-logged with actor and reason. Super Admin role changes require a second Super Admin's confirmation. |
MVP |
Account lifecycle, deletion & export
| ID |
Requirement |
Phase |
| SRS-AUTH-060 |
Account statuses: pending_verification, active, suspended, deletion_requested, anonymized (see state machine). |
MVP |
| SRS-AUTH-061 |
Self-service account deletion: re-authentication required; a 30-day grace period starts during which login cancels the deletion. After grace expiry, personal data is anonymized (name → "Deleted user", email/phone/avatar purged) while transactional records (bookings, payments, invoices, reviews content) are retained for legal/financial integrity, unlinked from identity. |
MVP |
| SRS-AUTH-062 |
Deletion of a vendor_owner account is blocked while the vendor business has active bookings or an active paid subscription; the user is told exactly which blockers exist. |
MVP |
| SRS-AUTH-063 |
Data export: on request, generate a machine-readable archive (JSON + attached files) of profile, events, RFQs, quotes, bookings, invoices, messages, and reviews. Delivered via signed download link (72 h validity) within ≤ 72 h of request; rate-limited to 1 export per 7 days. |
MVP |
| SRS-AUTH-064 |
Suspension (admin action, reason mandatory) blocks login and API access but preserves all data; the user sees a dedicated "account suspended — contact support" screen. |
MVP |
Business rules
- One email and one phone number map to at most one identity each. Cross-type reuse (customer + vendor) happens through roles on one identity, not duplicate accounts.
- Unverified accounts can browse and save favorites, but every money- or communication-adjacent action requires the relevant verification (SRS-AUTH-010/012).
- Social-only accounts (no password) set a password via the forgot-password flow if they want email+password login.
- OTP SMS sender ID is "Yam3at"; message templates exist in AR and EN and follow the account's
preferred_locale (default AR for +965).
- All auth events (register, login success/failure, logout, password change/reset, verification, session revoke, 2FA change, suspension, deletion request) are audit-logged.
Validation rules
| Field |
Rule |
email |
RFC 5322 syntactically valid, ≤ 254 chars, lowercased for uniqueness, disposable-domain blocklist. |
password |
8–128 chars, must not appear in breach corpus, no composition rules beyond length (NIST 800-63B), Unicode allowed. |
phone |
E.164. Kuwait: ^\+965[569]\d{7}$. Other GCC country codes accepted but marked unsupported-market until expansion. |
first_name, last_name |
1–50 chars each, Arabic and Latin letters, spaces, hyphen, apostrophe. |
| OTP code |
Exactly 6 digits; constant-time comparison of hash. |
avatar |
JPEG/PNG/WebP, ≤ 5 MB, min 200×200 px; EXIF stripped. |
preferred_locale |
ar or en. |
| Terms acceptance |
Boolean must be true; terms_version recorded. |
Permissions matrix
| Action |
Guest |
Customer |
Vendor user |
Admin (Support) |
Admin (Super) |
| Register / login |
✔ |
— |
— |
— |
— |
| Manage own profile |
— |
✔ |
✔ |
✔ |
✔ |
| List/revoke own sessions |
— |
✔ |
✔ |
✔ |
✔ |
| View any user's profile (PII) |
— |
— |
— |
✔ (read) |
✔ |
| Suspend / reinstate account |
— |
— |
— |
— |
✔ |
| Create admin users, assign roles |
— |
— |
— |
— |
✔ |
| Request own deletion / export |
— |
✔ |
✔ |
— |
— |
| Execute deletion on behalf of user |
— |
— |
— |
✔ (verified request) |
✔ |
Status / state machine
stateDiagram-v2
[*] --> PendingVerification: register
PendingVerification --> Active: verify email or phone
Active --> Suspended: admin suspends
Suspended --> Active: admin reinstates
Active --> DeletionRequested: user requests deletion
DeletionRequested --> Active: user logs in / cancels (within 30 days)
DeletionRequested --> Anonymized: grace period expires (system job)
Suspended --> Anonymized: admin-executed verified erasure
Anonymized --> [*]
Edge cases
- Apple private relay + later Google login: emails differ → two identities. Resolution: manual account-merge via support (Scale: self-service merge). Never auto-merge on name similarity.
- Phone number recycled by telecom operator: new owner OTP-registers a number attached to an old account → if the old account has been inactive ≥ 12 months, prompt "is this a new account?" flow that detaches the number after email-based challenge to the old account; otherwise block and route to support.
- OTP SMS not delivered: after 2 failed sends, offer voice-call OTP fallback (Scale) and email-based path if the account has a verified email.
- Clock skew on mobile: token expiry validated server-side only; client uses server time from response headers.
- User deletes account with an upcoming confirmed booking: blocked with an explicit list of blocking bookings; must cancel per policy first.
- Concurrent password reset and login: completing a reset invalidates all outstanding reset tokens and other sessions atomically.
- Vendor owner is the vendor's only user and requests deletion: see SRS-AUTH-062; support-assisted business transfer is the escape hatch.
Empty / loading / error states
| Screen |
Empty |
Loading |
Error |
| Login |
n/a |
Button spinner, inputs disabled, no double-submit |
Generic invalid-credentials message; lockout message shows remaining minutes |
| OTP entry |
6 empty boxes, auto-focus first |
Auto-verify on 6th digit with inline spinner |
"Code incorrect or expired" + resend countdown; attempts remaining after 3rd failure |
| Sessions list |
"This is your only active session" |
Skeleton rows ×3 |
Retry banner; revoke failures roll back optimistic UI |
| Profile |
Never empty (registration data) |
Skeleton form |
Field-level validation errors in AR/EN; save is atomic |
| Data export |
"No export requested yet" + CTA |
"Preparing your archive…" progress state |
Failure notification with retry; support link |
Acceptance criteria
Data entities touched
users, user_auth_providers, user_sessions, user_devices (push tokens), otp_codes, password_resets, roles, permissions, role_user (with vendor_id scope), account_deletion_requests, data_export_requests, audit_logs.
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/otp/send
POST /api/v1/auth/otp/verify
POST /api/v1/auth/social/{provider}/callback (google | apple)
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
POST /api/v1/auth/password/forgot
POST /api/v1/auth/password/reset
PUT /api/v1/auth/password
POST /api/v1/auth/email/verify
POST /api/v1/auth/phone/verify
GET /api/v1/me
PATCH /api/v1/me
GET /api/v1/me/sessions
DELETE /api/v1/me/sessions/{sessionId}
DELETE /api/v1/me/sessions (all others)
POST /api/v1/me/deletion-request
DELETE /api/v1/me/deletion-request
POST /api/v1/me/export
GET /api/v1/admin/users
PATCH /api/v1/admin/users/{userId}/status
POST /api/v1/admin/users/{userId}/roles