# Upgrade Plan — Admin Security, Multi-Admin Auth & Observability

**Status:** Implementation in progress (Phases B–E + infra baseline shipped for govt demo)  
**Applies to:** `admin/` application + shared MySQL (`ujjain_khumbh`) + production hardening of `website/`  
**Event window:** Visitor arrival dates **1 April 2028 – 15 June 2028**  
**Date:** 28 July 2026  
**Last updated:** 28 July 2026 — security core live

---

## 1. Objective

Upgrade the separate **Admin** application and shared platform so that:

1. **Multiple admins** authenticate from the **database** (not a single env password).
2. **All admin activity** is audited and reviewable.
3. Operators can **observe services, memory, disk, and network (SNMP)** from the admin panel.
4. **Website / application logs** and a **security audit** surface are available for threat detection and response.
5. **HTTP / application network activity** is captured so **unwanted requests, abuse, and unexpected load** show up clearly and can be blocked or rate-limited.
6. **Passes and identity data** are stored and logged in a **production-safe** way: no full Aadhaar in DB or logs; pass payloads encrypted; identity confirmation via **Aadhaar last-4 + mobile OTP**.

This document is the agreed upgrade blueprint before coding.

---

## 2. Current state (baseline)

| Area | Today | Gap |
| --- | --- | --- |
| Admin auth | Single `ADMIN_PASSWORD` in `.env.local` + JWT cookie | No multi-user, no roles, not DB-driven |
| Admin activity | None | Cannot answer who did what / when |
| Visitor Aadhaar | Salted SHA-256 hash + `aadhaar_last4`; full number not stored in `visitors` | Hash salt reuses `JWT_SECRET`; production log hygiene not enforced; QR/`qr_payload` is plaintext JSON |
| OTP | Hashed OTP in `otp_sessions`; mobile stored plaintext | Acceptable for verification; needs production log redaction |
| Funnel audit | `audit_logs` for registration steps | Not exposed in admin; no security-threat scoring |
| Observability | None in admin | No CPU/memory/disk/SNMP/service health |
| HTTP / request network activity | None (no request telemetry, no abuse dashboard) | Cannot see unwanted traffic, scrapers, floods, or sudden load |
| Website logs | Server stdout only | No central viewer in admin |
| Apps | `website` (public) + `admin` (separate app, port 3002) | Correct split — keep it |

---

## 3. Target principles

1. **Least privilege** — roles (`superadmin`, `operator`, `auditor`, `viewer`) limit what each admin can see/do.
2. **No full Aadhaar at rest** — never persist the 12-digit number; never print it in production logs.
3. **Confirm, don’t reconstruct** — identity proof = Verhoeff-valid Aadhaar used once to derive hash + last-4, then **mobile OTP** seals the session.
4. **Encrypt sensitive pass material** — QR / pass payloads encrypted at rest; decrypt only for authorized pass rendering / gate scan paths.
5. **Observable & auditable** — every admin action and every security-relevant website event is queryable in admin.
6. **See bad traffic early** — every public request path contributes to network-activity telemetry so unwanted load, bots, and attacks are visible and actionable.
7. **Separate apps, shared DB** — `website` and `admin` remain separate deployments; share MySQL and crypto/key conventions.

---

## 4. Identity & pass security upgrade

### 4.1 Aadhaar handling (production)

```
Aadhaar (12 digits, in memory only)
        │
        ├─► Verhoeff validate
        ├─► aadhaar_hash = HMAC-SHA256(AADHAAR_PEPPER, aadhaar)   // dedicated pepper, not JWT_SECRET
        ├─► aadhaar_last4 = last 4 digits only
        └─► discard full Aadhaar from memory ASAP
```

**Rules:**

| Rule | Detail |
| --- | --- |
| DB | Store only `aadhaar_hash` + `aadhaar_last4`. Never store full Aadhaar. |
| Logs (production) | Log only `****XXXX` (last-4) or hash prefix (e.g. first 8 of hash). Never log full Aadhaar, OTP plaintext, or session tokens. |
| Dev | `OTP_DEV_MODE=true` may show OTP on UI; disable in production. |
| Lookup | Duplicate check and OTP session keyed by `aadhaar_hash` only. |
| User confirmation | After OTP verify, registration JWT binds `{ aadhaar_hash, mobile, sid }` — mobile must match on submit. |

