# TOPIQ Authentication Audit Report

**Date**: 2026-10-09  
**Target**: `topiq-backend`  
**Auditor**: Senior Node.js / Express Security Architect  

---

## 1. Executive Summary

This audit assesses the current state of authentication, session handling, token mechanics, password security, and identity verification in the TOPIQ backend. The system foundation relies on Express.js, MySQL (`mysql2/promise`), `bcryptjs`, `jsonwebtoken`, and `joi`.

While basic login and initial ASM registration were drafted in earlier phases, critical production authentication features are either **missing**, **partially implemented**, or **insecure**:
- **Missing**: Refresh token rotation endpoint (`/refresh`), logout endpoint (`/logout`), password reset endpoints (`/forgot-password`, `/reset-password`), role-based authorization middleware, and general user/student registration.
- **Insecure**: Hardcoded fallback JWT secrets (`fallback_secret`, `fallback_refresh_secret`) in code without production enforcement; stateless auth middleware that does not check user account status (`SUSPENDED`/`DISABLED`) against the database.
- **Partially Implemented**: Refresh tokens are issued during login and hashed into `refresh_sessions`, but no refresh rotation, reuse detection, or invalidation logic exists to actually use them.

---

## 2. Feature-by-Feature Audit Matrix

| Feature | Classification | File / Endpoint Path | Audit Findings & Security Assessment |
|---|---|---|---|
| **Password Hashing** | **Implemented and verified** | `src/modules/auth/auth.service.js` | Uses `bcryptjs` with salt round 10. Passwords are never stored plaintext. Parameterized queries used. |
| **Password Verification** | **Implemented and verified** | `src/modules/auth/auth.service.js` | Uses `bcrypt.compare`. Does not expose password hashes in responses. |
| **Login Endpoint** | **Partially implemented** | `POST /api/v1/auth/login` (`src/modules/auth/auth.controller.js`) | Accepts mobile or email identifier and password. Rejects invalid credentials with a generic message. Checks `status === 'ACTIVE'`. **Issue**: Missing identifier normalization (trim/lowercase email); accepts `deviceMetadata` in Joi but ignores it. |
| **Access Tokens** | **Implemented but insecure** | `src/modules/auth/auth.service.js`, `src/middleware/authenticate.js` | Issues 15-minute JWTs. **Security Flaw**: Uses `process.env.JWT_SECRET || 'fallback_secret'`. Must fail securely if required production secret is missing rather than falling back to a known hardcoded string. |
| **Authentication Middleware** | **Partially implemented** | `src/middleware/authenticate.js` | Extracts `Bearer <token>` and verifies signature and expiration. **Flaw**: Does not verify if the user account is still `ACTIVE` or exists in the database. Suspended or deleted users retain access until token expiry. |
| **Role Authorization Middleware** | **Missing** | N/A | No middleware exists to enforce role permissions (`ADMIN`, `ASM`, `COORDINATOR`, `STUDENT`) on protected routes. |
| **Refresh Token Issuance** | **Partially implemented** | `src/modules/auth/auth.service.js` | Issues a 7-day refresh JWT during login and hashes it into `refresh_sessions`. |
| **Refresh Token Endpoint** | **Missing** | N/A (`POST /api/v1/auth/refresh` not mounted) | No endpoint or service method exists to consume a refresh token, rotate it, or issue a new access token. |
| **Refresh Token Rotation & Reuse Detection** | **Missing** | N/A | `refresh_sessions` table has `rotated_from_id` and `revoked_at`, but zero code implements rotation or token family revocation upon reuse. |
| **Logout Endpoint** | **Missing** | N/A (`POST /api/v1/auth/logout` not mounted) | No endpoint exists to invalidate the active refresh session on the server. |
| **Current User (`/me`)** | **Partially implemented** | `GET /api/v1/auth/me` (`src/modules/auth/auth.controller.js`) | Returns decoded JWT payload (`req.user`). Does not query fresh database state or verify profile information. |
| **Public Registration** | **Partially implemented** | `POST /api/v1/auth/register/asm` (`src/modules/auth/auth.routes.js`) | Only ASM self-registration exists. General registration endpoint (`POST /api/v1/auth/register`) is missing. Need to ensure public registration defaults to `STUDENT` and strictly prevents self-assigning privileged roles (`ADMIN`, `ASM`, `COORDINATOR`). |
| **OTP Challenges** | **Missing** | N/A (`otp_challenges` table exists) | `otp_challenges` table exists in database (`002_add_referral_events_classes_audit.sql`), but no endpoints or services exist to issue, verify, rate-limit, or consume OTPs. |
| **Password Reset Request** | **Missing** | N/A (`POST /api/v1/auth/forgot-password`) | No endpoint exists to initiate a password reset challenge. |
| **Password Reset Completion** | **Missing** | N/A (`POST /api/v1/auth/reset-password`) | No endpoint exists to verify an OTP challenge and update password with session invalidation. |
| **Environment Configuration** | **Missing** | `.env.example` | Missing `.env.example`. No startup validation to verify that essential secrets (`JWT_SECRET`, `REFRESH_SECRET`) are configured. |
| **Global Error Handling** | **Implemented and verified** | `src/middleware/errorHandler.js` | Returns consistent error payload: `{ success: false, error: { code, message, details } }`. |
| **Request Validation** | **Implemented and verified** | `src/middleware/validate.js` | Uses Joi schema validation with `abortEarly: false` and standard formatting. |

