Files
ipwhitelist-app/.github/AGENT-GUIDE.md

11 KiB
Raw Permalink Blame History

IP Whitelist App — AI Agent Guide

For AI coding agents (Copilot, etc.) to understand and continue development. Last updated: 2026-06-04 | Version: 0.5.23

1. Quick Overview

Purpose: Self-service portal for cloud provider customers to manage trusted IPv4 whitelist entries (CIDRs) that are excluded from DDoS filtering.

Stack: Node.js + Express + EJS + PostgreSQL
Auth: Mock RS256 JWT (dev) / Keycloak OIDC (production)
Deployment: PM2, behind nginx on a VM

2. Architecture

Browser → nginx:443 → Express:3001
                        ├── /export (public, no auth)
                        ├── /api/v1/* (Bearer JWT, REST API)
                        └── /* (UI layer, SSR EJS, session-based)

Two-layer design:

  • API layer (src/api/routes/): REST endpoints, Bearer JWT auth, direct DB access
  • UI layer (ui/routes/): SSR EJS pages, session-based auth, proxies to API via HTTP (ui/api-client.js)

Why two layers?

  • API can be used directly by nginx/firewall for whitelist export
  • UI provides human-friendly management interface
  • Clear separation of concerns

Key flow:

Browser → UI route → api-client.js (HTTP) → API route → PostgreSQL

3. Project Structure

ipwhitelist-app/
├── server.js                 # Entry point — mounts all routes
├── package.json
├── .env                      # Config (not in git)
├── .env.example              # Config template
├── sql/schema.sql            # Database schema
│
├── src/
│   ├── auth.js               # Auth: mock JWT + OIDC/Keycloak
│   ├── config.js             # MOCK_USERS + helpers
│   ├── db.js                 # PostgreSQL pool
│   ├── queries.js            # All DB queries (CRUD, export, audit)
│   ├── validators.js         # CIDR validation + blocked ranges + aggregation
│   │
│   ├── api/
│   │   ├── index.js          # API router mount
│   │   └── routes/
│   │       ├── entries.js    # GET/POST/PATCH/DELETE /entries + /entries/export
│   │       └── admin.js      # GET /companies, PATCH /limit, GET /audit
│   │
│   ├── middleware/
│   │   ├── csrf.js           # CSRF protection (NOT ACTIVE — tokens are empty)
│   │   ├── rateLimit.js      # Rate limiters (auth, mutation, export)
│   │   └── session.js        # express-session with pg store
│   │
│   └── routes/
│       ├── oidc.js           # OIDC/Keycloak routes (inactive in mock mode)
│       └── (old monolithic routes deleted in v0.5.11)
│
├── ui/
│   ├── index.js              # UI middleware: requireToken + resolveUser + mount routers
│   ├── api-client.js         # HTTP client to /api/v1/* (from UI layer)
│   └── routes/
│       ├── auth.js           # GET/POST /login, POST /login-token, GET /logout
│       ├── entries.js        # GET /, POST /add, POST /edit/:id, POST /delete/:id
│       ├── admin.js          # GET /admin, /audit (admin-only)
│       └── export.js         # GET /export (proxies to API)
│
├── views/
│   ├── index.ejs             # Main page: entries table + add/edit/delete forms
│   ├── ui-login.ejs          # Login page (mock + manual clientId entry)
│   ├── admin.ejs             # Admin: companies list + limits
│   ├── audit.ejs             # Audit log
│   └── error.ejs             # 404/500 error page
│
├── tests/
│   ├── api.js                # 121 API integration tests (Bearer JWT)
│   ├── integration.js        # 68 UI integration tests (session, EJS)
│   ├── tz-compliance.js      # 47 ТЗ compliance tests
│   ├── tz-full-compliance.js # 111 comprehensive ТЗ tests (20 companies × 100 CIDRs)
│   ├── run-tests.js          # 68 unit/smoke tests (no DB)
│   └── stress.js             # 191 concurrency/stress tests
│
└── docs/
    ├── ТЗ.md                 # Tech spec (Russian)
    ├── ТЗ-плюс.md            # Client clarifications
    ├── ТЗ-реализация.md       # Implementation notes
    ├── keycloak-auth-reference.md  # Keycloak/OIDC reference
    ├── devops-deploy.md      # DevOps deployment guide
    └── deploy-keycloak.md    # Keycloak setup guide

4. Auth System

Mock Mode (default, DEV_MODE=true)

OIDC Mode (production, when KC_CLIENT_ID set)

  • Authorization Code flow via Keycloak
  • Routes in src/routes/oidc.js (inactive in mock mode)
  • JWKS certificate verification

UI resolveUser (ui/index.js)

  • Decodes JWT from session (no signature check — API validates on each call)
  • Extracts ClientID, allClientIds, activeClientId, companyId, companyName, email
  • Sets req.user for all downstream handlers

API bearerMiddleware (src/auth.js)

  • Verifies Bearer JWT signature (RS256 via JWKS or mock keypair)
  • Sets req.user via userFromPayload()

5. Multi-Company

Users can belong to multiple companies via IAM API profiles:

  • User's companies come from IAM API (profiles[]) with is_active_profile flag
  • Active company selector in UI (dropdown, ?switchTo=X or JS navigation)
  • API supports ?client_id=<id> parameter to specify active company
  • Default: first company in list (allClientIds[0])

IMPORTANT: req.user.activeClientId must be synced BOTH in session.user.activeClientId AND req.user.activeClientId — see ui/routes/entries.js switch handler.

6. Company Isolation

  • Each company is a row in companies table (unique by client_id)
  • Entries are isolated by company_id foreign key
  • Admin can view/edit any company via ?company=<db_id>
  • User sees only their company's entries
  • Multi-company user switches via ?client_id=<clientId>

7. Database

Three tables (see sql/schema.sql):

  • companies — (id, client_id, name, custom_limit, created_at, updated_at)
  • whitelist_entries — (id, company_id FK, value_cidr, comment, created_by, created_at, updated_at, deleted_at, deleted_by)
  • audit_log — (id, user_email, company_id, action, old_value, new_value, entry_id, created_at)

Default limit: 15 entries per company (configurable via DEFAULT_LIMIT env).

8. Key Endpoints

Method Path Auth Description
GET /export None (public) Aggregated CIDR list, text/plain
GET /api/v1/entries Bearer JWT List entries for company
POST /api/v1/entries Bearer JWT Create entry
PATCH /api/v1/entries/:id Bearer JWT Update entry
DELETE /api/v1/entries/:id Bearer JWT Soft-delete entry
GET /api/v1/entries/export Bearer JWT Aggregated CIDR export
GET /api/v1/companies Bearer JWT (admin) List companies
PATCH /api/v1/companies/:id/limit Bearer JWT (admin) Set company limit
GET /api/v1/audit Bearer JWT (admin) Audit log
GET /login None Login page (UI)
POST /login None Mock login (form)
POST /login-token None Manual clientId login
GET /logout Session Logout

Export query params:

  • ?view=1 — inline (browser display)
  • ?filename=X — custom filename (default: white-list.txt)

9. CIDR Validation Rules

  • Only IPv4, no IPv6
  • Mask must be /22 to /32 (inclusive)
  • Single IP without mask = /32
  • 14 blocked ranges (RFC1918, CGNAT, loopback, etc.)
  • No duplicates within a company
  • No overlaps within a company
  • Normalization: host bits zeroed (e.g., 9.9.9.5/24 → 9.9.9.0/24)
  • Comment max 255 chars, optional

10. Running Tests

# Unit tests (no DB needed)
node tests/run-tests.js

# API tests (requires DB, DEV_MODE=true)
node tests/api.js

# UI integration tests
node tests/integration.js

# ТЗ compliance
node tests/tz-compliance.js

# Full ТЗ compliance (20 companies × 100 CIDRs)
node tests/tz-full-compliance.js

# Stress/concurrency tests
node tests/stress.js

All tests: 347+ PASS, 0 FAIL

11. Deployment

Deploy the app on a VM with nginx as reverse proxy. See docs/devops-deploy.md for details.

12. Common Pitfalls

🚨 Rate Limiter

authLimiter: 10 requests per 5 minutes. Hits quickly during testing. Reset after 5 min or use different IPs.

🚨 Mock Keypair Mismatch

Each initAuth() generates a NEW RSA keypair. When testing with supertest, the UI api-client makes HTTP requests to localhost:3001 (PM2 instance), which has a DIFFERENT keypair → "invalid signature". Fix: test against the running PM2 instance directly, or stop PM2 and use the supertest port.

🚨 Company Switch Bug (FIXED v0.5.12)

resolveUser runs BEFORE the route handler. When switching companies via ?switchTo=X, the session was updated but req.user.activeClientId still had the old value. Fixed by adding req.user.activeClientId = switchTo after session update.

🚨 CSRF Not Active

csrfToken is always "" in templates. CSRF middleware is not mounted. Forms work, but there's no CSRF protection. To enable: mount doubleCsrfProtection middleware and call generateCsrfToken() in GET handlers.

🚨 .env on VM gets overwritten

Rsync excludes .env now, but double-check. If .env contains DB credentials, they MUST NOT be overwritten.

🚨 Version Must Always Increment

After ANY code change: bump "version" in package.json. PM2 shows version — user expects it to match.

13. OIDC/Keycloak Transition

To enable production auth:

  1. DevOps creates client in Keycloak with redirect URI /callback
  2. Set .env: KC_CLIENT_ID, KC_CLIENT_SECRET, KC_BASE_URL, APP_URL
  3. Set DEV_MODE=false
  4. pm2 restart ipwhitelist --update-env
  5. Routes in src/routes/oidc.js activate automatically when KC_CLIENT_ID is set

See docs/deploy-keycloak.md for details.

14. Version History

Version Date Key Changes
0.5.23 2026-06-04 0.0.0.0/8 blocked, security+edge test suites
0.5.22 2026-06-04 4-audit cleanup, gitignore hardening
0.5.21 2026-06-04 Header injection fix, API timeout, nginx duplicate
0.5.20 2026-06-04 IAM fallback warning, fail-fast IAM_API_URL, X-Forwarded-Proto
0.5.19 2026-06-04 safeReturn in all redirects (open redirect fix)
0.5.18 2026-06-04 IAM API integration: fetchIamUser, switchProfile, profiles[]
0.5.17 2026-06-04 Previous stable
0.5.14 2026-06-03 Token login, JWKS in mock mode, production deploy docs
0.5.13 2026-06-03 OIDC routes prepared, .env.example, devops-deploy guide, repo cleanup
0.5.12 2026-06-02 Fixed company switch bug, aggregation, validation, TZ compliance
0.5.11 2026-06-02 Dead code cleanup, new integration tests, /export public
0.5.10 2026-06-02 UI/API split, mock users fixed, 121 API tests passing