Ctrl K
ComunidadGuías…

CLAUDE.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# CLAUDE.md

Guidance for AI coding agents working in this repository. This file is read before any task.

## What this repository is

**PowerDNS-AuthAdmin** - a self-hosted DNS administration UI for PowerDNS Authoritative. The app
manages one or many PDNS backends (standalone primaries, primary + secondaries groups, multi-
primary clusters) from a single web app with RBAC, OIDC SSO, audit log, and YAML-driven
provisioning.

The stack is Next.js 16 (App Router + RSC) on Node 24, TypeScript strict mode, Drizzle ORM
over Postgres or SQLite, CASL for RBAC, Argon2id for passwords, openid-client for OIDC,
better-sqlite3 / pg for storage.

Licensed under **MIT**.

## Start here

1. **[`README.md`](./README.md)** - public-facing overview + feature list + quickstart.
2. **[`docs/FEATURES.md`](./docs/FEATURES.md)** - exhaustive feature catalog with module pointers.
3. **[`CONTRIBUTING.md`](./CONTRIBUTING.md)** - code standards, testing, security, perf budgets.
   Enforced by ESLint + CI; following these rules saves review churn.
4. **[`docs/adr/`](./docs/adr/)** - Architecture Decision Records. Read the relevant ADR before
   changing anything load-bearing (auth, RBAC, PDNS client, sessions, migrations).
5. **[`docs/dev-setup.md`](./docs/dev-setup.md)** - local development workflow.

## How to run things

### Containerized demo (quickest)

```sh
docker compose up -d   # demo stack (app + PDNS); reads throwaway secrets from .env.example
#   app  at http://localhost:3000          (login: admin@example.com / change-me-now)
#   pdns at http://localhost:8081/api/v1   (X-API-Key: demo-pdns-api-key)
```

For a real install (your own `.env` + compose, SQLite or Postgres) see
[`docs/02-INSTALLATION.md`](./docs/02-INSTALLATION.md).

### Local dev with HMR

```sh
nvm use                          # Node 24 from .nvmrc
npm ci                           # exact-pin install
cp .env.example .env.local       # set APP_SECRET_KEY + APP_ENCRYPTION_KEY; DATABASE_URL=file:./dev.db
docker compose up -d pdns        # a local PowerDNS to develop against (SQLite app runs on host)

npm run dev                      # http://localhost:3000
npm run validate                 # the CI gate - lint + typecheck + format + test
npm run test:integration         # integration suite (builds + boots the stack in Docker)
npm run db:generate              # after a schema change → new migration (also db:generate:sqlite)
npm run db:migrate               # apply pending migrations
```

