Files
praxis/server/auth/cookies.py
T
Praxis CI 00e39a3f85 feat(milestone): merge phase/01 operator-foundation → milestone/v0.4-operator-tier
Phase 1 complete — Operator Foundation:
- Postgres 16 in Docker-in-LXC (asyncpg pool, 5-table schema, PgStore, migrations)
- Operator auth (argon2id, signed stateless cookies, slowapi 5/min rate limit)
- VC issuer key migration SQLite→Postgres (archive-before-active, R-VC-MIG-01)
- Operator bootstrap CLI (create-operator.py, idempotent)
- Backup cron script + G-008 restore drill
- Graceful degradation (server starts without Postgres)
- 272 tests pass, 33 skip (Postgres-requiring), 0 fail

---ci---
project: praxis
phase: 1
milestone: v0.4
status: complete
requirements:
  covered: [REQ-MT-01, REQ-AUTH-01, REQ-NFR-AUTH-01, REQ-NFR-MT-01, REQ-MT-02]
  partial: []
---/ci---
2026-08-04 01:41:06 +00:00

68 lines
2.5 KiB
Python

"""Signed cookie configuration (TASK-03-02, D-041, D-056, R-AUTH-01, G-031).
Returns kwargs for Starlette SessionMiddleware (itsdangerous HMAC-SHA256
signed cookies — D-056, stateless, no sessions table). The cookie name is
`praxis_op` (distinct from any future learner cookie).
R-AUTH-01 / G-031 reframe: the PRIMARY mitigation for a sniffed operator
cookie is the k-anonymity defense-in-depth — the cohort dashboard reads
only k-anonymized aggregates, so a sniffed cookie leaks NO learner PII.
The `PRAXIS_COOKIE_SECURE` flag is the SECONDARY mitigation (operational
convenience for when TLS arrives). It defaults to true; the HTTP pilot
(LXC, no TLS — D-030) sets it to false with a logged WARNING.
"""
from __future__ import annotations
import os
import secrets
from loguru import logger
_COOKIE_MAX_AGE_S = 28800 # 8h (D-041)
def _env_bool(key: str, default: bool) -> bool:
raw = os.environ.get(key, "").strip().lower()
if raw in ("true", "1", "yes", "on"):
return True
if raw in ("false", "0", "no", "off"):
return False
return default
def get_session_middleware_kwargs() -> dict:
"""Return kwargs for Starlette SessionMiddleware.
If PRAXIS_COOKIE_SECRET is unset, generate an ephemeral random secret
and log a WARNING (dev only — sessions won't survive a restart and this
MUST NOT be used in pilot/production).
"""
secret = os.environ.get("PRAXIS_COOKIE_SECRET", "").strip()
if not secret:
secret = secrets.token_urlsafe(48)
logger.warning(
"PRAXIS_COOKIE_SECRET not set — generated an ephemeral random secret. "
"Sessions will NOT survive a server restart. This is dev-only; set "
"PRAXIS_COOKIE_SECRET (>=32 bytes) for pilot/production."
)
secure = _env_bool("PRAXIS_COOKIE_SECURE", True)
if not secure:
logger.warning(
"Cookie Secure flag disabled (PRAXIS_COOKIE_SECURE=false) — HTTP pilot "
"mode (R-AUTH-01). Do not use in production. NOTE (G-031): the primary "
"R-AUTH-01 mitigation is k-anon defense-in-depth (cohort dashboard reads "
"only k-anonymized aggregates → sniffed cookie leaks no PII); this flag "
"is the secondary mitigation."
)
return {
"secret_key": secret,
"session_cookie": "praxis_op",
"max_age": _COOKIE_MAX_AGE_S,
"https_only": secure,
"same_site": "strict",
"path": "/",
}
__all__ = ["get_session_middleware_kwargs"]