fix(auth): bootstrap init_db on fresh DB; permissions matrix for inbox endpoints
Two bootability gaps caught by running the live stack: 1. cyclone.auth.bootstrap.run() called SessionLocal() before db.init_db() had run on a fresh DB, so 'python -m cyclone serve' / 'users create' / '--help' all crashed with 'db.init_db() has not been called'. Add init_db() inside bootstrap (idempotent — no-op once the schema is current) so first-boot works without manual prep. 2. PERMISSIONS matrix didn't register the inbox endpoints that were added in SP10 (export.csv) and SP14 (candidates/dismiss, rejected/resubmit, payer-rejected/acknowledge). The matrix's longest-prefix match meant /api/inbox/candidates/dismiss matched the '/api/inbox' prefix but only because that single entry existed; adding the dedicated write-prefix entries makes the intent explicit and keeps viewer role out of every inbox write even if the broader /api/inbox prefix is later broadened. Verified live: - admin can login, GET /api/inbox/lanes (200), GET /api/inbox/export.csv (200), POST /api/inbox/candidates/dismiss (200). - viewer can login, GET /api/inbox/lanes (200), GET /api/inbox/export.csv (200), but POST /api/inbox/candidates/dismiss and POST /api/inbox/rejected/resubmit both return 403.
This commit is contained in:
@@ -45,6 +45,16 @@ def run() -> None:
|
||||
username = os.environ.get("CYCLONE_ADMIN_USERNAME")
|
||||
password = os.environ.get("CYCLONE_ADMIN_PASSWORD")
|
||||
|
||||
# First-boot fix: ``python -m cyclone`` calls bootstrap before any
|
||||
# subcommand or the FastAPI lifespan handler runs, so on a brand-new
|
||||
# DB ``SessionLocal()`` raises "init_db() has not been called".
|
||||
# Initialize here so ``serve``, ``users create``, and friends can
|
||||
# all reach the DB without the operator having to know about
|
||||
# migrations. Idempotent — no-op when the schema is already current.
|
||||
from cyclone import db as _db
|
||||
|
||||
_db.init_db()
|
||||
|
||||
with SessionLocal()() as db:
|
||||
existing = db.execute(select(User)).scalars().first()
|
||||
if existing is not None:
|
||||
|
||||
@@ -41,6 +41,7 @@ PERMISSIONS: dict[tuple[str, str], set[Role]] = {
|
||||
("GET", "/api/dashboard/summary"): ALL_ROLES,
|
||||
("GET", "/api/activity"): ALL_ROLES,
|
||||
("GET", "/api/inbox/lanes"): ALL_ROLES,
|
||||
("GET", "/api/inbox/export.csv"): ALL_ROLES,
|
||||
("GET", "/api/reconcile"): ALL_ROLES,
|
||||
("GET", "/api/audit-log"): ADMIN_ONLY,
|
||||
|
||||
@@ -48,6 +49,9 @@ PERMISSIONS: dict[tuple[str, str], set[Role]] = {
|
||||
("POST", "/api/parse-837"): WRITE_ROLES,
|
||||
("POST", "/api/parse-835"): WRITE_ROLES,
|
||||
("POST", "/api/inbox"): WRITE_ROLES,
|
||||
("POST", "/api/inbox/candidates"): WRITE_ROLES,
|
||||
("POST", "/api/inbox/rejected"): WRITE_ROLES,
|
||||
("POST", "/api/inbox/payer-rejected"): WRITE_ROLES,
|
||||
("POST", "/api/reconcile"): WRITE_ROLES,
|
||||
("POST", "/api/resubmit"): WRITE_ROLES,
|
||||
("POST", "/api/acks"): WRITE_ROLES,
|
||||
|
||||
Reference in New Issue
Block a user