---

## 3. Database Schema Alignment

### `refresh_sessions` Schema
```sql
CREATE TABLE refresh_sessions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  token_hash VARCHAR(255) NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  revoked_at TIMESTAMP NULL,
  rotated_from_id INT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
```
- **Capability**: Supports O(1) SHA-256 token hashing lookup, rotation chaining via `rotated_from_id`, and revocation via `revoked_at`.
- **Status**: Table exists; implementation missing.

### `otp_challenges` Schema
```sql
CREATE TABLE otp_challenges (
  id INT AUTO_INCREMENT PRIMARY KEY,
  identifier VARCHAR(255) NOT NULL,
  purpose VARCHAR(50) NOT NULL,
  otp_hash VARCHAR(255) NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  attempts INT DEFAULT 0,
  consumed_at TIMESTAMP NULL
);
```
- **Capability**: Supports secure hashed OTP storage (SHA-256), bounded attempt counters, expiry tracking, and one-time consumption (`consumed_at`).
- **Status**: Table exists; implementation missing.

---

## 4. Remediation Plan

1. **Environment & Secrets**:
   - Create `.env.example` with clear documentation.
   - Introduce environment validation in `src/config/env.js` or `src/config/jwt.js` that fails fast if `JWT_SECRET` or `REFRESH_SECRET` is missing in production. Remove hardcoded fallback secrets.
2. **Refresh Session Architecture**:
   - Generate cryptographically secure random refresh tokens (`crypto.randomBytes(40).toString('hex')`).
   - Store deterministic SHA-256 hash in `token_hash` for fast, secure lookup.
   - Implement `POST /api/v1/auth/refresh` with atomic rotation inside a transaction, setting `rotated_from_id` and revoking the old session.
   - Detect reuse of rotated or revoked tokens and invalidate all active sessions for that user family.
3. **Logout Endpoint**:
   - Implement `POST /api/v1/auth/logout` to revoke the specific refresh session.
4. **Enhanced Auth & Authorization Middleware**:
   - Update `authenticate.js` to look up user status in database to ensure suspended/disabled accounts are immediately blocked.
   - Add `authorize(...roles)` middleware to protect privileged routes.
5. **Registration & Role Protection**:
   - Implement generic `POST /api/v1/auth/register` defaulting strictly to `STUDENT`, prohibiting self-assignment of `ADMIN`, `ASM`, or `COORDINATOR`.
6. **OTP & Password Reset**:
   - Implement `POST /api/v1/auth/forgot-password` (generates bounded 6-digit OTP, stores SHA-256 hash in `otp_challenges`, enforces cooldown and generic response).
   - Implement `POST /api/v1/auth/reset-password` (validates OTP, checks attempts <= 3, marks `consumed_at`, updates password hash, and revokes all active `refresh_sessions` for the user).
   - Mock external SMS delivery cleanly without third-party external charges.
7. **Refined `/me` Endpoint**:
   - Fetch fresh safe user profile (`id`, `full_name`, `mobile_normalized`, `email_normalized`, `role`, `status`) from the database.
8. **Automated Testing Suite**:
   - Implement automated tests covering all 16 test criteria on a dedicated disposable test database.
