- audits/: все аудиты + ревью вынесены из docs/ - .gitignore: audits/, удалены старые паттерны - README.md: v0.5.22 - .github/AGENT-GUIDE.md: v0.5.22 - Удалены из трекинга: TZ-COMPLIANCE, аудиты
255 lines
11 KiB
Markdown
255 lines
11 KiB
Markdown
# 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.22
|
||
|
||
## 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)
|
||
- 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=<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
|
||
|
||
```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
|
||
|
||
## 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.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 |
|