Knacky 700b563297 feat(m2): auth, JWT, invitations, bootstrap, RTOps SPA pages
Crypto + tokens
- app/core/security.py: Argon2id PasswordHasher (time_cost=2, memory_cost=
  64 MiB, parallelism=2) + opaque-token SHA-256 helpers (raw token shown
  once, only the hash lives in the DB).
- app/core/jwt_tokens.py: HS256, claims iss/sub/type/jti/iat/exp. Access
  1h, refresh 30d.

Services
- services/auth.py: login, refresh with token rotation + reuse-detection
  chain revoke, logout (idempotent), change_password (forces logout-all).
- services/invitations.py: create, preview, accept, revoke. Default 7d TTL.
- services/bootstrap.py: seeds the 3 system groups (admin/redteam/blueteam),
  consumes the install token, attaches the first user to admin.
- core/install_token.py: mints, persists in settings, marks consumed,
  regenerate hook for /diag/reset.

API
- POST /setup (consume install token, create 1st admin) + GET /setup
  (status).
- POST /auth/{login,refresh,logout,change-password} + GET /auth/me.
- POST /invitations + GET /invitations + GET /invitations/preview/<token> +
  POST /invitations/accept/<token> + POST /invitations/<id>/revoke.
- POST /diag/reset: test-only kill switch (truncate auth tables + mint
  fresh install token). Allowed in dev too (with WARNING log) so the e2e
  suite can run against a make-up stack; production locked out.

Middleware
- @require_auth populates g.current_user (snapshot dataclass, session
  closed before request handler runs).
- @require_perm(*codes): atomic perm union check; admin group bypasses.
  Perm catalogue lands in M3, scaffolding here.
- flask-limiter: 10/min/IP on /auth/login & /auth/refresh, 5/min on
  /auth/change-password & /setup, 10–20/min on invitation endpoints.
  Disabled in APP_ENV=test.

CLI
- flask --app app.cli metamorph print-install-token [--force]
- flask --app app.cli metamorph seed-mitre (M4 placeholder)

Refresh cookie metamorph_refresh: HttpOnly + Secure (localhost is a secure
context for modern browsers) + SameSite=Strict + Path=/api/v1/auth/.

Email validation: app.api._validation.Email permissive RFC-shape regex so
internal TLDs (.local/.corp/.test) are accepted — pydantic.EmailStr's
deliverability check is too strict for red-team labs.

Frontend
- lib/{api,auth}.ts: access token in module memory, refresh cookie,
  automatic 401-retry via /auth/refresh, useAuth() hook.
- components/{Layout,RequireAuth}.tsx + ui/{TextField,Alert}.tsx.
- pages/{Login,Setup,Register,Profile}.

Testing
- tests/test_auth_flow.py: 15 integration tests (24 backend total).
- e2e/tests/m2-auth.spec.ts: 8 Playwright tests (20 e2e total).
- tasks/testing-m2.md.

DoD: make test-api → 24 passed, make e2e → 20 passed; spec-reviewer pass
applied (Secure unconditional, refresh limit 10/min/IP).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 06:16:48 +02:00

Metamorph

Collaborative purple-team platform. Red team logs the tests they execute (procedure, command, timestamp); blue team annotates each test with detection evidence (alerts, logs, files). At the end of an engagement, Metamorph generates a standalone reveal.js slide deck classified by MITRE ATT&CK tactic.

Status: M0 (bootstrap). See tasks/spec.md for the full specification and tasks/todo.md for the milestone-by-milestone plan.

Stack

  • Backend: Python 3.12, Flask 3, SQLAlchemy 2 + Alembic (M1+), PostgreSQL 16.
  • Frontend: React 18 + TypeScript + Vite + TailwindCSS (RTOps design tokens, see tasks/design.md).
  • Auth (M2+): JWT access (1h) + refresh (30d), Argon2id, invite-link enrollment.
  • Delivery: docker-compose. TLS termination is expected to be handled by an external reverse proxy in production.

Quickstart

Works with Docker or Podman. The Makefile auto-detects the available engine and picks the matching compose driver (docker compose, podman compose, or podman-compose).

Requires one of:

  • Docker Engine 24+ with the Compose v2 plugin, or
  • Podman 4.0+ with podman compose (or the legacy podman-compose ≥ 1.0.6)
git clone <this repo>
cd Metamorph
make engine               # confirm which engine the Makefile picked up
make env                  # creates .env from .env.example
$EDITOR .env              # set strong values for POSTGRES_PASSWORD and JWT_SECRET
make up                   # builds and starts api + db + front
make logs                 # tail logs

Override the auto-detection if you have both engines installed:

make up ENGINE=podman                       # force podman + auto-pick its compose driver
make up ENGINE=docker COMPOSE="docker compose"
COMPOSE=podman-compose make up              # force the legacy wrapper specifically

Then:

To stop:

make down            # keep volumes
make clean           # also drop volumes (DESTRUCTIVE)

Local dev (no Docker)

Requires:

  • uv for Python deps
  • Node.js 20+ and npm
  • A reachable Postgres (or make up db to run only the db container)
make dev-api     # in one terminal
make dev-front   # in another

Environment variables

See .env.example. The most important ones:

Variable Purpose
APP_ENV dev allows placeholder secrets; anything else (prod/staging) refuses to boot with defaults
POSTGRES_* DB credentials (used by db and api)
JWT_SECRET HS256 signing key — generate 64+ random bytes (python -c "import secrets; print(secrets.token_urlsafe(64))")
LOG_LEVEL DEBUG / INFO / WARNING / ERROR
FRONT_ORIGIN Allowed CORS origin for the SPA
EVIDENCE_DIR Path inside the api container where uploads land
HOST_API_PORT Host port mapped to the api (default 8000)
HOST_FRONT_PORT Host port mapped to the front nginx (default 8080)

Testing

  • Manual + automated checklist for the current milestone: see tasks/testing-m<N>.md (currently testing-m0.md).
  • Backend unit tests: make test-api
  • End-to-end (Playwright): make e2e-install (once), then make up && make e2e. Reports land in e2e/playwright-report/ (HTML + JUnit XML); open with make e2e-report.

Pre-commit hooks

After cloning, install hooks once:

pipx install pre-commit   # or: pip install --user pre-commit
pre-commit install
pre-commit run --all-files   # initial sweep

The hooks run ruff + ruff-format on the backend and eslint / tsc --noEmit / prettier --check on the frontend (see .pre-commit-config.yaml).

Project layout

.
├── backend/             # Flask API
│   └── app/
│       ├── api/         # HTTP layer (blueprints)
│       ├── core/        # config, logging, errors
│       ├── db/          # SQLAlchemy session, migrations (M1+)
│       ├── models/      # ORM models (M1+)
│       ├── services/    # domain logic (M2+)
│       └── i18n/        # message catalogs (M13)
├── frontend/            # Vite + React + TS + Tailwind
│   └── src/components/ui/   # RTOps design system primitives
├── tasks/
│   ├── spec.md          # source of truth for requirements
│   ├── design.md        # RTOps design system
│   ├── todo.md          # milestone plan
│   └── lessons.md       # session retrospectives
├── docker-compose.yml
├── Makefile
└── CHANGELOG.md

Roadmap

See tasks/todo.md. Current milestone: M0 — bootstrap.

License

TBD.

Description
No description provided
Readme 1.4 MiB
Languages
Python 59.6%
TypeScript 38.1%
Makefile 1.2%
Dockerfile 0.6%
CSS 0.3%
Other 0.1%