kennethobsequio 358fecb510 fix: local regression
replace my hard coded thinking

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-06-22 11:09:43 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 11:09:43 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-05-05 23:18:47 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-05-05 23:18:47 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-05-03 15:00:34 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-05-20 13:35:54 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00
2026-06-22 10:06:58 +08:00

STAR Phase 1 — Tier 1 Auth System

Technical Documentation

Version: 1.0.0
Author: rgrgogu
Date: October 6, 2025
Stack: Node.js · Express · Sequelize (PostgreSQL) · JWT · Google OAuth 2.0 · Nodemailer


Table of Contents

  1. Architecture Overview
  2. Project Structure
  3. Setup & Installation
  4. Environment Variables
  5. Database Models
  6. Security Layers
  7. Authentication Flows
  8. API Reference
  9. RBAC Permission Matrix
  10. File-by-File Documentation
  11. Error Codes Reference
  12. Extending the System

1. Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│  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  │    │
│  └──────────┘  └─────────────────┘  └──────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

2. Project Structure

new_starr/
├── server.js                        ← App entry point
├── package.json
├── .env.example                     ← Copy to .env and fill values
│
├── 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

# 1. Clone / copy the project
cd star-auth-system

# 2. Install dependencies
npm install

# 3. Configure environment
cp .env.example .env
# Edit .env with your DB, SMTP, Google OAuth, and JWT credentials

# 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 <token>.
  • 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 <accessToken>


8.1 Auth Endpoints (Public)

POST /auth/register

Register a new client account.

Request body:

{
  "email": "jane@example.com",
  "password": "Password1"
}

Response 201:

{
  "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:

{ "email": "jane@example.com", "otp": "048291" }

Response 200:

{
  "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:

{ "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:

{ "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:

{
  "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:

{
  "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 <token>, 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:

{
  "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/<role>/
  3. Add the route in routes/<role>/<role>.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:

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

S
Description
A course & training platform currently live in staging test deployment.
https://starr.philproperties.orij.space Readme Apache-2.0
5.5 MiB
Languages
JavaScript 99.4%
CSS 0.5%