# Nucleus Backend - Authentication & Authorization Deep Dive

Complete documentation of the authentication, authorization, session management, and security enforcement system.

---

## Table of Contents

1. [Architecture Overview](#architecture-overview)
2. [Key Files](#key-files)
3. [Sign Up Flow](#sign-up-flow)
4. [Sign In Flow](#sign-in-flow)
5. [JWT Token Structure](#jwt-token-structure)
6. [Middleware Stack](#middleware-stack)
7. [Role-Based Access Control (RBAC)](#role-based-access-control-rbac)
8. [User Matrix - Permission System](#user-matrix---permission-system)
9. [Password Security Policies](#password-security-policies)
10. [Forgot Password / Reset Flow](#forgot-password--reset-flow)
11. [Change Password Flow](#change-password-flow)
12. [Account Lockout (Max Retries)](#account-lockout-max-retries)
13. [Password Expiry (90-Day Rule)](#password-expiry-90-day-rule)
14. [License Validation](#license-validation)
15. [Logout & Inactive Session Tracking](#logout--inactive-session-tracking)
16. [Form-Level Verification (Initiate Verification)](#form-level-verification-initiate-verification)
17. [Department Role Middleware (remember_token)](#department-role-middleware-remember_token)
18. [Admin - User Management](#admin---user-management)
19. [Audit Trail (21 CFR Part 11 Compliance)](#audit-trail-21-cfr-part-11-compliance)
20. [Database Models Reference](#database-models-reference)
21. [Route Registration & Protection Map](#route-registration--protection-map)
22. [Security Summary](#security-summary)

---

## Architecture Overview

```
Client Request
  |
  v
server.js (Express, CORS, Morgan, body-parser 150MB limit)
  |
  v
app/routes/index.js
  |
  +-- [UNAUTHENTICATED ZONE] -- Lines 65-84
  |     /user/signup, /user/signin, /user/send-email, /user/update-password
  |     /pod, /poi, /mrn-dom, /mrn-int, /purchase-returns (department_role)
  |     /eqp_integration, /eqp_area (department_role)
  |     /equipment-planner (select endpoints)
  |
  +-- router.use(verify_auth) -- Line 85 (JWT GATE)
  |
  +-- [AUTHENTICATED ZONE] -- Lines 88-309
        /admin (role_admin on sub-routes)
        /material, /batch, /qc, /sampling, /stock, /dispense, ...
        /license, /audit_log, /failed-logins, /department, ...
```

---

## Key Files

| File | Purpose |
|---|---|
| `app/controllers/user.controller.js` | Signup, signin, logout, password reset, password change |
| `app/controllers/admin.controller.js` | User CRUD, user matrix, role management |
| `app/controllers/department.controller.js` | Designation and department management |
| `app/controllers/initiate_verification.controller.js` | Form-level re-authentication |
| `app/controllers/failed-logins.js` | Failed login report endpoint |
| `app/middelwares/verify_auth.js` | JWT verification, role_admin, role_user, department_role |
| `app/routes/user.route.js` | Auth route definitions |
| `app/routes/admin.route.js` | Admin route definitions with role_admin |
| `app/routes/index.js` | Master route aggregator, auth boundary |
| `app/models/users.js` | User schema with bcrypt hooks |
| `app/models/user_password.js` | Password history (reuse prevention) |
| `app/models/failed_logins.js` | Failed login tracking |
| `app/models/loginlogoutdetails.js` | Login/logout audit log |
| `app/models/active_session.js` | Active session tracking |
| `app/models/user_change_log.js` | 21 CFR Part 11 audit trail |
| `app/models/change_pwd_emails.js` | Password reset email log |
| `app/models/license.js` | License key & validity |
| `app/models/user_matrix.js` | Role + level permission matrix |
| `app/models/qa_superuser_level.js` | QA multi-department role assignments |
| `app/models/designated_authorities.js` | Designation master |
| `app/models/depatment_authorities.js` | Department master |
| `app/models/designation_log.js` | Designation/department change log |

---

## Sign Up Flow

**Endpoint:** `POST /api/v1/user/signup` (unauthenticated)

**Controller:** `user.controller.js` > `signup`

```
Client sends: { first_name, last_name, email, password, phone, role, level, designation_id }
  |
  v
1. Check if email already exists (case-insensitive via Op.iLike)
  |-- YES --> 400 "User email already in use"
  |-- NO  --> continue
  |
  v
2. Create user record
   - Password auto-hashed via model's bcrypt setter (salt rounds: 10)
   - beforeSave hook sets password_reset_date
  |
  v
3. Create user_password record (password history entry)
  |
  v
4. Return 200 { success: true, user }
```

**Note:** This endpoint is not used for normal user creation in production. The admin `create_user` flow is the standard path.

---

## Sign In Flow

**Endpoint:** `POST /api/v1/user/signin` (unauthenticated)

**Controller:** `user.controller.js` > `signin`

**Input:** `{ email (employee_id), password }`

```
1. Find user by employee_id WHERE is_active = true
   - Includes qa_superuser_level association
   |
   |-- NOT FOUND --> "User Not Found. Or Suspended Your Account."
   |
   v
2. LICENSE CHECK (non-admin users only)
   - Query: license WHERE is_active = true
   - If user.role !== 'admin' AND no active license found:
     --> "Your license is expired"
   |
   v
3. ACCOUNT LOCKOUT CHECK
   - If password_tried == 3:
     --> 401 "Your account has been deactivated due to 3 incorrect password attempts."
   |
   v
4. PASSWORD VALIDATION (bcrypt.compareSync)
   |
   |-- INVALID PASSWORD:
   |     a. Create failed_logins record { email }
   |     b. Increment password_tried counter (+1)
   |     c. If password_tried reaches 3:
   |        - Set is_active = false (LOCKS ACCOUNT)
   |        - Return 401 "deactivated due to 3 incorrect attempts"
   |     d. Otherwise:
   |        - Return 401 "You have made N unsuccessful attempt(s)..."
   |
   |-- VALID PASSWORD --> continue
   |
   v
5. PASSWORD EXPIRY CHECK (90 days)
   - Formula: floor((now - password_reset_date) / (24*60*60*1000))
   - If > 90 days:
     a. Set is_expired = false
     b. Return "Your password has expired. Kindly proceed to reset."
   |
   v
6. RESET password_tried to 0
   |
   v
7. GENERATE JWT TOKEN
   - Payload: { id, fullname, email, role, is_active, level,
                designation, employee, remember_token, department, license }
   - Secret: process.env.JWT_ENCRYPTION
   - Expiry: 365 days
   |
   v
8. LOG LOGIN
   - Create LoginLogoutDetails { userId, type: 'Login' }
   |
   v
9. RETURN RESPONSE
   {
     success: true,
     token: "<jwt>",
     user: { id, fullname, email, role, is_active, level,
             designation, employee, remember_token, qa_super_level, license }
   }
```

---

## JWT Token Structure

**Generation:** `jwt.sign(payload, process.env.JWT_ENCRYPTION, { expiresIn: "365d" })`

**Payload fields:**

| Field | Source | Purpose |
|---|---|---|
| `id` | `user.id` | Primary user identifier |
| `fullname` | `first_name + " " + last_name` | Display name for audit trails |
| `email` | `user.email` | Notification target |
| `role` | `user.role` | RBAC checks (`admin`, `user`, `qa_super_user`, etc.) |
| `is_active` | `user.is_active` | Account status at time of token creation |
| `level` | `user.level` | Permission tier (1, 2, 3) |
| `designation` | `user.designation_id` | Designation FK |
| `employee` | `user.employee_id` | Employee identifier (login username) |
| `remember_token` | `user.remember_token` | Department session validation |
| `department` | `user.department` | Department name |
| `license` | `licenseCheck` | License { id, is_active, valid_until } |

**Token is returned as a Bearer token** — client must send `Authorization: Bearer <token>` on protected requests.

---

## Middleware Stack

### 1. `verify_auth` (Primary JWT Gate)

**File:** `app/middelwares/verify_auth.js`

```js
const verify_auth = (req, res, next) => {
  try {
    const token = req.headers.authorization.split(' ')[1];  // Extract Bearer token
    const decoded = jwt.verify(token, process.env.JWT_ENCRYPTION);  // Verify signature + expiry
    if (decoded) {
      req.user_data = decoded;  // Attach decoded payload for downstream use
      next();
    }
  } catch (err) {
    return res.status(403).json({ message: 'Authentication failed.' });
  }
};
```

- Applied globally at `index.js` line 85 for all routes below
- Returns **403** on invalid/expired/missing token

### 2. `role_admin` (Admin + QA Super User Gate)

```js
const role_admin = async (req, res, next) => {
  const { role, id } = req.user_data;
  let check_role = await user.findOne({ where: { id } });
  if (check_role.role == 'admin' || check_role.role == 'qa_super_user') {
    next();
  } else {
    res.status(403).json({ message: 'Authorization denied, only admin can access' });
  }
};
```

- Re-queries the database (does NOT trust the JWT role alone)
- Allows: `admin` and `qa_super_user`
- Used on: `/admin/user-details`, `/admin/user-update`, `/admin/user-delete`, `/admin/user-create`, `/admin/user-matrix`

### 3. `role_user` (Standard User Gate)

```js
const role_user = async (req, res, next) => {
  const { role } = req.user_data;
  let check_role = await user.findOne({ where: { id } });
  if (check_role.role == 'user') {
    next();
  } else {
    res.status(403).json({ message: 'Authorization denied, only user can access' });
  }
};
```

- Restricts to role `user` only

### 4. `department_role` (Department Token Validation)

```js
const department_role = async (req, res, next) => {
  const token = req.headers.authorization.split(' ')[1];
  const decoded = jwt.decode(token, { complete: true });  // NOTE: decode, not verify
  if (decoded) {
    let check_role = await user.findOne({ where: { email: decoded.payload.email } });
    if (check_role.remember_token && check_role.remember_token === token) {
      req.user_data = decoded;
      next();
    } else {
      return res.status(403).json({ message: 'The remember_token is not matching' });
    }
  }
};
```

- Uses `jwt.decode()` (not `verify`) — decodes without signature verification
- Compares the **entire token** against `user.remember_token` stored in DB
- Used on: `/pod`, `/poi`, `/mrn-dom`, `/mrn-int`, `/purchase-returns`, `/eqp_integration`, `/eqp_area`
- These are pre-`verify_auth` routes with their own independent token validation

---

## Role-Based Access Control (RBAC)

### Roles in the System

| Role | Description | Login Requires License? |
|---|---|---|
| `admin` | Full system admin, user management | No |
| `qa_super_user` | QA department superuser, elevated permissions | Yes |
| `qa_initiator` | Can initiate QA processes (e.g., finished goods) | Yes |
| `user` | Standard operational user | Yes |
| `prod_user` | Production department user | Yes |
| `prod_ebmr` | Production eBMR user | Yes |

### Levels

Users have a `level` field (integer, default: 1) that works in combination with role:

| Level | Typical Access |
|---|---|
| 1 | Basic/limited access |
| 2 | Mid-tier access (cold_chamber, stretch_wrapping, finished_goods) |
| 3 | Full operational access (TRHMR, usage_cleaning, 70% prep, etc.) |

### Access Resolution

Authorization is resolved by **role + level combination**:

```
role = "user" + level = 3  --> Full operational access
role = "user" + level = 2  --> Mid-tier (cold chamber, stretch wrapping, finished goods)
role = "qa_initiator" + level = 2/3 --> Finished goods + QA initiation
role = "admin" --> Full system access, no license check
role = "qa_super_user" --> Admin-level routes + QA operations
```

---

## User Matrix - Permission System

**Model:** `user_matrix`

The `user_matrix` table defines granular feature-level permissions per `role_type` + `level` combination.

**Endpoint:** `GET /api/v1/admin/user-matrix?role=<role>&level=<level>`

**Permission flags (all STRING type):**

| Permission | Controls Access To |
|---|---|
| `user_management` | Admin user CRUD |
| `material_receipt` | MRN creation and receipt |
| `material_movement` | Material transfers |
| `sampling_trf` | Sampling / TRF |
| `material_status_update` | QC status changes |
| `dispensing_request` | Dispensing request creation |
| `dispensing_execution` | Dispensing operations |
| `dispensing_approval` | Dispensing approval workflow |
| `stock_hold_release` | Stock hold/release operations |
| `stock_deduction` | Stock deduction operations |
| `stock_reconciliation` | Stock reconciliation |
| `material_return_request` | Material return workflow |
| `code_to_code_transfer` | Code transfer operations |
| `vendor_material_master` | Vendor and material masters |
| `line_clearance` | Line clearance operations |
| `wip_weighing_labels` | WIP and weighing labels |
| `bom_creation` | BOM / product master creation |
| `equipment_master` | Equipment master management |
| `ebmr_configuration` | eBMR configuration |
| `ebmr_execution` | eBMR execution |
| `ebmr_event_review` | eBMR event review |
| `ebmr_approval` | eBMR approval workflow |
| `electronic_logbooks` | Electronic log book access |
| `reports` | Report generation |
| `audit_trail` | Audit trail viewing |
| `material_verification` | Material verification |

This matrix is typically fetched at login/session and used by the **frontend** to show/hide features.

---

## Password Security Policies

### Hashing

- **Algorithm:** bcrypt
- **Salt rounds:** 10
- **Applied at:** Model setter (auto-hashes on create/update in `users.js` and `user_password.js`)

```js
// users.js model
password: {
  type: DataTypes.STRING,
  set(value) {
    this.setDataValue("password", bcrypt.hashSync(value, 10));
  },
  validate: { len: [8, 500] },
}
```

### Complexity Requirements

**Regex:** `/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/`

| Rule | Requirement |
|---|---|
| Minimum length | 8 characters |
| Uppercase letter | At least 1 |
| Lowercase letter | At least 1 |
| Digit | At least 1 |
| Special character | At least 1 of: `@$!%*?&` |

**Enforced at:** `change_password` controller function.

**Note:** The `update_password` (forgot password) flow does NOT enforce the regex — it only checks password reuse.

### Password Reuse Prevention

Last **3 passwords** are checked before allowing a new password:

```js
const lastThreePasswords = await db.user_password.findAll({
  where: { user_id: user.id },
  order: [['id', 'DESC']],
  limit: 3,
});

const is_password_exist = lastThreePasswords.some(passwordData =>
  bcrypt.compareSync(new_password, passwordData.password)
);
```

Each password change creates a new `user_password` record, building the history.

### Password History Table

| Field | Type | Purpose |
|---|---|---|
| `password` | STRING | bcrypt-hashed password |
| `user_id` | INTEGER | FK to users |

---

## Forgot Password / Reset Flow

**Step 1 - Request Reset Email**

**Endpoint:** `PATCH /api/v1/user/send-email` (unauthenticated)

**Input:** `{ email (employee_id) }`

```
1. Find user WHERE employee_id = email AND is_active = true
   |-- NOT FOUND --> "No account found for the email provided."
   |
   v
2. Generate token: crypto.randomBytes(32).toString("hex")
   (64-character hex string)
   |
   v
3. Update user:
   - password_reset_email_sent_at = new Date()
   - reset_password_token = token
   |
   v
4. Create change_pwd_emails record { email, employee, user_id }
   |
   v
5. Send email via Nodemailer (Office 365 SMTP)
   - Template: app/email/password_reset.hbs
   - Contains: receiver name, reset token, employee_id, URL
   |
   v
6. Return "email sent"
```

**Step 2 - Submit New Password**

**Endpoint:** `PATCH /api/v1/user/update-password` (unauthenticated)

**Input:** `{ email (employee_id), new_password, token }`

```
1. Find user WHERE employee_id = email
                AND is_active = true
                AND reset_password_token = token
   |
   v
2. Check password reuse (last 3 passwords via bcrypt compare)
   |-- REUSED --> "New password already Exist"
   |
   v
3. Validate 15-minute window:
   Date.now() - password_reset_email_sent_at <= 15 * 60 * 1000
   |-- EXPIRED --> "make sure you are changing within the timeline"
   |
   v
4. Update user:
   - password = new_password (auto-hashed by model)
   - password_reset_date = new Date()
   - reset_password_token = null (invalidates token)
   |
   v
5. Create user_password history record
   |
   v
6. Create user_change_log:
   { field: "forgot_password", from: '[REDACTED]', to: '[REDACTED]',
     action_type: 'password_change' }
   |
   v
7. Return "password updated successfully"
```

**Key security details:**
- Token is single-use (cleared after successful reset)
- 15-minute window is hardcoded
- Token must match exactly (stored in DB)
- Password reuse check runs before time window check

---

## Change Password Flow

**Endpoint:** `PATCH /api/v1/user/change-password` (unauthenticated - note: no verify_auth in route file)

**Input:** `{ current_password, new_password, confirm_password, user_id (employee_id) }`

```
1. Find user WHERE employee_id = user_id
   |
   v
2. Fetch last 3 passwords from user_password table
   |
   v
3. Validate password complexity regex
   |-- FAIL --> "must contain minimum 8 characters, at least one uppercase..."
   |
   v
4. Verify current password (bcrypt.compareSync)
   |-- FAIL --> "current password did not match"
   |
   v
5. Check new password against last 3 passwords
   |-- REUSED --> "Password you're trying to use has already been used before."
   |
   v
6. Update user:
   - password = new_password (auto-hashed)
   - password_reset_date = new Date()
   - is_expired = true (marks as NOT expired)
   |
   v
7. Create user_password history record
   |
   v
8. Create user_change_log:
   { field: "change_password", from: '[REDACTED]', to: '[REDACTED]',
     action_type: 'password_change' }
   |
   v
9. Return "password changed successfully"
```

---

## Account Lockout (Max Retries)

### How It Works

The system tracks failed login attempts per user via `password_tried` (INTEGER field on `users` table).

```
Failed Login Attempt:
  |
  v
1. Record in failed_logins table { email }
  |
  v
2. Increment password_tried: current + 1
  |
  v
3. Check threshold:
   |
   |-- password_tried < 3:
   |     Return 401: "You have made N unsuccessful attempt(s).
   |     A maximum of 3 login attempts are permitted."
   |
   |-- password_tried >= 3 (on 3rd failure):
         a. Set is_active = false (DEACTIVATES ACCOUNT)
         b. Return 401: "Your account has been deactivated due to
            3 incorrect password attempts."
```

### Account Recovery After Lockout

An admin must reactivate the account. When an admin updates `is_active` back to `true`:

```js
// admin.controller.js - update_user and delete_user
if (is_active == true) {
  await users.update({ password_tried: 0 }, { where: { id: user_id } });
}
```

This resets `password_tried` to 0, unlocking the account.

### Already-Locked Check

If a user whose `password_tried == 3` attempts to log in again, the check at the top of the signin flow immediately returns:

```
401: "Your account has been deactivated due to 3 incorrect password attempts.
      To regain access, please reach out to your support team."
```

### Tracking Table

The `failed_logins` table stores every failed attempt with a timestamp for audit/reporting:

```
GET /api/v1/failed-logins/inventoryReport?from_date=<date>&to_date=<date>
```

---

## Password Expiry (90-Day Rule)

After a successful password validation during signin, the system checks how many days since the last password change:

```js
const dateDifference = Math.floor(
  (new Date() - new Date(user.password_reset_date)) / (24 * 60 * 60 * 1000)
);

if (dateDifference > 90) {
  await users.update({ is_expired: false }, { where: { employee_id } });
  return res.status(200).send({
    success: false,
    message: "Your password has expired. Kindly proceed to reset your password."
  });
}
```

### Key behaviors:

- The `is_expired` field is set to `false` when the password has expired
- The `is_expired` field is set to `true` when the password is successfully changed (confusing naming but intentional)
- `password_reset_date` is set:
  - On user creation (via `beforeSave` hook)
  - On password change (`change_password`)
  - On password reset (`update_password`)
- Login is **blocked** until the user resets their password
- The check occurs **after** successful password validation but **before** token generation

---

## License Validation

**Model:** `license` table with fields: `license_key`, `valid_until`, `is_active`, `last_checked`

### Check during signin:

```js
let licenseCheck = await license.findOne({
  attributes: ['is_active', 'valid_until', 'id'],
  where: { is_active: true },
});

if (user.role !== 'admin') {
  if (!licenseCheck) {
    return res.status(200).send({ success: false, message: "Your license is expired" });
  }
}
```

- **Admin users bypass license check entirely**
- Non-admin users cannot log in if no active license exists
- License data is included in the JWT payload and returned to the client
- License management is done via `/api/v1/license` routes (protected)

---

## Logout & Inactive Session Tracking

### Standard Logout

**Endpoint:** `GET /api/v1/user/logout` (requires `verify_auth`)

```js
await db.LoginLogoutDetails.create({
  type: 'Logout',
  userId: user_data.id,
});
```

### Inactive/Idle Logout

**Endpoint:** `GET /api/v1/user/inactive_logout` (requires `verify_auth`)

```js
await db.LoginLogoutDetails.create({
  type: status,         // Custom status string from client
  userId: user_data.id,
  inactive_date: new Date()
});
```

This is triggered by the frontend when it detects user inactivity (idle timeout).

### LoginLogoutDetails Model

| Field | Type | Purpose |
|---|---|---|
| `isActive` | BOOLEAN | Default true |
| `type` | STRING | 'Login', 'Logout', or custom status |
| `userId` | INTEGER | FK to users |
| `inactive_date` | DATE | Timestamp of inactivity event |

---

## Form-Level Verification (Initiate Verification)

**Endpoint:** `POST /api/v1/initiate-verification` (requires `verify_auth`)

**Controller:** `initiate_verification.controller.js`

This provides a **second layer of authentication** for specific forms. The user must re-enter their credentials before performing sensitive operations.

```
Input: { common_unique_id, email (employee_id), password, form }
  |
  v
1. Find user by employee_id
  |
  v
2. bcrypt.compare password
  |-- FAIL --> 400 "authentication failed"
  |
  v
3. Check form-specific role + level requirements:

   FORM                        REQUIRED ROLE           REQUIRED LEVEL
   -----------------------------------------------------------------------
   trhmr                       user                    3
   usage_cleaning              user                    3
   70%_preparation             user                    3
   cold_chamber                user                    2 or 3
   stretch_wrapping            user                    2 or 3
   finished_goods_checklist    user or qa_initiator    2 or 3

  |-- AUTHORIZED:
  |     Create initiate_verification_tokens { common_unique_id }
  |     Return "verification success"
  |
  |-- UNAUTHORIZED:
        Return 400 "authentication failed"
```

This ensures that even if a user is logged in, specific operational forms require fresh credential verification with appropriate role/level checks.

---

## Department Role Middleware (remember_token)

The `department_role` middleware provides an alternative authentication path for specific modules that operate outside the standard `verify_auth` flow.

### Routes using department_role:

These routes are registered **before** the `verify_auth` middleware in `index.js` (lines 68-74):

- `/pod` - Purchase Order Domestic
- `/poi` - Purchase Order International
- `/mrn-dom` - MRN Domestic
- `/mrn-int` - MRN International
- `/purchase-returns` - Purchase Returns
- `/eqp_integration` - Equipment Integration
- `/eqp_area` - Equipment Area Master

### How it works:

1. Extracts JWT token from Authorization header
2. **Decodes** (NOT verifies) the token using `jwt.decode()`
3. Looks up user by decoded email
4. Compares the **entire token string** against `user.remember_token` in the database
5. If they match, proceeds with `req.user_data = decoded`

### remember_token lifecycle:

- Stored in `users.remember_token` (VARCHAR 500)
- Included in the JWT payload at signin
- Used as a session binding mechanism for department-specific routes
- The token in DB must exactly match the token being sent by the client

---

## Admin - User Management

**Route file:** `app/routes/admin.route.js`

```
GET  /admin/user-list     --> No role_admin (just verify_auth from index.js)
-- role_admin middleware applied below --
GET  /admin/user-details  --> role_admin required
PATCH /admin/user-update  --> role_admin required
PATCH /admin/user-delete  --> role_admin required (soft delete)
POST /admin/user-create   --> role_admin required
GET  /admin/user-matrix   --> role_admin required
```

### Create User (Admin)

```
Input: { first_name, last_name, middle_name, date_of_birth, email,
         employee_id, department, level, designation_id, qa_dept[] }
  |
  v
1. Check employee_id uniqueness
   |-- EXISTS --> 400 "User Employee already in use"
   |
   v
2. Look up department from depatment_authorities
  |
  v
3. Create user with:
   - Default password: 'Graviti@123'
   - role = department.dept_name
   - department = department.dept_name
   - created_by = admin's employee_id
   - date_created = now
   |
   v
4. Log all initial fields to user_change_log (action_type: 'create')
   Fields logged: first_name, middle_name, last_name, email,
                  employee_id, role, department, level, designation_id
   |
   v
5. If qa_dept array provided:
   For each { role, permission } in qa_dept:
     - Look up department name
     - Create qa_superuser_level { department_id, level, department, user_id }
   |
   v
6. Return 201 { success: true, data: user }
```

### Update User (Admin)

- Compares every field against existing values
- Logs each changed field to `user_change_log` with: field, from, to, changed_by_id, action_type: 'update'
- If reactivating (`is_active: true`), resets `password_tried` to 0

### Delete/Deactivate User (Admin)

- Soft delete: toggles `is_active` flag
- Logs to `user_change_log` with action_type: 'delete'
- If reactivating, resets `password_tried` to 0

---

## Audit Trail (21 CFR Part 11 Compliance)

The system maintains comprehensive audit logs for regulatory compliance.

### user_change_log

Tracks ALL changes to user records:

| Field | Type | Purpose |
|---|---|---|
| `field` | TEXT | Field name changed (e.g., 'first_name', 'role', 'change_password') |
| `from` | TEXT | Previous value (or '[REDACTED]' for passwords) |
| `to` | TEXT | New value (or '[REDACTED]' for passwords) |
| `user_log_id` | INTEGER | FK: user being modified (subject) |
| `changed_by_id` | INTEGER | FK: admin/user who made the change (operator) |
| `change_by` | STRING | Legacy: employee_id of operator |
| `action_type` | STRING | 'create', 'update', 'delete', 'password_change' |
| `update_date` | DATE | When the change occurred |
| `is_active` | BOOLEAN | Soft delete flag |

### Events logged:

| Event | action_type | Fields Logged |
|---|---|---|
| Admin creates user | `create` | All initial field values (from: '', to: value) |
| Admin updates user | `update` | Each changed field individually |
| Admin deactivates/reactivates | `delete` | is_active field change |
| User changes own password | `password_change` | field: 'change_password', values: '[REDACTED]' |
| User resets via forgot password | `password_change` | field: 'forgot_password', values: '[REDACTED]' |

### LoginLogoutDetails

Tracks every login and logout event with timestamp and user ID.

### failed_logins

Records every failed login attempt with email and timestamp.

### change_pwd_emails

Records every password reset email sent.

### designation_log

Tracks changes to designations and departments:

| Field | Type |
|---|---|
| `field` | STRING |
| `from` | STRING |
| `to` | STRING |
| `user_id` | INTEGER |
| `designation_id` | INTEGER |
| `department_id` | INTEGER |
| `update_date` | DATE |
| `transaction_type` | STRING ('Designation' or 'Department') |

---

## Database Models Reference

### Entity Relationship Diagram

```
users (central table)
  |
  |-- 1:N --> user_password          (password history for reuse prevention)
  |-- 1:N --> LoginLogoutDetails     (login/logout audit log)
  |-- 1:N --> user_change_log        (as user_log_id: subject of change)
  |-- 1:N --> user_change_log        (as changed_by_id: operator)
  |-- 1:N --> qa_superuser_level     (multi-department QA roles)
  |-- 1:N --> designation_log        (designation change history)
  |-- 1:N --> active_session         (session tracking)
  |-- N:1 --> depatment_authorities  (department_id FK)
  |-- N:1 --> designated_authorities (designation_id FK)
  |
  failed_logins                      (standalone: email + timestamp)
  change_pwd_emails                  (standalone: email + employee + timestamp)
  license                            (standalone: system-wide license)
  user_matrix                        (standalone: role_type + level -> permissions)
  initiate_verification_tokens       (standalone: common_unique_id for form auth)
```

### Users Table - Complete Schema

| Field | Type | Default | Purpose |
|---|---|---|---|
| `first_name` | STRING | required | |
| `middle_name` | STRING | | |
| `last_name` | STRING | required | |
| `name` | STRING | | Legacy/display |
| `email` | STRING | unique, email validated | |
| `password` | STRING | bcrypt hashed, min 8 | |
| `role` | STRING | | admin, user, qa_super_user, etc. |
| `level` | INTEGER | 1 | Permission tier |
| `is_active` | BOOLEAN | true | Account active flag |
| `is_verified` | BOOLEAN | | Email verification flag |
| `is_expired` | BOOLEAN | true | true = not expired, false = expired |
| `employee_id` | STRING | | Login identifier |
| `designation_id` | INTEGER | | FK to designated_authorities |
| `department` | STRING | | Department name |
| `department_id` | INTEGER | | FK to depatment_authorities |
| `date_of_birth` | DATEONLY | | |
| `created_by` | STRING | | Employee ID of creator |
| `date_created` | DATE | | |
| `password_reset_date` | DATE | | Last password change date |
| `password_reset_email_sent_at` | DATE | | Reset email timestamp |
| `reset_password_token` | STRING | | Current reset token |
| `password_tried` | INTEGER | | Failed attempt counter (0-3) |
| `password_tried_date` | DATEONLY | | Date of last failed attempt |
| `remember_token` | STRING(500) | | Department role session token |

---

## Route Registration & Protection Map

### Unauthenticated Routes (No middleware)

| Route | Method | Handler | Notes |
|---|---|---|---|
| `/user/signup` | POST | `user.signup` | User self-registration |
| `/user/signin` | POST | `user.signin` | Login |
| `/user/send-email` | PATCH | `user.send_password_reset_email` | Forgot password |
| `/user/update-password` | PATCH | `user.update_password` | Reset password with token |
| `/user/change-password` | PATCH | `user.change_password` | Change password |
| `/user/fetch_file` | GET | `fetch_cont.fetchFile` | File download |
| `/user/material-details` | GET | `container.get_containers` | Public material data |
| `/user/upload-materials` | POST | `preinspection.upload` | Material bulk upload |
| `/user/upload-products` | POST | `product.upload_products` | Product bulk upload |
| `/user/upload-bin` | POST | `bin.upload` | Bin bulk upload |
| `/user/update-batches` | GET | `batch.update_all_materials` | Batch material update |
| `/user/batch-pdfs-regenarate` | GET | `batch.create_pdf` | PDF regeneration |

### Department Role Routes (department_role middleware)

| Route Prefix | Middleware |
|---|---|
| `/pod` | `department_role` (in route file) |
| `/poi` | `department_role` (in route file) |
| `/mrn-dom` | `department_role` (in route file) |
| `/mrn-int` | `department_role` (in route file) |
| `/purchase-returns` | `department_role` (in route file) |
| `/eqp_integration` | `department_role` (in route file) |
| `/eqp_area` | `department_role` (in route file) |

### Authenticated Routes (verify_auth middleware - line 85)

All routes registered after line 85 in `index.js` require a valid JWT token. This includes 100+ route modules covering the entire operational system.

### Admin Routes (verify_auth + role_admin)

| Route | Method | Handler |
|---|---|---|
| `/admin/user-list` | GET | `admin.user_list` (verify_auth only) |
| `/admin/user-details` | GET | `admin.user_details` (+ role_admin) |
| `/admin/user-update` | PATCH | `admin.update_user` (+ role_admin) |
| `/admin/user-delete` | PATCH | `admin.delete_user` (+ role_admin) |
| `/admin/user-create` | POST | `admin.create_user` (+ role_admin) |
| `/admin/user-matrix` | GET | `admin.get_user_matrix` (+ role_admin) |

---

## Security Summary

### Implemented Security Features

| Feature | Implementation | Location |
|---|---|---|
| Password hashing | bcrypt, 10 salt rounds | `users.js`, `user_password.js` model setters |
| JWT authentication | Bearer token, 365-day expiry | `verify_auth.js`, `user.controller.js` |
| Account lockout | 3 failed attempts = deactivation | `signin` function |
| Password expiry | 90-day forced reset | `signin` function |
| Password complexity | Regex: 8+ chars, upper, lower, digit, special | `change_password` function |
| Password reuse prevention | Last 3 passwords checked | `change_password`, `update_password` |
| Password reset token | 64-char hex, 15-minute window | `send_password_reset_email`, `update_password` |
| License validation | Active license required for non-admin | `signin` function |
| Role-based access | admin, qa_super_user, user roles | `role_admin`, `role_user` middleware |
| Level-based access | 3-tier permission levels | `user_matrix`, `initiate_verification` |
| Form re-authentication | Credential re-entry for sensitive ops | `initiate_verification.controller.js` |
| Login/logout audit | Every session tracked | `LoginLogoutDetails` model |
| Failed login tracking | Every failure recorded | `failed_logins` model |
| User change audit | 21 CFR Part 11 compliant | `user_change_log` model |
| Password change audit | All changes logged (redacted) | `user_change_log` |
| Soft deletes | `is_active = false` instead of DELETE | All user operations |
| Department token binding | remember_token session validation | `department_role` middleware |

### Authentication Decision Flow (Complete)

```
REQUEST
  |
  v
Is route before verify_auth in index.js?
  |
  YES --> Is it a department_role route? (/pod, /poi, /mrn-dom, etc.)
  |         |-- YES --> department_role middleware (token + remember_token match)
  |         |-- NO  --> Unauthenticated (signin, signup, reset, etc.)
  |
  NO --> verify_auth middleware (JWT verify)
           |
           |-- FAIL --> 403 "Authentication failed"
           |-- PASS --> req.user_data populated
                          |
                          v
                        Is route under /admin?
                          |-- YES --> role_admin check
                          |             |-- admin or qa_super_user --> ALLOWED
                          |             |-- other --> 403 "Authorization denied"
                          |-- NO  --> ALLOWED (any authenticated user)
                                        |
                                        v
                                      Frontend checks user_matrix
                                      for feature-level permissions
```
