diff --git a/.env-development b/.env-development index 31a60e9..8ad8bc8 100644 --- a/.env-development +++ b/.env-development @@ -1,10 +1,13 @@ # ═══════════════════════════════════════════════════════════════════════════════ # $APP_NAME — Environment Variables Template (LOCAL DEVELOPMENT) -# Copy to .env and fill in the CHANGE_ME values before running `npm run dev`. +# Points at the team's shared dev infra (CockroachDB Cloud, Garage, Google +# OAuth, PayPal Sandbox, Gmail) — nothing to stand up locally. The only +# CHANGE_ME values left are the 4 per-machine secrets (JWT*/SESSION_SECRET, +# auto-generated by new_starr_setup's wizard) and the optional Chibisafe block. # # Local development setup: # 1. cp .env-development .env -# 2. Fill in every CHANGE_ME value below +# 2. Fill in the remaining CHANGE_ME values below (or let the setup wizard do it) # 3. npm install # 4. npm run dev (nodemon, auto-restarts on file change) # @@ -21,17 +24,17 @@ NODE_ENV=development ORIGIN_GUARD_DISABLED=true # allows Postman/curl in dev; hard-locked false when NODE_ENV=production PORT=3024 APP_URL=http://localhost:3024 -APP_LOGO_URL=https://your-cdn.com/logo.png +APP_LOGO_URL=https://cq5as7pc73.ufs.sh/f/pHNnzIw3VjcgjEJ1kFubDgYdvnt1cCAMyRjfOIGiUXPmZF3W FRONTEND_URL=http://localhost:5173 # ── Database (PostgreSQL) ───────────────────────────────────────────────────── -# Local Postgres install — no SSL needed. -DB_HOST=127.0.0.1 -DB_PORT=5432 -DB_NAME=CHANGE_ME -DB_USER=CHANGE_ME -DB_PASSWORD=CHANGE_ME -DB_SSL=false +# Shared team CockroachDB Cloud cluster — SSL is on by default (see +# config/db.config.js: useSSL is true unless DB_SSL=false is set explicitly). +DB_HOST=philproperties-16426.j77.aws-ap-southeast-1.cockroachlabs.cloud +DB_PORT=26257 +DB_NAME=star-philpro +DB_USER=lash +DB_PASSWORD=K5mXTLyL_FrVGOHLHpzU4g DB_FORCE_SYNC=false # never drop tables — even in dev, unless you mean it # ── Cache driver ────────────────────────────────────────────────────────────── @@ -40,7 +43,10 @@ DB_FORCE_SYNC=false # never drop tables — even in dev, unless you mean it CACHE_DRIVER=memory # ── JWT ─────────────────────────────────────────────────────────────────────── -# Generate each secret independently — never reuse across fields. +# Per-machine secrets — never reuse across fields or share between devs. +# new_starr_setup's wizard auto-generates fresh values for these on first run; +# if filling this in by hand instead, generate each independently: +# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" JWT_SECRET=CHANGE_ME_32_BYTE_HEX MEDIA_JWT_SECRET=CHANGE_ME_32_BYTE_HEX JWT_EXPIRES_IN=15m @@ -48,41 +54,38 @@ JWT_REFRESH_SECRET=CHANGE_ME_32_BYTE_HEX JWT_REFRESH_EXPIRES_IN=7d # ── CSRF & Cookies ──────────────────────────────────────────────────────────── +# Also per-machine — auto-generated by the setup wizard, see note above. SESSION_SECRET=CHANGE_ME_32_BYTE_HEX # ── Google OAuth ────────────────────────────────────────────────────────────── -# console.cloud.google.com → Credentials → OAuth 2.0 Client ID -# Add http://localhost:3024/api/auth/google/callback to Authorized redirect URIs -GOOGLE_CLIENT_ID=CHANGE_ME -GOOGLE_CLIENT_SECRET=CHANGE_ME +# Shared team OAuth client (console.cloud.google.com → Credentials). +GOOGLE_CLIENT_ID=379154949440-atchm4lurp9c1k23vbsjvgnqod2o9s5c.apps.googleusercontent.com +GOOGLE_CLIENT_SECRET=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 GOOGLE_CALLBACK_URL=http://localhost:3024/api/auth/google/callback # ── PayPal ──────────────────────────────────────────────────────────────────── -# Use Sandbox credentials for local testing: developer.paypal.com → Sandbox tab. -PAYPAL_CLIENT_ID=CHANGE_ME -PAYPAL_CLIENT_SECRET=CHANGE_ME +# Shared team Sandbox app: developer.paypal.com → Sandbox tab. +PAYPAL_CLIENT_ID=AYjb-DmhvvCagQM0SZxw-sfz30Qb8D-dBPr-wmvq80zOKmfxUrKykv-h85Kzp_Z5YmCX4T06DlaH2-zK +PAYPAL_CLIENT_SECRET=EDkmLa47T0H5QtKdO8VfKdEDh--tuzP0Q2c_RFKASvdIJvs2j051v7j2hW0f8JxNDpjW8XyixE4JCxV4 PAYPAL_ENV=sandbox # ── Email (Gmail API — OAuth2, over HTTPS) ──────────────────────────────────── -# Reuses the same OAuth client as GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET above — -# enable the Gmail API on that project, then run: -# node scripts/get_gmail_refresh_token.js -# and paste the printed value below. -GMAIL_REFRESH_TOKEN=CHANGE_ME -EMAIL_FROM=CHANGE_ME@gmail.com +# Reuses the same OAuth client as GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET above. +# Shared team refresh token — same value the deployed app uses. +GMAIL_REFRESH_TOKEN=1//0ebfPPkFoe9piCgYIARAAGA4SNwF-L9IrsTIWK1dWFRa2dmc33jd94_u5IA-LpTKQweSi3NODT78UhX91OH25ewE_riCT-DjaQg8 +EMAIL_FROM=services.philpro@gmail.com OTP_EXPIRY_MINUTES=10 -# ── S3-compatible Storage (Garage, run locally — bare-metal) ───────────────── -# Run Garage directly on your machine (the docker-compose.yml Garage service -# is set up for the production stack). See: -# https://garagehq.deuxfleurs.fr/documentation/quick-start/ -S3_ENDPOINT=http://127.0.0.1:3900 +# ── S3-compatible Storage (Garage) ──────────────────────────────────────────── +# Shared team Garage instance. Uses the public HTTPS endpoint here (not a +# loopback/WireGuard address) so it works on a fresh machine with no tunnel. +S3_ENDPOINT=https://media.star-philpro-media.space S3_REGION=garage -S3_ACCESS_KEY=CHANGE_ME -S3_SECRET_KEY=CHANGE_ME -S3_BUCKET=CHANGE_ME -S3_PUBLIC_URL=http://127.0.0.1:3900 +S3_ACCESS_KEY=GK1c811a656fafd5ae444eea7b747c4869 +S3_SECRET_KEY=76001b32d753375bd1fc22c9389b590021143b6fbe9d1688d8ed4dba5f004965 +S3_BUCKET=philproperties +S3_PUBLIC_URL=https://media.star-philpro-media.space # ── Chibisafe (optional — used alongside S3 for some asset types) ──────────── CHIBISAFE_BASE_URL=CHANGE_ME diff --git a/.github/workflows/deploy-qas.yml b/.github/workflows/deploy-qas.yml new file mode 100644 index 0000000..98908e8 --- /dev/null +++ b/.github/workflows/deploy-qas.yml @@ -0,0 +1,18 @@ +name: Deploy to Cloudflare + +on: + push: + branches: [qas] + workflow_dispatch: {} + +jobs: + deploy: + # Runner lives on the same home machine (ux) that Cloudflare Tunnel + # already points at — no WireGuard/SSH hop needed like the droplet + # workflow, the runner IS the target. + runs-on: [self-hosted, ux] + steps: + - uses: actions/checkout@v4 + + - name: Rebuild and restart backend + run: docker compose up -d --build backend diff --git a/README.md b/README.md index 73c4018..1b99ace 100644 --- a/README.md +++ b/README.md @@ -2,785 +2,17 @@ ## Technical Documentation **Version:** 1.0.0 -**Author:** rgrgogu **Date:** October 6, 2025 **Stack:** Node.js · Express · Sequelize (PostgreSQL) · JWT · Google OAuth 2.0 · Nodemailer **Deployment** DigitalOcean Droplet (3 Droplets for API + S3 and Postgres) ---- - -## Table of Contents - -1. [Architecture Overview](#1-architecture-overview) -2. [Project Structure](#2-project-structure) -3. [Setup & Installation](#3-setup--installation) -4. [Environment Variables](#4-environment-variables) -5. [Database Models](#5-database-models) -6. [Security Layers](#6-security-layers) -7. [Authentication Flows](#7-authentication-flows) -8. [API Reference](#8-api-reference) -9. [RBAC Permission Matrix](#9-rbac-permission-matrix) -10. [File-by-File Documentation](#10-file-by-file-documentation) -11. [Error Codes Reference](#11-error-codes-reference) -12. [Extending the System](#12-extending-the-system) - ---- - -## 1. Architecture Overview - +## TODO +Branchings and Tags (Backups) ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Client (SPA / Mobile / Browser) │ -└────────────────────────────┬────────────────────────────────────┘ - │ HTTPS - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ Express.js App (server.js) │ -│ │ -│ ┌──────────┐ ┌───────────┐ ┌────────────┐ ┌────────────┐ │ -│ │ CORS │ │ Rate Limit│ │ Cookie │ │ Session │ │ -│ │ Guard │ │ (global) │ │ Parser │ │ (csurf) │ │ -│ └──────────┘ └───────────┘ └────────────┘ └────────────┘ │ -│ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Routes │ │ -│ │ /api/auth/* → auth.routes.js │ │ -│ │ /api/client/* → client.routes.js │ │ -│ │ /api/staff/users/*→ staff.routes.js │ │ -│ │ /api/admin/* → admin.routes.js │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Middleware Chain (per-route) │ │ -│ │ authLimiter → csrfProtection → validate → authenticate │ │ -│ │ → requireRole() → Controller │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Services │ Utils │ │ -│ │ email.service.js │ token.util.js │ │ -│ │ │ otp.util.js │ │ -│ │ │ response.util.js │ │ -│ └──────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────┬───────────────────────┘ - │ Sequelize ORM - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ PostgreSQL │ -│ ┌──────────┐ ┌─────────────────┐ ┌──────────────────────┐ │ -│ │ users │ │ user_sessions │ │ user_groups / │ │ -│ │ │ │ │ │ user_group_members │ │ -│ └──────────┘ └─────────────────┘ └──────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ +main (protected) → prod +qas (protected) → staging +dev (protected*) → integration/playground +feature/xyz → short-lived, deleted after merge +hotfix/xyz → for urgent prod patches ++ tags (v1.0.9...) → released snapshots ``` - ---- - -## 2. Project Structure - -``` -new_starr/ -├── server.js ← App entry point -├── package.json -├── .env-development ← Local dev template — copy to .env -├── .env-production ← Self-hosted/Docker template — copy to .env -│ -├── config/ -│ ├── db.config.js ← Sequelize PostgreSQL instance -│ └── passport.config.js ← Google OAuth 2.0 strategy -│ -├── models/ -│ ├── users.mdl.js ← Users table (with OTP fields) -│ ├── user_sessions.mdl.js ← Session audit table -│ └── user_groups.mdl.js ← Groups + Junction table -│ -├── middleware/ -│ ├── auth.middleware.js ← JWT Bearer token verification -│ ├── rbac.middleware.js ← Role-based access guards -│ ├── csrf.middleware.js ← CSRF protection (csurf) -│ ├── rateLimiter.middleware.js ← Per-route rate limits -│ └── validate.middleware.js ← express-validator result handler -│ -├── validators/ -│ ├── auth.validator.js ← register / login / OTP / password rules -│ └── profile.validator.js ← profile update rules -│ -├── controllers/ -│ ├── auth.controller.js ← Shared: register, OTP, login, refresh, logout -│ ├── client/ -│ │ └── profile.controller.js ← Own profile + own sessions -│ ├── staff/ -│ │ └── users.controller.js ← View/toggle non-admin users -│ └── admin/ -│ └── users.controller.js ← Full CRUD + groups -│ -├── routes/ -│ ├── auth.routes.js ← /api/auth/* -│ ├── client/ -│ │ └── client.routes.js ← /api/client/* -│ ├── staff/ -│ │ └── staff.routes.js ← /api/staff/users/* -│ └── admin/ -│ └── admin.routes.js ← /api/admin/* -│ -├── services/ -│ └── email.service.js ← Nodemailer OTP + welcome emails -│ -└── utils/ - ├── token.util.js ← JWT generate / verify / hash - ├── otp.util.js ← OTP generate / expiry / check - └── response.util.js ← Standardised JSON responses -``` - ---- - -## 3. Setup & Installation - -```bash -# 1. Clone / copy the project -cd star-auth-system - -# 2. Install dependencies -npm install - -# 3. Configure environment -cp .env-development .env -# Edit .env with your DB, SMTP, Google OAuth, and JWT credentials -# (For a production/self-hosted deploy, use .env-production instead — see docker-compose.yml) - -# 4. Ensure PostgreSQL is running and the DB exists -createdb star_db - -# 5. Start development server (auto-syncs models) -npm run dev - -# 6. Production -npm start -``` - -> **Note:** On first boot, Sequelize will create all tables automatically via `sequelize.sync({ alter: true })`. - ---- - -## 4. Environment Variables - -| Variable | Description | Example | -|---|---|---| -| `APP_NAME` | Application name | `starr` | -| `NODE_ENV` | `development` or `production` | `development` | -| `ORIGIN_GUARD_DISABLED` | Set `true` to bypass originGuard (dev/Postman only; ignored in production) | `false` | -| `PORT` | HTTP port | `3000` | -| `APP_URL` | Frontend/app URL (used for CORS) | `http://localhost:3000` | -| `DB_HOST` | PostgreSQL host | `localhost` | -| `DB_PORT` | PostgreSQL port | `5432` | -| `DB_NAME` | Database name | `star_db` | -| `DB_USER` | DB user | `postgres` | -| `DB_PASSWORD` | DB password | `yourpassword` | -| `JWT_SECRET` | Access token signing key (≥32 chars) | `your_super_secret_…` | -| `JWT_EXPIRES_IN` | Access token TTL | `1d` | -| `JWT_REFRESH_SECRET` | Refresh token signing key | `your_refresh_secret_…` | -| `JWT_REFRESH_EXPIRES_IN` | Refresh token TTL | `7d` | -| `SESSION_SECRET` | Express session secret (used by csurf) | `your_session_secret_…` | -| `GOOGLE_CLIENT_ID` | Google OAuth client ID | from GCP Console | -| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | from GCP Console | -| `GOOGLE_CALLBACK_URL` | OAuth redirect URI | `/api/auth/google/callback` | -| `SMTP_HOST` | Mail server host | `smtp.gmail.com` | -| `SMTP_PORT` | Mail server port | `587` | -| `SMTP_USER` | Mail username | `you@gmail.com` | -| `SMTP_PASS` | Mail password / App Password | `xxxx xxxx xxxx xxxx` | -| `EMAIL_FROM` | Sender address | `no-reply@star-system.com` | -| `OTP_EXPIRY_MINUTES` | OTP validity in minutes | `10` | - ---- - -## 5. Database Models - -### 5.1 `users` Table - -| Column | Type | Notes | -|---|---|---| -| `user_id` | BIGINT PK | Auto-increment | -| `email` | VARCHAR(255) UNIQUE | Validated email | -| `password` | TEXT | bcrypt hash (null for Google users) | -| `is_active` | BOOLEAN | Default `true` | -| `is_verified` | BOOLEAN | Default `false`; set `true` on OTP verify | -| `reg_type` | ENUM | `system` or `google` | -| `acc_type` | ENUM | `user` / `staff` / `admin` | -| `personal_info` | JSONB | Nested name, addresses, phone, avatar, album | -| `otp_code` | VARCHAR(6) | Cleared after verification | -| `otp_expires_at` | TIMESTAMP | Cleared after verification | -| `createdAt` | TIMESTAMP | Auto | -| `updatedAt` | TIMESTAMP | Auto | - -### 5.2 `user_sessions` Table - -| Column | Type | Notes | -|---|---|---| -| `session_id` | BIGINT PK | Auto-increment | -| `user_id` | BIGINT FK | → users.user_id | -| `login_info` | JSONB | `{ date, ip_address, device_info }` | -| `logout_info` | JSONB | Populated on logout | -| `refresh_token_hash` | TEXT | SHA-256 of refresh token | -| `is_active` | BOOLEAN | `false` after logout | - -### 5.3 `user_groups` + `user_group_members` Tables - -Groups are named permission bundles. A user can belong to multiple groups. - -``` -users ──< user_group_members >── user_groups - (user_id, group_id, - joined_at) -``` - ---- - -## 6. Security Layers - -### 6.1 Rate Limiting - -| Limiter | Routes | Window | Max | -|---|---|---|---| -| `globalLimiter` | All routes | 15 min | 1 000 | -| `authLimiter` | `/register`, `/login` | 15 min | 20 | -| `otpLimiter` | `/verify-otp`, `/resend-otp` | 15 min | 5 | -| `sensitiveOpsLimiter` | `/change-password` | 60 min | 10 | - -### 6.2 CSRF Protection - -CSRF is provided via **csurf** (Double Submit Cookie pattern). - -- Retrieve token: `GET /api/auth/csrf-token` -- Send token: `X-CSRF-Token` header OR `_csrf` body field -- Applied automatically to cookie-based flows - -> **SPA / REST API clients using `Authorization: Bearer` are inherently CSRF-safe** because cross-site requests cannot set custom headers. CSRF is included for hybrid/SSR scenarios. - -### 6.3 JWT Authentication - -- **Access Token:** Short-lived (1 day). Sent as `Authorization: Bearer `. -- **Refresh Token:** Long-lived (7 days). SHA-256 hash stored in `user_sessions`. Rotated on every `/refresh` call. -- **Session invalidation:** Logout, password change, and admin force-terminate all flip `is_active = false`. - -### 6.4 Password Security - -- bcryptjs with **cost factor 12** -- Minimum 8 chars, must contain uppercase + digit -- Current password required before change -- All sessions invalidated after password change - -### 6.5 Origin Guard - -All API routes (except `/api/health`) are protected by `originGuard.middleware.js`, a two-layer non-browser request filter: - -| Layer | Header checked | Applies to | Blocks | -|---|---|---|---| -| 1 | `Sec-Fetch-Site` (must be present) | All methods | curl, Postman, Nikto, sqlmap, scanners | -| 2 | `Origin` (must be in `ALLOWED_ORIGINS`) | POST · PUT · PATCH · DELETE | Cross-origin mutation from unlisted domains | - -**Development bypass** — set `ORIGIN_GUARD_DISABLED=true` in `.env` to allow Postman and other tools through without restarting with a different config. The bypass is **hard-locked off** when `NODE_ENV=production`, even if the flag is set. - -``` -# .env — enable for Postman testing -ORIGIN_GUARD_DISABLED=true - -# .env — re-enable when done -ORIGIN_GUARD_DISABLED=false -``` - -> Note: BurpSuite running as a MITM proxy through a real browser is not blocked — the browser supplies all correct headers. Rate limiting and valid credentials are the only defences there. - ---- - -### 6.6 RBAC Hierarchy - -``` -admin ──► can do everything - │ -staff ──► can view/toggle client+staff users; cannot touch admin accounts - │ -user ──► can only manage own profile and sessions -(client) -``` - ---- - -## 7. Authentication Flows - -### 7.1 System Registration → OTP Verify → Auto-Login - -``` -Client Server Email - │ │ │ - │── POST /api/auth/register ──► │ │ - │ { email, password } │ │ - │ │── create user (unverified)│ - │ │── generate OTP ──────────►│ - │◄── 201 { email } ─────────── │ │ - │ │ │ - │── POST /api/auth/verify-otp ►│ │ - │ { email, otp } │ │ - │ │── validate OTP │ - │ │── mark is_verified=true │ - │ │── create session record │ - │ │── send welcome email ────►│ - │◄── 200 { accessToken, │ │ - │ refreshToken, │ │ - │ user } ─────────── │ │ -``` - -### 7.2 System Login - -``` -Client Server - │── POST /api/auth/login ─────►│ - │ { email, password } │ - │ │── lookup user by email - │ │── bcrypt.compare(password, hash) - │ │── create session record - │◄── 200 { accessToken, │ - │ refreshToken, │ - │ session_id, user } ──│ -``` - -### 7.3 Google OAuth - -``` -Client Google Server - │── GET /api/auth/google ──────────────►│ - │ │── redirect to Google - │◄─────── Google consent screen ───────│ - │── approve ──►[Google] ───callback──► │ - │ │── find or create user - │ │── create session - │◄── 200 { accessToken, refreshToken } ─│ -``` - -### 7.4 Token Refresh - -``` -Client Server - │── POST /api/auth/refresh ───►│ - │ { refreshToken } │ - │ │── verify JWT signature - │ │── lookup session by token hash - │ │── rotate refresh token - │◄── 200 { accessToken, │ - │ refreshToken } ─────│ -``` - -### 7.5 Logout - -``` -Client Server - │── POST /api/auth/logout ────►│ - │ Authorization: Bearer ... │ - │ { session_id } │ - │ │── authenticate JWT - │ │── set session.is_active=false - │ │── save logout_info - │◄── 200 "Logged out" ─────── │ -``` - ---- - -## 8. API Reference - -> **Base URL:** `http://localhost:3000/api` -> **Auth header:** `Authorization: Bearer ` - ---- - -### 8.1 Auth Endpoints (Public) - -#### `POST /auth/register` -Register a new client account. - -**Request body:** -```json -{ - "email": "jane@example.com", - "password": "Password1" -} -``` - -**Response 201:** -```json -{ - "status": "success", - "message": "Registration successful. Please check your email for the OTP.", - "data": { "email": "jane@example.com" } -} -``` - ---- - -#### `POST /auth/verify-otp` -Verify email with OTP. Returns tokens (auto-login). - -**Request body:** -```json -{ "email": "jane@example.com", "otp": "048291" } -``` - -**Response 200:** -```json -{ - "status": "success", - "message": "Email verified successfully. You are now logged in.", - "data": { - "accessToken": "eyJ...", - "refreshToken": "eyJ...", - "session_id": 1, - "user": { "user_id": 1, "email": "jane@example.com", "acc_type": "user", ... } - } -} -``` - ---- - -#### `POST /auth/resend-otp` -Resend OTP email (5 requests / 15 min limit). - -**Request body:** `{ "email": "jane@example.com" }` - ---- - -#### `POST /auth/login` -System (email + password) login. - -**Request body:** -```json -{ "email": "jane@example.com", "password": "Password1" } -``` - -**Response 200:** Same shape as verify-otp. - ---- - -#### `POST /auth/refresh` -Rotate access + refresh tokens. - -**Request body:** `{ "refreshToken": "eyJ..." }` - ---- - -#### `GET /auth/google` -Redirect to Google consent screen. No body needed. - ---- - -#### `GET /auth/google/callback` -Google OAuth redirect target. Returns tokens on success. - ---- - -#### `POST /auth/logout` *(requires Bearer token)* -Invalidates the specified session. - -**Request body:** `{ "session_id": 1 }` - ---- - -#### `POST /auth/change-password` *(requires Bearer token)* -Change password. Invalidates all sessions. - -**Request body:** -```json -{ "current_password": "OldPass1", "new_password": "NewPass2" } -``` - ---- - -### 8.2 Client Endpoints *(requires Bearer — any acc_type)* - -| Method | Path | Description | -|---|---|---| -| GET | `/client/profile` | Own profile | -| PUT | `/client/profile` | Update own `personal_info` | -| GET | `/client/sessions` | Own active sessions | -| DELETE | `/client/sessions/:id` | Revoke one of own sessions | - -**PUT /client/profile — Example body:** -```json -{ - "personal_info": { - "name": { - "given_name": "Jane", - "last_name": "Doe" - }, - "occupation": "Engineer", - "date_of_birth": "1995-06-15" - } -} -``` - ---- - -### 8.3 Staff Endpoints *(requires Bearer — staff or admin)* - -| Method | Path | Description | -|---|---|---| -| GET | `/staff/users` | Paginated list (non-admins) | -| GET | `/staff/users/:id` | Single non-admin user | -| PUT | `/staff/users/:id/status` | Toggle `is_active` | -| GET | `/staff/users/:id/sessions` | User's sessions | - -**Query params for GET list:** `?page=1&limit=20` - -**PUT /staff/users/:id/status — Body:** `{ "is_active": false }` - ---- - -### 8.4 Admin Endpoints *(requires Bearer — admin only)* - -| Method | Path | Description | -|---|---|---| -| GET | `/admin/users` | All users (paginated) | -| GET | `/admin/users/:id` | Any user with groups | -| PUT | `/admin/users/:id` | Update `acc_type`, `is_active`, `personal_info` | -| DELETE | `/admin/users/:id` | Hard delete user | -| GET | `/admin/users/:id/sessions` | All sessions | -| DELETE | `/admin/users/:id/sessions/:sid` | Force-terminate session | -| GET | `/admin/groups` | List all groups | -| POST | `/admin/groups` | Create group `{ name, description }` | -| DELETE | `/admin/groups/:gid` | Delete group | -| POST | `/admin/users/:id/groups/:gid` | Add user to group | -| DELETE | `/admin/users/:id/groups/:gid` | Remove user from group | - -**PUT /admin/users/:id — Allowed fields:** -```json -{ - "acc_type": "staff", - "is_active": true, - "personal_info": { ... } -} -``` - ---- - -## 9. RBAC Permission Matrix - -| Action | Client (`user`) | Staff (`staff`) | Admin (`admin`) | -|---|:---:|:---:|:---:| -| Register | ✅ | ✅ | ✅ | -| Login | ✅ | ✅ | ✅ | -| View own profile | ✅ | ✅ | ✅ | -| Edit own profile | ✅ | ✅ | ✅ | -| View own sessions | ✅ | ✅ | ✅ | -| Revoke own session | ✅ | ✅ | ✅ | -| Change own password | ✅ | ✅ | ✅ | -| List all users | ❌ | ✅ (no admins) | ✅ | -| View any user | ❌ | ✅ (no admins) | ✅ | -| Toggle user status | ❌ | ✅ (no admins) | ✅ | -| View user sessions | ❌ | ✅ (no admins) | ✅ | -| Change user `acc_type` | ❌ | ❌ | ✅ | -| View admin accounts | ❌ | ❌ | ✅ | -| Hard delete users | ❌ | ❌ | ✅ | -| Force-terminate session | ❌ | ❌ | ✅ | -| Manage groups | ❌ | ❌ | ✅ | - ---- - -## 10. File-by-File Documentation - -### `server.js` -**Purpose:** Application entry point. Bootstraps Express, registers global middleware (CORS, rate limiter, cookie-parser, session, Passport), mounts all routes, syncs DB models. -**Key behaviors:** -- Runs `sequelize.sync({ alter: true })` on startup (development) — safe column updates. -- Registers a global 404 handler and error handler. -- Enables `trust proxy` for correct IP detection behind load balancers. - ---- - -### `config/db.config.js` -**Purpose:** Creates and exports a single authenticated Sequelize instance. -**Usage:** `const sequelize = require('./config/db.config');` - ---- - -### `config/passport.config.js` -**Purpose:** Registers the Google OAuth 2.0 strategy. Auto-creates user on first Google login with `reg_type='google'` and `is_verified=true`. -**Usage:** Imported in `server.js`; used in `routes/auth.routes.js` via `passport.authenticate('google', ...)`. - ---- - -### `models/users.mdl.js` -**Purpose:** Defines the `users` table including OTP fields (`otp_code`, `otp_expires_at`) added for the verification flow. -**Exported as:** `mdl_Users` (default) - ---- - -### `models/user_sessions.mdl.js` -**Purpose:** Audit log for logins/logouts. Stores `refresh_token_hash` for secure token rotation and session invalidation. Sets up `belongsTo/hasMany` with Users. -**Exported as:** `mdl_UserSessions` (default) - ---- - -### `models/user_groups.mdl.js` -**Purpose:** `user_groups` and `user_group_members` junction table. Implements `belongsToMany` associations between Users and Groups. -**Exported as:** `{ mdl_UserGroups, mdl_UserGroupMembers }` - ---- - -### `middleware/auth.middleware.js` -**Purpose:** Reads `Authorization: Bearer `, calls `verifyAccessToken()`, loads the user from DB, and attaches it to `req.user`. -**Exported:** `authenticate` - ---- - -### `middleware/rbac.middleware.js` -**Purpose:** Role guards that inspect `req.user.acc_type`. All guards must be used **after** `authenticate`. -**Exported:** `requireClient()`, `requireStaff()`, `requireAdmin()`, `requireOwnerOrStaff()`, `requireOwnerOrAdmin()` - ---- - -### `middleware/csrf.middleware.js` -**Purpose:** Wraps `csurf` with a token endpoint and a dedicated error handler. -**Exported:** `csrfProtection`, `getCsrfToken`, `csrfErrorHandler` - ---- - -### `middleware/rateLimiter.middleware.js` -**Purpose:** Four rate-limiter configurations for different route tiers. -**Exported:** `globalLimiter`, `authLimiter`, `otpLimiter`, `sensitiveOpsLimiter` - ---- - -### `middleware/validate.middleware.js` -**Purpose:** Reads the `validationResult` from express-validator and returns 422 if any rule failed. -**Exported:** `validate` - ---- - -### `validators/auth.validator.js` -**Purpose:** express-validator chains for `register`, `login`, `verify-otp`, `resend-otp`, `change-password`. -**Usage:** Spread into route definitions: `router.post('/login', ...loginValidator, validate, handler)` - ---- - -### `validators/profile.validator.js` -**Purpose:** Validates `personal_info` fields on profile update requests. - ---- - -### `utils/token.util.js` -**Purpose:** JWT helpers. -**Functions:** -- `generateTokens(user)` → `{ accessToken, refreshToken }` -- `verifyAccessToken(token)` → decoded payload (throws on expiry/invalid) -- `verifyRefreshToken(token)` → decoded payload -- `hashToken(token)` → SHA-256 hex string for DB storage - ---- - -### `utils/otp.util.js` -**Purpose:** Cryptographically secure OTP helpers. -**Functions:** -- `generateOTP()` → `"048291"` (6 digits, padded) -- `getOTPExpiry(minutes)` → `Date` N minutes from now -- `isOTPExpired(expiresAt)` → `boolean` - ---- - -### `utils/response.util.js` -**Purpose:** Standardised response envelope `{ status, message, data?, errors? }`. -**Functions:** `success(res, msg, data, statusCode)`, `error(res, msg, statusCode, errors)`, `validationError(res, errors)` - ---- - -### `services/email.service.js` -**Purpose:** Nodemailer transporter with two email templates. -**Functions:** -- `sendOTPEmail(to, otp, expiryMinutes)` — styled HTML OTP email -- `sendWelcomeEmail(to, name)` — post-verification welcome - ---- - -### `controllers/auth.controller.js` -**Purpose:** Shared auth logic: `register`, `verifyOTP`, `resendOTP`, `login`, `googleCallback`, `refreshToken`, `logout`, `changePassword`. - ---- - -### `controllers/client/profile.controller.js` -**Purpose:** Self-service profile actions for any authenticated user. -**Functions:** `getProfile`, `updateProfile` (deep-merge), `getSessions`, `revokeSession` - ---- - -### `controllers/staff/users.controller.js` -**Purpose:** Staff-level user management (read-only + status toggle; no admin data). -**Functions:** `getUsers`, `getUser`, `setUserStatus`, `getUserSessions` - ---- - -### `controllers/admin/users.controller.js` -**Purpose:** Full admin control over users, sessions, and groups. -**Functions:** `getUsers`, `getUser`, `updateUser`, `deleteUser`, `getUserSessions`, `terminateSession`, `getGroups`, `createGroup`, `deleteGroup`, `addUserToGroup`, `removeUserFromGroup` - ---- - -## 11. Error Codes Reference - -| HTTP | Scenario | -|---|---| -| 400 | Bad request (missing field, wrong type, already verified) | -| 401 | Missing/invalid/expired token, wrong credentials | -| 403 | Account deactivated, wrong role, CSRF error | -| 404 | Resource not found | -| 409 | Email already registered | -| 422 | Validation failed (see `errors[]` array) | -| 429 | Rate limit exceeded | -| 500 | Internal server error | - -All error responses follow: -```json -{ - "status": "error", - "message": "Human-readable reason", - "errors": [ ... ] // only on 422 -} -``` - ---- - -## 12. Extending the System - -### Add a New Endpoint - -1. Create validator rules in `validators/` -2. Create or extend a controller in `controllers//` -3. Add the route in `routes//.routes.js` with appropriate middleware chain -4. No changes to `server.js` needed if mounting the same base path - -### Add a New Role - -1. Add the ENUM value to `acc_type` in `users.mdl.js` -2. Add a guard in `rbac.middleware.js` (e.g., `requireSuperAdmin()`) -3. Create a dedicated route + controller folder - -### Add Redis Session Store - -Install `connect-redis` and update `server.js`: -```js -const RedisStore = require('connect-redis').default; -const { createClient } = require('redis'); -const redisClient = createClient({ url: process.env.REDIS_URL }); -await redisClient.connect(); -app.use(session({ store: new RedisStore({ client: redisClient }), ... })); -``` - -### Production Checklist - -- [ ] Set `NODE_ENV=production` -- [ ] Set `ORIGIN_GUARD_DISABLED=false` (or remove the key entirely) -- [ ] Use strong `JWT_SECRET` (≥ 64 random characters) -- [ ] Enable HTTPS / TLS (set `cookie.secure: true`) -- [ ] Point `APP_URL` to your actual domain for CORS -- [ ] Replace in-memory rate limiter with Redis store -- [ ] Set up log rotation (replace `console.log` with Winston/Pino) -- [ ] Use `sequelize.sync({ force: false, alter: false })` in production (run migrations instead) - ---- - -*End of Documentation — STAR Phase 1 Auth System v1.0.0*