diff --git a/.github/AGENT-GUIDE.md b/.github/AGENT-GUIDE.md new file mode 100644 index 0000000..890d764 --- /dev/null +++ b/.github/AGENT-GUIDE.md @@ -0,0 +1,259 @@ +# IP Whitelist App — AI Agent Guide + +> For AI coding agents (Copilot, etc.) to understand and continue development. +> Last updated: 2026-06-03 | Version: 0.5.13 + +## 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 on VM (`5.172.178.213`), HTTP only via `italo.kube5s.ru` + +## 2. Architecture + +``` +Browser → nginx:80 → 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) +- Local RSA keypair generated at startup +- `POST /login-token` with `clientId` field → mock JWT → session +- Mock users defined in `src/config.js`: + - `admin` (WZ01112) — admin@nubes.ru + - `alfa` (WZ88888) — alfa@example.com + - `multi` (WZ88888, WZ77777) — multi@example.com + - `beta` (WZ77777) — beta@example.com + +### 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 comma-separated `clientId`: +- `ClientID: "WZ88888, WZ77777"` → two companies +- Active company selector in UI (dropdown, `?switchTo=X` or JS navigation) +- API supports `?client_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=` +- User sees only their company's entries +- Multi-company user switches via `?client_id=` + +## 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 + +```bash +# 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 (v0.5.13) + +## 11. Deployment (VM) + +```bash +# From local workspace: +rsync -az -e "ssh -i ~/.ssh/naeel_vm_id_ed25519 ..." /home/naeel/ipwhitelist-app/ naeel@5.172.178.213:~/ipwhitelist/ --exclude node_modules --exclude .git --exclude .env + +# On VM: +cd ~/ipwhitelist && pm2 restart ipwhitelist --update-env +``` + +## 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.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 | diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8d3bea2..0590484 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -26,3 +26,4 @@ - `docs/ТЗ-плюс.md` — уточнения от заказчика - `docs/keycloak-auth-reference.md` — справка по Keycloak/OIDC - `Files/token.txt` — креды (токены + БД) +- `.github/AGENT-GUIDE.md` — полный гайд для AI-агентов (англ.)