Chris TonjesandClaude Opus 4.7 0501850a9c Phase 0 milestone 0.3: auth + JWT + iam.assurance_level claim
Lands local password auth with JWT access + refresh tokens, and starts
carrying YPMS-specific claims (`uid`, `roles`, `iam.assurance_level`,
`mfa_verified`) so downstream authorization can gate high-risk actions
above base RBAC without a DB round-trip. Follows the OWC auth pattern
verbatim except where noted.

Added:

- src/ypms/auth/security.py    hash_password / verify_password
                               (pbkdf2_sha256), create_access_token
                               with YPMS-specific claims,
                               create_refresh_token, decode_token
- src/ypms/auth/rate_limit.py  SlidingWindow (vendored from OWC),
                               login_limiter tuned to NIST AC-7 /
                               source RFP C.5.7.10: 5 failed attempts
                               per IP per 15 minutes → 429 lockout
- src/ypms/auth/schemas.py     LoginResponse, RefreshRequest,
                               RefreshResponse, ChangePasswordRequest,
                               UserRead
- src/ypms/auth/deps.py        get_current_user replaces the milestone
                               0.2 stub; rejects refresh tokens on
                               resource endpoints; refuses inactive users
- src/ypms/auth/router.py      POST /auth/login  (OAuth2 form; audit-logs
                               successful auth; DOES_SYSTEM_ADMIN role
                               upgrades assurance_level to `admin` in
                               the token)
                               POST /auth/refresh (issues fresh access;
                               refuses non-refresh tokens)
                               GET  /auth/me
                               POST /auth/change-password (self-service;
                               ≥12 char floor; audit-logged)
- src/ypms/main.py             Registers auth_router

Tests (43 passing, 93.47% coverage — up from 26 / 93.10%):

- tests/auth/test_security.py  Token claims shape, refresh-vs-access
                               separation, invalid-token handling
                               (5 tests)
- tests/auth/test_router.py    End-to-end via TestClient with a
                               dependency-overridden DB: login happy
                               path + wrong password + inactive user,
                               admin assurance upgrade, /me auth-gate,
                               refresh happy path + reject access token,
                               change-password full flow + length gate,
                               login lockout after 5 attempts (12 tests)

Test-infra fixes:

- tests/conftest.py            Env defaults moved from a session-scoped
                               fixture to module-import time so
                               `ypms.main` (which builds the app at
                               import) can be transitively imported by
                               test modules without ValidationError on
                               Settings.
                               `db` fixture switched to StaticPool +
                               check_same_thread=False so TestClient's
                               worker thread sees the same in-memory
                               SQLite DB the fixture set up.

Notes:

- OWC includes an /auth/register for external users; that's out of scope
  here — Phase 1 lands parental co-consent and youth/host registration,
  which needs its own PIA-driven flow. Keeping /auth minimal in Phase 0.
- MFA is captured in `User.mfa_verified` and JWT claim but not enforced
  yet; enforcement lands as an assurance-level gate in a later phase.
- Refresh flow returns only a new access token; refresh-token rotation
  is a later hardening step.

Advances YPMS-POAM-040 and unblocks milestone 0.4 (RBAC seed) and 0.5
(Registrant vertical slice).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-30 18:30:39 -04:00

YPMS — Youth Program Management System

DC Department of Employment Services / Office of Youth Programs — in-house build replacing seven legacy tools (MBSYEP intake, TMS timekeeping, PF3 admin, manual payroll scripts, MicroStrategy, and two supporting apps) with a single Python / FastAPI / Postgres system on Azure Government.

Status: Pre-development. Phase 0 (foundations) in flight. System owner + ISSO + AO: Chris Tonjes, DOES CIO. Full context: OVERVIEW.md, ARCHITECTURE.md, PHASE0_PLAN.md.

Quickstart (local)

Requires Docker Desktop (or equivalent). Ports 8000 (app), 5433 (Postgres), 8081 (Adminer) must be free.

cp .env.example .env
docker compose up --build

App at http://localhost:8000. OpenAPI docs at http://localhost:8000/docs.

curl http://localhost:8000/healthz    # → {"status":"ok"}
curl http://localhost:8000/readyz     # → {"status":"ok"} once db is up

Adminer (DB explorer) at http://localhost:8081. Connect with:

  • System: PostgreSQL
  • Server: db
  • Username / Password / Database: ypms / ypms / ypms

Stop with Ctrl+C; wipe local DB with docker compose down -v.

Tests

Local (no Docker required for the tests):

python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
pytest

Coverage:

pytest --cov=src/ypms --cov-report=term-missing

Tests use SQLite in-memory for speed. Postgres-specific behaviour gets its own Postgres-only fixture when a migration or query needs it (see ARCHITECTURE.md §4.8).

Repo shape

Full tree in ARCHITECTURE.md §5.1. High points:

  • src/ypms/main.py — FastAPI factory
  • src/ypms/settings.py — pydantic-settings config
  • src/ypms/db.py — SQLAlchemy engine, session, Base
  • src/ypms/logging.py — structlog + sensitive-field sanitizer
  • src/ypms/middleware.py — request id
  • src/ypms/framework/ — vendored from OWC, milestone 0.2
  • src/ypms/security/ — auth + RBAC + column encryption, milestones 0.3–0.4
  • src/ypms/youth/ — Registrant entity (vertical slice), milestone 0.5
  • Other entity packages — 501 stubs, milestone 0.6
  • migrations/ — Alembic, immutable-once-merged
  • tests/ — pytest, SQLite by default

Adding an entity

Once the framework lands in milestone 0.2, adding a new entity follows the OWC add-entity pattern (model → schemas → router → main.py registration → migration → permissions seed entry → test → coverage doc). Full instructions land here at milestone 0.2 completion.

Contributing

See CONTRIBUTING.md for the hard rules. Highlights: migrations are immutable once merged, every state-mutating route emits an audit row, framework/security modules must not carry domain logic, no --no-verify or # type: ignore without a written reason.

Compliance

License / usage

Internal DOES system. Not for external distribution.

S
Description
ypms build
Readme
497 KiB
0 Stars 1 Watchers 0 Forks
Languages
Python 99.3%
Dockerfile 0.4%
Mako 0.3%