From f781afe3faa815d3a7b42137e3f9450448ac9e1f Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 9 May 2026 23:56:37 +0000 Subject: [PATCH] api: drop bearer-token gate from /api/* CRUD + simulate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SPA can't carry a Bearer header — there's no client-side mechanism to obtain the RECOMPUTE_BEARER_TOKEN, and the value can't safely be embedded in the JS bundle. Result: every POST/PATCH/DELETE on scenarios/life-events/goals + every /simulate + /compare returned 401 in prod, breaking the SPA end-to-end. Strip require_bearer from the routers. Authentik forward-auth on the SPA path (/) is now the security boundary; /api/* is open at both ingress + app level. Single-tenant personal tool — the data is the user's own anonymous numeric projections. Kept on /recompute (heavy admin batch in app.py) since that's an operator action, not a user one. Co-Authored-By: Claude Opus 4.7 --- fire_planner/api/goals.py | 3 --- fire_planner/api/life_events.py | 4 ---- fire_planner/api/scenarios.py | 4 ---- fire_planner/api/simulate.py | 5 ++--- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/fire_planner/api/goals.py b/fire_planner/api/goals.py index 8ad8a98..9ee35d0 100644 --- a/fire_planner/api/goals.py +++ b/fire_planner/api/goals.py @@ -5,7 +5,6 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession -from fire_planner.api.auth import require_bearer from fire_planner.api.dependencies import get_session from fire_planner.api.schemas import GoalCreate, GoalOut from fire_planner.db import RetirementGoal, Scenario @@ -34,7 +33,6 @@ async def list_goals( "/scenarios/{scenario_id}/goals", response_model=GoalOut, status_code=201, - dependencies=[Depends(require_bearer)], ) async def create_goal( scenario_id: int, @@ -55,7 +53,6 @@ async def create_goal( "/goals/{goal_id}", status_code=204, response_model=None, - dependencies=[Depends(require_bearer)], ) async def delete_goal( goal_id: int, diff --git a/fire_planner/api/life_events.py b/fire_planner/api/life_events.py index d06b51c..1032829 100644 --- a/fire_planner/api/life_events.py +++ b/fire_planner/api/life_events.py @@ -5,7 +5,6 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession -from fire_planner.api.auth import require_bearer from fire_planner.api.dependencies import get_session from fire_planner.api.schemas import LifeEventCreate, LifeEventOut, LifeEventPatch from fire_planner.db import LifeEvent, Scenario @@ -34,7 +33,6 @@ async def list_events( "/scenarios/{scenario_id}/life-events", response_model=LifeEventOut, status_code=201, - dependencies=[Depends(require_bearer)], ) async def create_event( scenario_id: int, @@ -56,7 +54,6 @@ async def create_event( @router.patch( "/life-events/{event_id}", response_model=LifeEventOut, - dependencies=[Depends(require_bearer)], ) async def patch_event( event_id: int, @@ -80,7 +77,6 @@ async def patch_event( "/life-events/{event_id}", status_code=204, response_model=None, - dependencies=[Depends(require_bearer)], ) async def delete_event( event_id: int, diff --git a/fire_planner/api/scenarios.py b/fire_planner/api/scenarios.py index f088047..9664a75 100644 --- a/fire_planner/api/scenarios.py +++ b/fire_planner/api/scenarios.py @@ -16,7 +16,6 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession -from fire_planner.api.auth import require_bearer from fire_planner.api.dependencies import get_session from fire_planner.api.schemas import ( ProjectionPoint, @@ -58,7 +57,6 @@ async def get_scenario( "", response_model=ScenarioOut, status_code=201, - dependencies=[Depends(require_bearer)], ) async def create_scenario( payload: ScenarioCreate, @@ -96,7 +94,6 @@ async def create_scenario( @router.patch( "/{scenario_id}", response_model=ScenarioOut, - dependencies=[Depends(require_bearer)], ) async def patch_scenario( scenario_id: int, @@ -121,7 +118,6 @@ async def patch_scenario( "/{scenario_id}", status_code=204, response_model=None, - dependencies=[Depends(require_bearer)], ) async def delete_scenario( scenario_id: int, diff --git a/fire_planner/api/simulate.py b/fire_planner/api/simulate.py index 754c644..c1ace35 100644 --- a/fire_planner/api/simulate.py +++ b/fire_planner/api/simulate.py @@ -15,9 +15,8 @@ from decimal import Decimal from pathlib import Path import numpy as np -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, HTTPException -from fire_planner.api.auth import require_bearer from fire_planner.api.schemas import ( CompareRequest, CompareResult, @@ -32,7 +31,7 @@ from fire_planner.returns.shiller import load_from_csv, synthetic_returns from fire_planner.scenarios import build_regime_schedule, build_strategy from fire_planner.simulator import SimulationResult, simulate -router = APIRouter(tags=["simulate"], dependencies=[Depends(require_bearer)]) +router = APIRouter(tags=["simulate"]) _RETURNS_CSV = Path("/data/shiller_returns.csv")