Authentication
HTTP APIs (summary)
Section titled “HTTP APIs (summary)”- Admin:
POST /api/v1/auth/admin/login,POST /api/v1/auth/admin/refresh, and related admin-only routes (see Swagger under auth and admin tags). - Vendor:
auth/vendorlogin/refresh, plus/api/v1/auth/vendor/context— JWT-only bootstrap routes so employees can load POS/store context even when RBAC modules are narrow (see comment onVENDOR_AUTH_CONTEXT_APIinapps/vendor-web/src/lib/api.ts). - Customer / shared: other
auth/*controllers as listed in Swagger.
Exact request/response shapes live in OpenAPI — do not treat this page as a schema reference.
SPAs (Axios + Bearer)
Section titled “SPAs (Axios + Bearer)”- Base URL: both
admin-webandvendor-webuseVITE_API_URLwhen set to a non-empty string; inimport.meta.env.DEVwith no URL they default tohttp://localhost:3000; in production builds with an emptyVITE_API_URL, the base is""so Axios uses same-origin relative URLs to/api/v1(matches Docker build). SeegetApiBaseUrl()in each app’ssrc/lib/api.ts. - Access token: request interceptor sets
Authorization: Bearer <access>from the Zustand auth store. - Refresh: on 401, clients call
POST /api/v1/auth/admin/refreshorPOST /api/v1/auth/vendor/refreshwith JSON body{ "refresh_token": "<refresh>" }, then persist the new pair. A single-flight promise (refreshAccessPromise) avoids concurrent refresh stampedes.
Token lifetimes (env-driven)
Section titled “Token lifetimes (env-driven)”Defaults in apps/backend/.env.example (override in each environment):
| Variable | Default | Role |
|---|---|---|
JWT_ACCESS_EXPIRES | 15m | Short-lived access JWT |
JWT_REFRESH_EXPIRES | 7d | Refresh token lifetime |
Secrets: JWT_ACCESS_SECRET, JWT_REFRESH_SECRET — use long random values in production.
Rotation is refresh-on-401 (no sliding refresh on a timer in the snippet above). OTP and password-reset shapes are defined in OpenAPI under the auth tags.
Password reset (admin & vendor)
Section titled “Password reset (admin & vendor)”Three-step flow (same pattern for admin and vendor); base path is /api/v1 plus auth/admin or auth/vendor:
| Step | Method & path | Body (JSON) | Notes |
|---|---|---|---|
| 1. Request code | POST …/password-reset | { "email", "admin_type" | "vendor_type" } | Response is always generic success (anti–email-enumeration). If the account exists, a 6-digit OTP is stored server-side. |
| 2. Verify OTP | POST …/password-reset/verify | { "email", "admin_type" | "vendor_type", "code" } | Returns { "success", "reset_token" } — short-lived JWT signed with JWT_ACCESS_SECRET. |
| 3. Complete | POST …/password-reset/complete | { "reset_token", "password" } (password min 8) | Updates the hashed password for that user. |
Operational notes
- OTP storage is in-memory in
PasswordResetOtpService(single-instance / dev friendly). For multiple API instances, replace with Redis (or similar). PASSWORD_RESET_LOG_OTP: set to1to always log the plaintext OTP;0to disable. If unset, OTP is logged whenNODE_ENV !== 'production'(see service implementation).- SPAs: call these endpoints with
skipAuthHandler: trueon the Axios config so a 401 on a bad OTP does not trigger the refresh-token flow.
JWT claims (behavioral)
Section titled “JWT claims (behavioral)”- Admin: payload includes
modules(see Admin panel overview); backend guards enforce module rules on services. - Vendor: profile after login includes
vendor_type,modules, and optionalmodule_permissionsfor employees; owners bypass module checks in the SPA.
Related
Section titled “Related”- Frontend applications — env vars and stack.