Pre-push gate: `npm run ci:local` (= `act -j static-checks && act -j test` - [act](https://github.com/nektos/act)
runs the CI lint/typecheck/format + unit-test jobs locally, incl. `eslint .` without the local OOM;
the committed `.actrc` pins the runner image + workflow + host-native arch). Run
`npm run test:integration` natively (act can't nest docker-compose). CodeQL/Docker/Scorecard still need real CI.

## Project layout

```
app/                  Next.js App Router
  (auth)/             login, signup, reset-password, verify-email (no app shell)
  (app)/              app shell, requires auth
    dashboard/        landing widgets (operator attention surfaces, PDNS metrics tab)
    zones/            amalgamated zone list + per-zone detail (+ new/, [zoneId]/dnssec, metadata)
    admin/            users, teams, roles, servers, clusters, authentication/{oidc,saml,ldap},
                      tsig-keys, autoprimaries, zone-templates, import-export, requests (PDNS
                      request log), settings (+ settings/backup), audit
    profile/          per-user account: password, sessions, MFA (TOTP + passkeys), API tokens
    change-email/     confirm page for the email-change token
  api/                route handlers (REST + SSE under api/realtime, CSP report sink)
  nic/update/         DynDNS 2 endpoint (ddclient-compatible)
  healthz, readyz     liveness + readiness probes
  metrics/            Prometheus exposition (bearer-gated)

components/
  ui/                 generic primitives (data-table, dialog, select-menu, turnstile-widget, …)
  domain/             feature-specific (record-table, health-bell, capability-badges, …)
  auth/               compliance guard (must-change-password / MFA hard-stop)
  realtime/           SSE provider + header status chip

lib/                  domain code, three-layer architecture enforced via ESLint
  auth/               sessions, providers (local, OIDC, SAML, LDAP), WebAuthn, TOTP, CSRF,
                      rate limit, tokens, demo locks (bootstrap-admin, settings-lock)
  rbac/               CASL ability builder, scope matching, zone grants, protected RRsets
  pdns/               typed HTTP client, sync probes, cluster picker, zone operations, TSIG,
                      capabilities, Lua gate, zone-state cache
  db/                 Drizzle schemas (schema/ pg + schema-sqlite/), repositories, backup
  audit/              append-only audit log, action vocabulary, secret redaction
  dns/                zonefile parser/formatter, zone dedupe + horizons, default TTL
  diff/               rrset before/after diffing for the review modal + change history
  dyndns/             DynDNS 2 request parsing + response vocabulary (pure)
  email/              SMTP transport, send API, message templates (env-driven, AUTH optional)
  crypto/             AES-256-GCM envelope encryption + HKDF subkeys
  health/             backend advisory evaluator (the notification bell)
  http/               shared route-handler helpers (error responses)
  metrics/            Prometheus exposition, PDNS statistics sampler, retention
  net/                outbound URL safety (SSRF guard primitives)
  security/           CSP builder
  settings/           cached app-settings reader
  validators/         Zod schemas at every boundary (incl. rr-types/)
  provisioning/       first-boot YAML applier (settings, roles, teams, templates, clusters,
                      servers, oidc, saml, ldap, demo zones)
  realtime/           SSE event bus, zone-state poller, backend gateway, TSIG replication
  client/             api-fetch (CSRF header injection)
  errors/             typed error hierarchy + secret redaction
  env.ts              boot-time env validation
  logger.ts           Pino structured logging
  client-ip.ts        proxy-aware client IP

proxy.ts              per-request CSP nonce + security headers (Next 16 proxy convention)
docker/               entrypoint that runs migrations then boots the server
drizzle/              generated PG migrations
drizzle-sqlite/       generated SQLite migrations
scripts/              migrate.ts, seed.ts, provision.ts, screenshots.mjs
tests/                vitest unit + integration (the latter wants a real Postgres on
                      $TEST_DATABASE_URL) + Playwright e2e
docs/                 ADRs, FEATURES, operator guides (01-13), dev-setup
screenshots/          gallery of every page in 4 variants - desktop+light, desktop+dark,
                      mobile+light, mobile+dark; regen with `npm run screenshots`
                      (Playwright + iPhone-frame CSS, optional pngquant+oxipng post-pass)
```

## Working conventions

### Three-layer rule

Every request flows through **authentication → authorization → business logic** (ADR 0004).
Boundaries are enforced by `eslint.config.mjs`'s `import/no-restricted-paths`. If a violation
shows up in CI, fix the boundary; don't suppress the rule.

### Migrations

- Schema lives in `lib/db/schema/` (PG) and `lib/db/schema-sqlite/` (SQLite).
- After a change, run `npm run db:generate` (PG) and `npm run db:generate:sqlite` (SQLite) to
  produce the migration SQL.
- Migrations run inside the app container entrypoint at boot (ADR 0011). The Postgres path
  takes a `pg_advisory_lock` so multi-replica boots are serialized.

### Audit

Every write goes through `appendAudit({...})` from `lib/audit/log.ts`. Action names come from
the typed vocabulary in `lib/audit/actions.ts` - adding a new action means appending to that
file. `before`/`after` snapshots are auto-redacted for known secret field names (see
`lib/audit/redact.ts`); don't bypass.

### Auth on routes

Use `requireUser({ can: "..." })` on route handlers, `requireUserForPage({ can: "..." })` on
page components. CSRF on every mutating route via `requireCsrf(request)`. The client side
adds the `x-csrf-token` header automatically via `lib/client/api-fetch.ts`.

### Comments

Code comments explain **why** something is done a non-obvious way, not what it does. Names
should carry the what. Don't write comments that paraphrase the code; do write comments when
the code embodies a constraint a future reader would otherwise have to reverse-engineer.

### Tests

`npm run test` runs the vitest unit suite (no external deps). Integration tests under
`tests/integration/` need a Postgres instance - the runner skips them when `TEST_DATABASE_URL`
isn't set. CI runs both.

### Responsive UI (v1.1.4+)

Every page is **mobile-first responsive**. Lists use the shared `<DataTable>`
(`components/ui/data-table.tsx`) which reflows to labelled cards under `md`;
chrome (app-shell, header chip, theme toggle, capability badges) is built to
operate at 320 px. Sync state is communicated by `<SyncIndicator>` (animated
concentric rings, honours `prefers-reduced-motion`); fleet-wide verdict is
`globalAnyLagging()` from `lib/pdns/sync.ts`.

When adding a new list view, use `<DataTable>`. When showing replication
status, use `<SyncIndicator>` - don't roll a new dot/badge. See
[`docs/FEATURES.md` § 19](./docs/FEATURES.md#19-operator-ux--responsive-design)
for the full vocabulary.

### Screenshots

Every page lives in `screenshots/<light|dark>/<name>{-mobile}.png` and is
referenced from `screenshots/README.md` with `<picture>` for auto theme
switching. When you add a page or change a major surface, regen the relevant
shot:

```sh
node scripts/screenshots.mjs <page-name>          # one page, all 4 variants
SKIP_MOBILE=1 node scripts/screenshots.mjs <name> # desktop only
```

The runner expects the combined demo stack up + `must_change_password`
cleared on `admin@example.com` - full prereqs in
[`docs/dev-setup.md` → Regenerating screenshots](./docs/dev-setup.md#regenerating-screenshots).

## When working in this repo

- **Read the relevant ADR before touching a load-bearing area.** Auth, RBAC, sessions, CSP,
  PDNS client, migrations - each has an ADR that explains the why.
- **If a decision is wrong, change it.** Open an issue, write a new ADR that supersedes the
  old one (mark the old one `Superseded by NNNN`), update the index in `docs/adr/README.md`.
- **Standards live in `CONTRIBUTING.md`.** Read it before writing code - the rules are
  enforced and following them from the start saves review iterations.
- **No new top-level docs without consensus.** ADRs go in `docs/adr/`; runbooks in
  `docs/runbooks/` (none yet); everything else is a section in an existing doc.