# Authentication & Roles

## JWT Authentication

- Token format: standard JWT signed with `JWT_SECRET`.
- TTL: controlled by `AUTH_TOKEN_EXPIRES_IN` env var (default `"7d"`), defined in `src/constants/auth.js`.
- Token is stored as a cookie named `token` on the frontend.
- **Critical alignment rule**: the frontend cookie expiry and the backend JWT TTL must stay in sync. A mismatch causes stale UI sessions where the cookie still exists but the backend returns 401.

### 401 Handling (Frontend)

The RTK Query base API clears auth state and redirects to `/login` when a 401 is received on a request that included a valid token. This prevents ghost sessions.

---

## Roles

Defined in Prisma schema as enum `Role`:

| Role | Description |
|---|---|
| `SUPER_ADMIN` | Full access to everything |
| `DB_ADMIN` | Auto-granted when a route lists `SUPER_ADMIN` (via `normalizeAuthorizedRoles`) |
| `ACCOUNT_ADMIN` | Must be listed **explicitly** per route; not auto-expanded from `SUPER_ADMIN` |
| `PRADESHIK_SANT` | Assigned to one or more Pradeshes; view/manage those pradeshes |
| `SEVA_DEPT_ACCOUNT` | Seva department accounts |
| `PRADESH_ADMIN` | Manages a single Pradesh |
| `MANDAL_ADMIN` | Manages a single Mandal |
| `KARYAKARTA` | Field-level karyakarta; collects cash, submits to pradesh |
| `PERSONAL` | End user (registrant); phone-based login |

### Role expansion in routes

`normalizeAuthorizedRoles()` in `src/constants/roles.js` only expands **`DB_ADMIN`** when a route includes `SUPER_ADMIN`:

```js
export const SUPER_ADMIN_EQUIVALENT_ROLES = ["SUPER_ADMIN", "DB_ADMIN"];

export const normalizeAuthorizedRoles = (roles = []) => {
  const normalizedRoles = new Set(roles);
  if (normalizedRoles.has("SUPER_ADMIN")) {
    SUPER_ADMIN_EQUIVALENT_ROLES.forEach((role) => normalizedRoles.add(role));
  }
  return [...normalizedRoles];
};
```

**Implications:**
- Route `authorizeRole(["SUPER_ADMIN", "ACCOUNT_ADMIN"])` → allows SUPER_ADMIN, DB_ADMIN, ACCOUNT_ADMIN.
- Route `authorizeRole(["SUPER_ADMIN"])` only → allows SUPER_ADMIN and DB_ADMIN, **not** ACCOUNT_ADMIN.
- `ACCOUNT_ADMIN` never inherits access from `SUPER_ADMIN` alone — add it to the array when that role should have access.

**Example:** `PATCH /api/admin/registrations/:registrationId/details` lists `["SUPER_ADMIN", "ACCOUNT_ADMIN"]` — DB_ADMIN can call it; ACCOUNT_ADMIN can; MANDAL_ADMIN cannot.

> **Note:** Some endpoints (e.g. notifications) document extra ACCOUNT_ADMIN behavior. If 403s appear after deploy, verify the running process was restarted and the route’s role array matches docs.

---

## OTP Policy

All OTP behavior is centralized in `src/services/otpPolicyService.js`.

```js
export const OTP_POLICY = Object.freeze({
  supportPhone: "+916351157828",
  registration: {
    reuseHours: 24,
    maxRequests: 5,
    maxVerifyAttempts: 5,
  },
  passwordReset: {
    reuseHours: 24,
    maxRequests: 5,
    maxVerifyAttempts: 5,
  },
});
```

- OTP records are stored in `RegistrationOtp` and `PersonalPasswordResetOtp` tables.
- Both tables track `attemptCount`, `requestCount`, `lastRequestedAt`, `expiresAt`, `verifiedAt`, `usedAt`.
- The reuse window prevents the same phone from requesting a new OTP within the TTL.

**Registration OTP verify** (`POST /api/registrations/otp/verify`):

- Within the 24-hour validity window, the **same OTP code can be verified again** even if it was previously marked `usedAt` (e.g. registration abandoned mid-flow).
- On successful verify: sets `verifiedAt`, clears `usedAt`, resets `attemptCount`.
- Only fails when: no OTP exists, code is wrong, too many wrong attempts, or `expiresAt` has passed.

---

## PRADESHIK_SANT Role

Added as a new role alongside a `PradeshikSantPradeshAssignment` many-to-many table:

```prisma
model PradeshikSantPradeshAssignment {
  id        Int
  userId    Int       -- User with PRADESHIK_SANT role
  pradeshId Int       -- Pradesh they are assigned to
}
```

A single PRADESHIK_SANT user can be assigned to multiple Pradeshes. This allows cross-pradesh visibility without full PRADESH_ADMIN access.

---

## Prisma Migration Note for Role Enum

Adding a new value to the `Role` enum (e.g. `PRADESHIK_SANT`) requires:

```sql
ALTER TYPE "Role" ADD VALUE 'PRADESHIK_SANT';
```

This must be run by the **owner** of the enum type in the database. The production app user is not the enum owner, so:

- Do **not** use `prisma db push` against production.
- Generate a migration locally and apply it with an owner-capable `DATABASE_URL` using `npm run prisma:migrate:prod`.
- Or have the DBA run the generated SQL manually.

---

## Karyakarta account — Pradesh / Mandal relocation

> **Last updated:** June 2026

**UI:** Accounts → Karyakarta Accounts (`PATCH /api/admin/accounts/:id`)

Super Admin and Account Admin can change Pradesh and Mandal when editing an existing Karyakarta account (without changing Haridham ID).

**Backend behavior** (`adminController.js` → `updateAdminAccount`, role `KARYAKARTA`):

1. Validates selected Mandal belongs to selected Pradesh.
2. Updates `Karyakarta.mandalId`.
3. Updates the linked `User` (`pradeshId`, `mandalId`).
4. Cascades to all active registrations where `karyakartaHaridhamId` matches: updates `Registration.mandalId` and `Registration.pradeshId`.
5. Cascades to linked PERSONAL `User` rows for those registrations (`mandalId`, `pradeshId`).

**Response:** includes `message` and `updatedRegistrations` count when location changes.

**Not cascaded:** `district` on registrations; pending cashflow transactions (karyakarta Haridham ID unchanged).

---