**Key derivation note (requested approach):**  
Operational confirmation for a visitor uses **last-4 of Aadhaar** (for human/agent verification and display masking) **plus OTP-confirmed mobile**.  
Encryption of pass payload should **not** use last-4 alone as the key (too weak / guessable). Instead:

- **Display / verify:** last-4 + OTP-confirmed mobile (already in flow).
- **Encrypt pass payload:** AES-256-GCM with a server-side `PASS_ENCRYPTION_KEY` (32-byte), optional AAD = `pass_no` + `aadhaar_last4` so ciphertext is bound to that pass identity without embedding full Aadhaar.

### 4.2 Encrypted pass storage

| Field | Current | Target |
| --- | --- | --- |
| `qr_payload` | Plaintext JSON | **AES-256-GCM ciphertext** (base64) + `iv` / `auth_tag` (or single sealed blob) |
| Pass page / QR API | Reads plaintext | Decrypts server-side with `PASS_ENCRYPTION_KEY`, then renders QR |
| Gate / offline | N/A | Optional: short-lived signed QR token; avoid long-lived plaintext PII in QR |

**Suggested sealed blob format:**

```text
v1:<iv_b64>:<ciphertext_b64>:<tag_b64>
```

**Plaintext inside (minimal):**

```json
{
  "pass_no": "KM2028-000123",
  "name": "…",
  "arrival": "2028-04-15",
  "status": "Approved",
  "aadhaar_last4": "0124"
}
```

No full Aadhaar inside ciphertext either.

### 4.3 Production log policy

| Environment | Allowed in logs |
| --- | --- |
| `NODE_ENV=production` | pass_no, masked mobile (`******7377`), aadhaar last-4, step names, HTTP status, IPs (hashed or truncated if policy requires) |
| Non-production | May include `dev_otp`, richer debug — still avoid full Aadhaar |

Add a shared `safeLog()` helper in both apps that redacts Aadhaar-like 12-digit sequences and OTP patterns.

---

## 5. Multi-admin authentication (DB-driven)

### 5.1 Tables

```sql
CREATE TABLE admins (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(64) NOT NULL UNIQUE,
  email VARCHAR(120) NULL UNIQUE,
  password_hash VARCHAR(255) NOT NULL,   -- bcrypt / argon2id
  display_name VARCHAR(120) NOT NULL,
  role ENUM('superadmin','operator','auditor','viewer') NOT NULL DEFAULT 'viewer',
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  last_login_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE admin_sessions (
  id VARCHAR(36) PRIMARY KEY,
  admin_id BIGINT NOT NULL,
  token_hash VARCHAR(64) NOT NULL,
  ip VARCHAR(64) NULL,
  user_agent VARCHAR(255) NULL,
  expires_at DATETIME NOT NULL,
  revoked_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_admin (admin_id),
  CONSTRAINT fk_admin_sessions_admin FOREIGN KEY (admin_id) REFERENCES admins(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

### 5.2 Roles

| Role | Capabilities |
| --- | --- |
| `superadmin` | Manage admins, all reports, logs, security, infra monitors, revoke sessions |
| `operator` | View/filter registrations, reports, website logs (read), service health |
| `auditor` | Read-only registrations + admin activity + security audit (no infra write) |
| `viewer` | Summary dashboards and aggregated reports only |

### 5.3 Login flow

1. Admin submits username + password on `admin` app.
2. Server verifies `password_hash` (argon2id preferred; bcrypt acceptable).
3. Create `admin_sessions` row; set httpOnly cookie with session id (or signed JWT whose `jti` maps to session).
4. Write `admin_activity_logs` entry: `login_success` / `login_failure`.
5. Replace env `ADMIN_PASSWORD` with **bootstrap seed only** (first superadmin), then disable env login in production.

### 5.4 Optional hardening (phase 2)

- TOTP / email OTP for admin MFA  
- IP allowlist for admin app  
- Lockout after N failed attempts  

---

## 6. Admin activity logs

```sql
CREATE TABLE admin_activity_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  admin_id BIGINT NULL,
  username VARCHAR(64) NULL,
  action VARCHAR(80) NOT NULL,          -- e.g. login, view_visitors, export_report, view_security
  resource VARCHAR(120) NULL,           -- e.g. visitors?state=MP, reports/by-state
  outcome VARCHAR(40) NOT NULL,         -- success | denied | error
  ip VARCHAR(64) NULL,
  user_agent VARCHAR(255) NULL,
  meta JSON NULL,                        -- no secrets / no full Aadhaar
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_admin_time (admin_id, created_at),
  INDEX idx_action (action),
  INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

