feat: implement identity and security features

- Added account types and privileged account types constants.
- Created admin user controller for updating user security fields.
- Developed role assignment controller for managing user roles.
- Implemented validation middleware for request schemas.
- Defined user role and auth session models for database interactions.
- Created services for authentication, email notifications, and OTP handling.
- Developed OAuth service for Google and Apple authentication.
- Added JWT utility functions for token generation and verification.
- Implemented comprehensive tests for authentication, session management, and password policies.
- Created migration for updating user schema and adding new tables for auth sessions and user identities.
This commit is contained in:
Sathira Sri Sathara
2026-09-03 13:56:18 +05:30
parent 267e80e2ec
commit 9d3d431416
54 changed files with 1389 additions and 1076 deletions
+95
View File
@@ -0,0 +1,95 @@
# ZUMRI Authentication API
All endpoints are available under both `/api/auth` and `/api/v1/auth`; new clients should use `/api/v1`. JSON errors use the Phase 0 standard error envelope. Examples contain placeholders only.
## Account states
- `PENDING_VERIFICATION`: registration exists but password/OAuth session creation is denied.
- `ACTIVE`: authentication and refresh are permitted.
- `SUSPENDED` and `DEACTIVATED`: login, refresh, and access-token middleware are denied.
## Register a customer
`POST /api/v1/auth/register`
```json
{"firstName":"Asha","lastName":"Perera","email":"asha@example.com","password":"Strong!1234x","phoneNumber":"+94000000000","address":"Customer-provided value"}
```
Public registration always creates `customer`; supplied privileged account fields are rejected/stripped by strict validation. Business and privileged registration are not public Phase 1 flows.
## Verify or resend email verification
- `POST /api/v1/auth/verify-email` — `{"token":"verification-token-from-email"}`
- `POST /api/v1/auth/resend-verification` — `{"email":"asha@example.com"}`
Tokens are random, stored only by hash in Redis, expire, and are single-use. Resend invalidates the previous token. Resend returns a generic response.
## Password plus OTP login
`POST /api/v1/auth/login`
```json
{"email":"asha@example.com","password":"Strong!1234x","rememberMe":true,"clientType":"WEB","deviceName":"Personal laptop"}
```
Successful credential verification returns HTTP 202 and a `challengeId`; it does not create a session. A hashed six-digit OTP challenge is stored in Redis for the configured TTL and attempt limit.
`POST /api/v1/auth/verify-otp`
```json
{"challengeId":"00000000-0000-4000-8000-000000000000","otp":"000000","clientType":"WEB"}
```
Successful verification consumes the challenge, creates a durable session, and issues tokens. `POST /api/v1/auth/req-otp` remains an alias for login challenge creation. The historical endpoint that submitted email+OTP to `/login` is intentionally superseded by challenge IDs.
## Token transport
`clientType: WEB` sets `access_token` and `refresh_token` as HttpOnly cookies. Production cookies use `Secure` and `SameSite=None` for the intended cross-subdomain frontend/API deployment. The refresh cookie is restricted to `/api`. The response includes the access token for current compatibility but never includes the web refresh token.
`clientType: MOBILE` returns access and refresh tokens in JSON and does not depend on cookies. Mobile clients must store the refresh token in operating-system secure storage. Access tokens remain short-lived regardless of Remember Me.
## Refresh and rotation
`POST /api/v1/auth/refresh`
Web request body may be `{}`; mobile sends `{"clientType":"MOBILE","refreshToken":"opaque-token"}`. Every successful call revokes/replaces the previous session row and returns a new token pair. Replaying a rotated token revokes its entire token family. Only SHA-256 refresh hashes are persisted.
## Current identity
`GET /api/v1/auth/me` requires the access cookie or `Authorization: Bearer <access-token>`. It returns identity fields, account state, verification status, profile, and effective permissions. It omits password, token version, session hashes, and OAuth internals.
## Logout
- `POST /api/v1/auth/logout` revokes the session identified by refresh or access token and is idempotent.
- `POST /api/v1/auth/logout-all` requires authentication, increments `tokenVersion`, revokes every active session, and clears cookies.
## Password recovery and change
- `POST /api/v1/auth/forgot-password` — `{"email":"asha@example.com"}`; response is generic.
- `POST /api/v1/auth/reset-password` — token, `newPassword`, `confirmPassword`.
- `POST /api/v1/auth/change-password` — authenticated; `currentPassword`, `newPassword`, `confirmPassword`.
Reset tokens are random, hash-only Redis records and atomically consumed. Reset/change enforce the same password policy, increment token version, and revoke all sessions. The user must log in again.
## Google and Apple
- `POST /api/v1/auth/google`
- `POST /api/v1/auth/apple`
```json
{"idToken":"provider-signed-id-token","rememberMe":false,"clientType":"WEB"}
```
Google verification validates signature/audience/issuer/expiry through Google's verifier and requires verified email. Apple validates the provider JWKS signature, issuer, audience, expiry, and stable subject. Identities persist `(provider, provider_subject)` uniquely; provider tokens are discarded. Verified email auto-linking is permitted only for customer accounts. Privileged and rider accounts are never automatically linked by email.
## Administrative and rider compatibility
- `POST /api/v1/admin/auth/login` and `/verify-otp` use the shared challenge/session flow but only accept privileged account types.
- `POST /api/v1/rider/auth/login` and `/verify-otp` use the same system and only accept rider accounts.
No separate token implementation exists for these actors.
## Password policy
Minimum 12 characters with uppercase, lowercase, a symbol, and at least four numeric characters. Registration, reset, and change use the same Zod schema.
+14
View File
@@ -373,3 +373,17 @@ New `/health/live` and `/health/ready` routes provide real liveness/readiness be
Deployment additions include a hardened Node 22/Chromium/non-root Dockerfile with healthcheck, API/worker/MySQL/Redis Compose configuration, an Nginx reverse-proxy example, and a Gitea Actions CI baseline. Jest/Supertest tests now cover environment validation, liveness/readiness, errors/404, protected routes, Bull Board denial, and request correlation. The first test run exposed incompatible ESM-only `uuid@13`; it was safely pinned to CommonJS-compatible v11. `nodemon` moved to devDependencies.
Remaining foundation-adjacent work is intentionally deferred: production database baseline verification, distributed cron locking, full queue policy/idempotency, stronger documentation sessions, permission-router/role integration, and the Phase 1 authentication/ownership/security issues. The original audit above remains the historical baseline; statements such as “missing tests/Helmet/migrations” are superseded by this update and `Documentation/PHASE_0_FOUNDATION_STABILIZATION.md`.
## Phase 1 Completion Update
**Date:** 2026-09-03
**Authentication completion:** approximately **91%**.
**Revised Day 2 completion:** approximately **90%**.
Phase 1 replaced process-memory OTP and refresh sessions with Redis hash-only login challenges and durable MySQL auth-session families. OTP now uses `crypto.randomInt`, challenge UUIDs, TTL, atomic verification, bounded attempts, and no plaintext logging. Refresh tokens are opaque random values stored only by SHA-256 hash; row-locked transaction rotation detects replay and revokes the family. Access JWTs are short-lived, issuer/audience/algorithm constrained, and bound to `sub`, live session ID, and User token version. Middleware enforces current account state and session revocation.
New `auth_sessions`, `user_identities`, and `user_roles` models/tables support devices, Remember Me, Google/Apple stable subjects, and configurable roles. The User security migration adds token version/last login, expands canonical account types, and adds RBAC unique indexes. Permission routes are mounted behind SUPER_ADMIN, permission resolution now uses UserRole, and cache invalidation covers assignments/grants. Customer self-service update is allowlisted and ID-based mutation is privileged, closing the audited identity IDOR/mass-assignment path.
Auth endpoints now cover customer registration, verification/resend, password-to-OTP challenge, OTP completion, refresh rotation, current/all-device logout, forgot/reset/change password, Google, Apple, `/me`, and shared admin/rider compatibility flows. Both `/api` and `/api/v1` remain. Security-focused unit/integration tests were added without real providers/email/database/Redis.
Remaining identity work is operational: validate/deduplicate deployed RBAC data before migration, run staging MySQL/Redis concurrency tests, seed initial privileged assignments securely, configure provider audiences/mail, and design manual privileged OAuth linking and email change if required. Module 01 is now approximately 91%; migration/staging validation prevents claiming 100% production completion.
@@ -41,7 +41,7 @@ Tests can import `app.js` without opening a TCP port. Startup failures prevent t
## Environment Variables
Required for API/worker startup: `NODE_ENV`, `PORT`, `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `JWT_SECRET`, `REFRESH_TOKEN_SECRET`, `REDIS_HOST`, `REDIS_PORT`, and `FRONTEND_URL`. JWT secrets must each be at least 32 characters. `REDIS_PASSWORD` is optional at schema level for deployments without Redis authentication.
Required for API/worker startup: `NODE_ENV`, `PORT`, `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `JWT_SECRET`, `REDIS_HOST`, `REDIS_PORT`, and `FRONTEND_URL`. The JWT secret must contain at least 32 characters. Phase 1 replaced refresh JWTs with opaque random refresh tokens, so no refresh-token signing secret is required. `REDIS_PASSWORD` is optional at schema level for deployments without Redis authentication.
Runtime controls: `TRUST_PROXY` (numeric trusted proxy hop count; keep `0` when directly exposed), `JSON_BODY_LIMIT`, `API_RATE_LIMIT_WINDOW_MS`, `API_RATE_LIMIT_MAX`, `SENSITIVE_RATE_LIMIT_WINDOW_MS`, `SENSITIVE_RATE_LIMIT_MAX`, `RUN_CRON`, `CACHE`, and `SHUTDOWN_TIMEOUT_MS`.
@@ -0,0 +1,119 @@
# ZUMRI Phase 1 Identity and Authorization
## Objective
Complete the identity security boundary without beginning commerce modules: verified registration, password+OTP authentication, durable revocable sessions, social identity verification, account-state enforcement, repaired configurable RBAC, and ownership-safe self service.
## Existing Components Reused
User/Profile and customer-extension models, Sequelize registration, Redis client, bcrypt helper, email templates/transport, hashed verification/reset concepts, activity queue, permission/role/grant models, permission cache, Phase 0 error/rate-limit/request-ID middleware, dual API mounting, tests, and migrations were extended rather than replaced.
## Identity Model
`User` is the account and contains broad `accountType`, state, password hash (nullable for social-only customers), verification/password timestamps, `tokenVersion`, and `lastLoginAt`. `UserIdentity` maps Google/Apple stable subjects to User. `AuthSession` persists refresh credentials/device context and rotation state. `UserRole` assigns configurable Roles independently from account type.
## Account Types
Canonical application constants map to persisted lowercase values: `SUPER_ADMIN=superadmin`, `ADMIN=admin`, `MANAGER=manager`, `CUSTOMER=customer`, `BUSINESS_CUSTOMER=business_customer`, `RIDER=rider`, `SUPPORT_AGENT=support_agent`. Existing lowercase values remain valid.
## Role vs Account Type
Account type is a broad trusted identity category used for hard security boundaries. Role is configurable authorization grouping. Users may have multiple roles through `UserRole`; effective permissions are the union of role grants and direct additive `UserPermission` grants. This removes dependence on undeclared `User.roleID`.
## Session Architecture
```text
Password -> OTP Challenge -> Verify OTP -> AuthSession
-> Access JWT + opaque Refresh Token
-> transactional rotation -> logout/revocation
```
Sessions support multiple devices, user-agent/IP/device metadata, Remember Me, token families, last use, expiry, revocation reason, and replacement linkage. Raw refresh tokens exist only at issuance/transport.
## Access Token
HS256 JWTs are short-lived and contain `sub`, `sid`, and `tokenVersion`, plus `iss`, `aud`, `iat`, and `exp`. Middleware enforces algorithm, signature, issuer, audience, expiry, live User state/token version, and live non-revoked session state. It loads permissions server-side rather than embedding them.
## Refresh Token Rotation
Refresh tokens are opaque `session-id.random-secret` values. The database stores only SHA-256 hashes. Rotation locks the current session row and User in a transaction, creates a same-family replacement, and revokes/links the old row.
## Reuse Detection
Presentation of a revoked/replaced or hash-mismatched known session token revokes all active members of that token family and returns the same invalid-session boundary. Row locks ensure two concurrent refresh calls cannot both succeed.
## Remember Me
Remember Me changes only refresh-session lifetime: `REFRESH_TOKEN_TTL_DAYS` versus `REMEMBER_ME_REFRESH_TOKEN_TTL_DAYS`. Access lifetime remains `ACCESS_TOKEN_TTL`.
## Account Status Enforcement
Only `ACTIVE` can finish login, refresh, or use protected endpoints. Pending, suspended, and deactivated accounts are rejected using current database state, not stale claims. Password/security administration increments token version and revokes sessions.
## Password Policy
One Zod policy requires at least 12 characters, uppercase, lowercase, a symbol, and four digits. It is used by registration, reset, and change-password flows. Reset/change require confirmation and reject reuse of the current password.
## Email Verification
Verification tokens are cryptographically random and hash-only in Redis. They expire, are consumed atomically, and activate the account. Per-user pointers let resend invalidate an earlier token. Resend responses are generic and rate limited.
## Password Recovery
Forgot-password normalizes/validates email and returns a generic response. Reset tokens are random, stored hashed with TTL, atomically consumed using Redis `GETDEL`, and never logged. Successful reset updates the hash/timestamp/tokenVersion and revokes every session.
## Google Authentication
The backend verifies Google ID tokens against configured audience and uses Google's `sub`; verified email is required. Provider access tokens are not stored. Automated tests mock Google's verifier.
## Apple Authentication
The backend obtains/caches Apple's JWKS, selects the signed key, and verifies RS256 signature, Apple issuer, configured audience, expiry, and `sub`. First-login email is used only when verified. Tests use a locally signed RSA token and mocked JWKS response.
## OAuth Account Linking Rules
Existing provider subject wins. Otherwise a strongly verified provider email may link/create a customer. Email-only automatic linking is denied for super admins, admins, managers, support agents, and riders. Provider subject is unique and provider tokens/secrets are not persisted. Manual privileged linking remains deferred.
## Role and Permission Architecture
`/api/v1/permissions` is now mounted and default-denied to SUPER_ADMIN at router level. Existing CRUD is retained behind that boundary. Role names and role/user grant pairs have unique constraints. UserRole pairs are unique. Model hooks and assignment controllers invalidate affected permission caches.
## Ownership Authorization
`GET/PATCH /user/me` provides self service. Self update allowlists only first and last name; account type/status/roles/security fields are rejected. ID-based legacy mutation is restricted to SUPER_ADMIN, and no arbitrary ID read route is exposed. Profile image access uses reusable self-or-admin ownership middleware.
## New Database Tables
- `auth_sessions`
- `user_identities`
- `user_roles`
User adds `tokenVersion` and `lastLoginAt`; password becomes nullable for verified social-only users; the account-type ENUM expands to all canonical types.
## New Migrations
`20260903010000-phase-1-identity-security.js` is forward-only relative to the Phase 0 baseline and was not executed. Before applying to an existing database, diagnose duplicate `roles.roleName`, `(role_id,permission_id)`, and `(user_id,permission_id)` rows because unique indexes intentionally fail on dirty data. Back up and test a restored database first.
## API Endpoints
Auth: register, login challenge, verify OTP, refresh, logout, logout-all, forgot/reset/change password, verify/resend email, Google, Apple, and me. Thin shared-flow admin and rider login routes exist. Admin User status/account-type endpoints and protected permission/UserRole endpoints are mounted. See `Documentation/API_AUTHENTICATION.md`.
## Security Controls
Hash-only OTP/reset/verification/refresh persistence; cryptographic random generation; bounded OTP attempts; single-use challenges; short access TTL; durable revocation; replay-family revocation; DB row locks; account-state/token-version checks; strict Zod bodies; generic enumeration responses; provider signature/audience checks; customer-only public registration; field allowlists; ownership checks; and no token/OTP credential logging.
## Tests
Unit coverage includes password policy, JWT claims/wrong issuer-audience/expiry/malformed input, OTP format/hash-only persistence/failure states, refresh hash-only persistence/rotation/replay, account-status/password-login behavior, and Google/Apple verification. Integration coverage preserves Phase 0 health/error behavior and checks RBAC denial, self mass-assignment, other-user mutation, suspended access, and Bull Board admin access. External DB/Redis/email/OAuth services are mocked.
## Legacy Compatibility
Both `/api` and `/api/v1` remain. `/auth/req-otp` aliases new login challenge creation; `/profile` password endpoints delegate to the same hardened controllers. The old email+OTP `/auth/login` second step is intentionally replaced by `/verify-otp` with challenge IDs because the old email-keyed flow could not meet security requirements.
## Remaining Known Issues
Manual privileged OAuth linking and secure email-change confirmation are deferred. Full email queue/delivery tracking remains Phase 2 infrastructure work. Existing business registration/account approval is not part of this phase. Role/grant uniqueness migration requires deployed-data diagnostics. The baseline test suite mocks MySQL/Redis; staging integration tests must run after migration review. Existing non-auth legacy routes may still contain account-name/ownership debt outside Phase 1 scope.
## Phase 2 Prerequisites
Review and run both migrations on a restored environment, configure mail plus Google/Apple client audiences where those flows are enabled, run staging MySQL/Redis integration tests, and seed the initial SUPER_ADMIN/roles/permissions through a controlled operational process. Once complete, identity is ready for the next non-commerce phase requested by the development roadmap.