quickly Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
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
- Architecture Overview
- Project Structure
- Setup & Installation
- Environment Variables
- Database Models
- Security Layers
- Authentication Flows
- API Reference
- RBAC Permission Matrix
- File-by-File Documentation
- Error Codes Reference
- 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-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
# 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-Tokenheader OR_csrfbody field - Applied automatically to cookie-based flows
SPA / REST API clients using
Authorization: Bearerare 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/refreshcall. - 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 proxyfor 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 payloadhashToken(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)→DateN minutes from nowisOTPExpired(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 emailsendWelcomeEmail(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
- Create validator rules in
validators/ - Create or extend a controller in
controllers/<role>/ - Add the route in
routes/<role>/<role>.routes.jswith appropriate middleware chain - No changes to
server.jsneeded if mounting the same base path
Add a New Role
- Add the ENUM value to
acc_typeinusers.mdl.js - Add a guard in
rbac.middleware.js(e.g.,requireSuperAdmin()) - 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_URLto your actual domain for CORS - Replace in-memory rate limiter with Redis store
- Set up log rotation (replace
console.logwith 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