**Log every:** login/logout, list/filter/export of registrations, opening security or infra pages, admin user CRUD, session revoke, decryption/view of a pass from admin (if ever allowed).

**Admin UI:** “Activity” tab — filter by admin, action, date range.

---

## 7. Website logs & registration audit in admin

### 7.1 Existing `audit_logs` (registration funnel)

Expose in admin as **Website registration audit**:

- Filters: step, outcome, channel, date  
- Meta must already exclude full Aadhaar (enforce in writers)

### 7.2 Application / runtime logs

| Approach | Recommendation |
| --- | --- |
| Short term | Ship website + admin logs to files or stdout → ship to DB table `app_log_events` via lightweight collector, **or** read from a shared log volume (tail API) |
| Better | Central log store (Loki / CloudWatch / ELK); admin queries via API |

**Suggested table (if self-hosted):**

```sql
CREATE TABLE app_log_events (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  app ENUM('website','admin','api') NOT NULL,
  level ENUM('debug','info','warn','error') NOT NULL,
  message VARCHAR(500) NOT NULL,
  context JSON NULL,                    -- redacted
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_app_time (app, created_at),
  INDEX idx_level (level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

Retention: e.g. 30–90 days; purge job for production.

---

## 8. Security audit & threat visibility

### 8.1 Goals

Detect and surface:

- Brute-force OTP / admin login attempts  
- Duplicate Aadhaar registration storms  
- Unusual IP / user-agent patterns  
- Rate-limit breaches and HTTP floods (from network activity — §9)  
- Decryption or pass-fetch anomalies  
- Admin privilege misuse (from activity logs)  
- Scanner / bot / unwanted endpoint probing  

### 8.2 Suggested table

```sql
CREATE TABLE security_events (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  category VARCHAR(40) NOT NULL,        -- auth, otp, registration, admin, infra, network
  severity ENUM('low','medium','high','critical') NOT NULL,
  title VARCHAR(160) NOT NULL,
  detail JSON NULL,
  source_ip VARCHAR(64) NULL,
  status ENUM('open','acknowledged','resolved') NOT NULL DEFAULT 'open',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  resolved_at DATETIME NULL,
  INDEX idx_status_sev (status, severity),
  INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

### 8.3 Admin UI — Security

- Open threats list + severity badges  
- Acknowledge / resolve workflow (role: `superadmin` / `auditor`)  
- Simple rules engine (phase 1): thresholds in config (e.g. >10 failed OTP from same IP in 10 min → `high`)  
- Deep-link from a security event into **Network activity** for that IP / path / time window  

---

## 9. Network activity monitoring (HTTP traffic & unwanted load)

SNMP (§10) shows **device/link** health. This section covers **application-layer network activity**: who is hitting the website/admin APIs, how hard, and whether the traffic looks legitimate.

**Goal:** Unwanted requests and sudden load must **show up in the admin panel** so operators can enforce rate limits, block IPs, and harden endpoints before the system degrades.

### 9.0 Human detection on the public website (implemented baseline)

Registration must resist scripted browsers and scroll bots, in addition to OTP.

| Control | How it works |
| --- | --- |
| Honeypot field | Hidden `website` input; if filled → reject (`human_check` audit) |
| Dwell time | Wizard must be open ≥ ~1.8s before sensitive API calls |
| Interaction score | Pointer / key / touch / focus counts must show real use |
| Scripted scroll | Near-constant scroll intervals with no pointer/touch → reject |
| IP logging | Every registration step + OTP + final create logs client IP + UA in `audit_logs` |
| Registration IP | Successful create stores `visitors.registration_ip` |
| Admin login IP | Success/failure stored in `admin_login_logs` with IP + UA |

Client hook: `website/src/hooks/useHumanSignals.ts`  
Server check: `website/src/lib/human.ts` (`evaluateHuman`)  
Applied on: Aadhaar validate, OTP send/verify, visitor create.

**Future (Phase E+):** Turnstile / captcha fallback for low scores; feed rejects into `security_events` and `/network` blocklist.

### 9.1 What we capture (per request / aggregated)

Middleware (or reverse-proxy access logs shipped into DB) on **website** and **admin** records:

| Field | Purpose |
| --- | --- |
| Timestamp | Timeline / spike detection |
| App (`website` / `admin`) | Which surface was hit |
| Method + path (normalized) | Endpoint heat map; hide secrets in query strings |
| Status code | 4xx/5xx bursts = probes or failures |
| Latency ms | Slowloris / overload signal |
| Client IP (and `X-Forwarded-For` when behind proxy) | Abuse source |
| User-Agent (truncated) | Bot / scanner fingerprint |
| Bytes in / out (optional) | Bandwidth abuse |
| Rate-limit outcome | `allowed` / `throttled` / `blocked` |
| Correlation id | Tie to app logs / security events |

**Do not store:** request bodies with Aadhaar, OTP, passwords, or full tokens. Paths may be normalized (`/pass/:id` → `/pass/:passNo`).

### 9.2 Aggregation (for load & abuse dashboards)

Raw rows can be high volume. Prefer:

1. **Short-term raw** (e.g. last 24–48h) in `http_request_events` for forensics  
2. **Rolling aggregates** (1-minute / 5-minute buckets) in `http_traffic_aggregates` for charts  

```sql
CREATE TABLE http_request_events (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  app ENUM('website','admin') NOT NULL,
  method VARCHAR(10) NOT NULL,
  path_norm VARCHAR(200) NOT NULL,
  status_code SMALLINT NOT NULL,
  latency_ms INT NOT NULL,
  client_ip VARCHAR(64) NOT NULL,
  user_agent VARCHAR(255) NULL,
  bytes_in INT NULL,
  bytes_out INT NULL,
  rate_limit_action ENUM('allowed','throttled','blocked') NOT NULL DEFAULT 'allowed',
  country_code CHAR(2) NULL,              -- optional GeoIP
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_app_time (app, created_at),
  INDEX idx_ip_time (client_ip, created_at),
  INDEX idx_path_time (path_norm, created_at),
  INDEX idx_status_time (status_code, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE http_traffic_aggregates (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  app ENUM('website','admin') NOT NULL,
  bucket_start DATETIME NOT NULL,       -- e.g. floor to minute
  path_norm VARCHAR(200) NULL,           -- NULL = all paths for that app
  request_count INT NOT NULL,
  error_4xx INT NOT NULL DEFAULT 0,
  error_5xx INT NOT NULL DEFAULT 0,
  throttled_count INT NOT NULL DEFAULT 0,
  blocked_count INT NOT NULL DEFAULT 0,
  avg_latency_ms INT NULL,
  unique_ips INT NULL,
  UNIQUE KEY uq_bucket (app, bucket_start, path_norm),
  INDEX idx_bucket (bucket_start)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

### 9.3 Unwanted traffic & load — detection rules

| Signal | Example threshold (tunable) | Severity | Action |
| --- | --- | --- | --- |
| Request flood from one IP | >120 req/min to website | high | Auto-throttle; open `security_events` |
| OTP / login endpoint hammering | >20 POST `/api/.../otp/*` or `/api/login` per IP / 10 min | critical | Block IP temporarily; alert |
| Scanner paths | Hits to `/.env`, `/wp-admin`, `/phpmyadmin`, etc. | medium | Block + log |
| Sudden global load | RPS > 3× rolling baseline for 5 min | high | Alert ops; scale / shed non-critical |
| Error storm | >5% 5xx for 3 min | high | Page infra + recent deploys |
| Pass enumeration | Many distinct `/pass/*` or QR GETs from one IP | high | Throttle; review |
| Admin surface probing | Unauthenticated hits to `/api/*` on admin | high | Fail2ban-style block |
| Geographic anomaly (optional) | Spike from unexpected countries | medium | Review / geo-challenge |

Rules emit rows into `security_events` (`category = 'network'`) and optionally write to `network_blocklist`.

### 9.4 Rate limiting & blocklist (enforce security)

```sql
CREATE TABLE network_blocklist (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  client_ip VARCHAR(64) NOT NULL,
  reason VARCHAR(200) NOT NULL,
  source ENUM('auto_rule','admin_manual') NOT NULL,
  expires_at DATETIME NULL,             -- NULL = until removed
  created_by_admin_id BIGINT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_ip (client_ip),
  INDEX idx_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE rate_limit_policies (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(80) NOT NULL,
  app ENUM('website','admin','both') NOT NULL,
  path_pattern VARCHAR(200) NOT NULL,   -- e.g. /api/v1/registration/otp/*
  window_seconds INT NOT NULL,
  max_requests INT NOT NULL,
  action ENUM('throttle','block') NOT NULL DEFAULT 'throttle',
  is_enabled TINYINT(1) NOT NULL DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

**Enforcement points:**

1. Edge / reverse proxy (Nginx, Cloudflare, WAF) — preferred for volumetric DDoS  
2. App middleware — path-specific limits (OTP, Aadhaar validate, admin login)  
3. Shared store (Redis or DB) for counters across instances  

Admin UI allows: view top offenders, **manually block/unblock IP**, adjust policies (superadmin), and see throttle/block counts in real time.

### 9.5 Admin UI — Network activity

Route: `/network` (min role: `operator`; blocklist write: `superadmin`)

| Panel | Content |
| --- | --- |
| Live / recent RPS | Requests per minute for website vs admin |
| Top IPs | By volume, 4xx, 5xx, throttles |
| Top paths | Hottest endpoints; highlight auth-sensitive routes |
| Unwanted / blocked | Blocklist hits, scanner paths, auto-rules fired |
| Load vs baseline | Chart comparing current RPS to 24h baseline |
| Latency | p50 / p95 if available from aggregates |
| Drill-down | Click IP → timeline of requests (raw window) |

This is how operators **see unwanted requests and unwanted load** and confirm that security controls are working.

### 9.6 Relationship to SNMP

| Layer | What it answers |
| --- | --- |
| HTTP network activity (§9) | “Is someone abusing our APIs / pages?” |
| SNMP / host infra (§10) | “Is the NIC, switch, or host saturated?” |

Use both: HTTP flood may show in §9 before SNMP bandwidth saturates; link errors / interface drops show in §10 even when HTTP looks fine.

---

## 10. Observability — service, memory, disk, SNMP network

Admin panel section: **Infrastructure / Health** (read-only for most roles).

### 10.1 Local / host metrics (Node collector or sidecar)

| Metric | Source | Display |
| --- | --- | --- |
| Process uptime, Node heap / RSS | `process.memoryUsage()`, `/proc` or `os` | Memory chart + status |
| CPU load | `os.loadavg()` | Gauge |
| Disk | `df` / `statfs` (mounted volumes for app, logs, DB backup path) | Used % / free GB; alert >85% |
| Service status | Health checks: website URL, admin URL, MySQL ping | Up / Down / Latency |

Prefer a small **metrics agent** (or admin API route that runs only on the admin host) rather than putting SNMP/disk scraping inside the public website process.

### 10.2 SNMP (network-level details)

| Item | Detail |
| --- | --- |
| Protocol | SNMPv3 preferred (authPriv); SNMPv2c only on isolated management VLAN |
| Targets | Core switch / router / firewall / host SNMP agent (configurable list in DB or env) |
| OIDs (examples) | `IF-MIB` interface in/out octets & errors; `HOST-RESOURCES-MIB` / `UCD-SNMP-MIB` for memory & disk where available; sysUpTime |
| Library | e.g. `net-snmp` bindings or a dedicated SNMP microservice called by admin |
| Storage | Snapshot samples into `infra_metric_samples` for charts / reports |

```sql
CREATE TABLE infra_targets (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(80) NOT NULL,
  type ENUM('host','snmp','http_health','mysql') NOT NULL,
  endpoint VARCHAR(255) NOT NULL,       -- host/IP or URL
  snmp_version VARCHAR(10) NULL,        -- v2c | v3
  snmp_community_enc TEXT NULL,         -- encrypted at rest; never show in UI logs
  is_enabled TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE infra_metric_samples (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  target_id BIGINT NOT NULL,
  metric_key VARCHAR(80) NOT NULL,      -- cpu.load1, mem.used_pct, disk./.used_pct, if.eth0.in_octets
  metric_value DOUBLE NOT NULL,
  unit VARCHAR(20) NULL,
  collected_at DATETIME NOT NULL,
  INDEX idx_target_time (target_id, collected_at),
  INDEX idx_metric (metric_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

### 10.3 Reporting

Admin “Infrastructure report” combines:

- Service uptime % (period)  
- Peak memory / disk  
- Top interface error rates (SNMP)  
- Open security events count  
- Peak HTTP RPS / throttles from network activity (§9) for the same period  

Export: CSV / PDF (phase 2).

**Security note:** SNMP credentials and community strings must be encrypted at rest and never written to `admin_activity_logs` or `app_log_events`.

---

## 11. Admin UI information architecture (target)

| Route (admin app) | Purpose | Min role |
| --- | --- | --- |
| `/login` | DB-backed multi-admin login | — |
| `/` | Summary KPIs | viewer |
| `/registrations` | List + filters | operator |
| `/reports/state` | By state | viewer |
| `/reports/arrival` | By visit date | viewer |
| `/activity` | Admin activity logs | auditor |
| `/website-audit` | Registration `audit_logs` | auditor |
| `/logs` | Application logs viewer | operator |
| `/security` | Threat / security events | auditor |
| `/network` | HTTP traffic, unwanted load, top IPs/paths, blocklist | operator |
| `/infra` | Memory, disk, services, SNMP | operator |
| `/admins` | Manage admin users | superadmin |

Keep visual language aligned with current Mahakal admin styling.

---

## 12. Environment & secrets (both apps)

| Variable | App | Purpose |
| --- | --- | --- |
| `DB_*` | both | Shared MySQL |
| `JWT_SECRET` | both | Session / OTP JWT (not Aadhaar pepper) |
| `AADHAAR_PEPPER` | website (+ admin if needed) | HMAC salt for Aadhaar hash |
| `PASS_ENCRYPTION_KEY` | website (required); admin if it decrypts | 32-byte key for AES-GCM pass payloads |
| `ADMIN_BOOTSTRAP_PASSWORD` | admin (once) | Seed first superadmin only |
| `NODE_ENV` | both | Enforce redaction when `production` |
| `SNMP_*` / targets in DB | admin / agent | Device/link SNMP monitoring |
| `REDIS_URL` (optional) | website / admin | Distributed rate-limit counters |
| `TRUST_PROXY` | both | Correct client IP behind load balancer |
| `NEXT_PUBLIC_SITE_URL` | admin | Links to public passes |

Rotate peppers/keys via documented runbook; re-hash Aadhaar only with a controlled migration if pepper changes.

---

## 13. Implementation phases

### Phase A — Document & schema (this doc) ✅

Review and approve this upgrade.

### Phase B — Auth & activity (admin)

1. `admins`, `admin_sessions`, `admin_activity_logs`  
2. Replace env password login with DB login  
3. Seed superadmin; role checks on APIs  
4. Activity UI  

### Phase C — Production identity & pass crypto (website)

1. Dedicated `AADHAAR_PEPPER`  
2. Encrypt `qr_payload` (AES-GCM)  
3. Production `safeLog` redaction  
4. Confirm OTP + last-4 masking on all public surfaces  

### Phase D — Logs & security audit

1. Expose `audit_logs` in admin (includes IP on registration + human_check rejects)  
2. Expose `admin_login_logs` (login IP success/failure)  
3. `app_log_events` or log shipper  
4. `security_events` + basic detection rules (include human_check / scripted_scroll)  

### Phase E — Network activity & abuse controls

1. Request middleware → `http_request_events` + minute aggregates  
2. Admin `/network` dashboard (RPS, top IPs/paths, errors)  
3. Rate-limit policies on OTP / login / sensitive APIs  
4. `network_blocklist` + auto-rules that open `security_events`  
5. Manual block/unblock from admin  
6. Optional captcha / Turnstile when human score is borderline  

**Already shipped (baseline):** website human detection + registration IP audit + `visitors.registration_ip` + admin login IP logs.

### Phase F — Host / SNMP observability

1. Host memory / disk / service health in `/infra`  
2. SNMP poller + targets + samples  
3. Combined infrastructure report (correlate with HTTP load from Phase E)  

### Phase G — Hardening

1. Admin MFA  
2. Retention / purge jobs (request events, aggregates, logs)  
3. Edge WAF / CDN integration (optional)  
4. Pen-test checklist & DPDP / UIDAI alignment review  

---

## 14. Compliance notes (India / Aadhaar)

- Do **not** store or log full Aadhaar numbers.  
- Prefer UIDAI-approved eKYC / tokenisation where legally mandated for production government deployments.  
- Current design (hash + last-4 + OTP) is a **registration convenience model**; production go-live must be validated against latest UIDAI / MeitY / state IT guidance.  
- Admin access to visitor PII (name, mobile, last-4) must be role-gated and activity-logged.  
- Client IPs in network telemetry are security data — retain only as long as needed; restrict who can export them.

---

## 15. Acceptance criteria (upgrade done when)

- [x] ≥2 admins can log in from DB with different roles (`superadmin`, `auditor`)  
- [x] Every admin login and sensitive view is in `admin_activity_logs`  
- [x] Production builds never log full Aadhaar or plaintext OTP (`safeLog` / `safeError`)  
- [x] New passes store encrypted `qr_payload`; public pass page still works (legacy plaintext fallback)  
- [x] Admin can view registration audit + app/security/network surfaces  
- [x] Security page lists open threats with severity  
- [x] Admin `/network` shows live/recent RPS, top IPs, top paths, 4xx/5xx  
- [x] Unwanted floods / OTP hammering create security events and can throttle or block  
- [x] Operators can manually block/unblock an IP and see blocklist hits  
- [x] Website human detection (honeypot, interaction, scripted-scroll) on registration APIs  
- [x] Registration steps log client IP in `audit_logs`; pass stores `registration_ip`  
- [x] Admin login success/failure logs client IP in `admin_login_logs`  
- [x] Infra page shows memory, disk/host, service (MySQL) health  
- [ ] At least one SNMP target polled for interface / host metrics *(Phase F remaining)*  
- [x] Visit registration window remains **1 Apr 2028 – 15 Jun 2028**

### Demo credentials (local / staging bootstrap)

| Username | Role | Password |
| --- | --- | --- |
| `superadmin` | superadmin | `ADMIN_BOOTSTRAP_PASSWORD` (default `admin@kumbh2028`) |
| `auditor` | auditor | same bootstrap password |

**Rotate bootstrap passwords before any government / production demo.**

---

## 16. Out of scope (for later)

- Full SIEM / full packet capture (PCAP) platform  
- Agent-based APM (Datadog/New Relic) — optional integration  
- Carrier-grade DDoS scrubbing (use CDN/WAF vendor when needed)  
- Offline gate hardware firmware  
- Mobile admin app  

---

## 17. Related docs

- `docs/website-registration.md` — public registration channel  
- `docs/mobile-app-registration.md` — mobile channel  
- `website/README.md` — website runtime  
- `admin/README.md` — admin runtime  

---

**Next step after approval:** implement **Phase B** (DB multi-admin + activity logs), then **Phase C** (pass encryption + production Aadhaar hygiene), then **Phase E** (HTTP network activity & abuse controls).
