fix: typer exit codes, JWT 7d expiry, shared test fixtures, AdminTab query, mantine 9.6.3
This commit is contained in:
parent
03af41e837
commit
696db34c90
16 changed files with 210 additions and 251 deletions
|
|
@ -36,11 +36,3 @@ def require_admin(user: Annotated[User, Depends(get_current_user)]) -> User:
|
|||
if user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="admin only")
|
||||
return user
|
||||
|
||||
|
||||
def resolve_tenant(user: User | None, explicit: str | None, default: str = "default") -> str:
|
||||
"""Tenant comes from the session; the body/query param is a deprecated
|
||||
fallback for unauthenticated callers."""
|
||||
if user is not None:
|
||||
return user.tenant_id
|
||||
return explicit or default
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ Keep API handlers thin: all persistence lives here. Tokens and session
|
|||
JWTs are bearer secrets — log user/request ids only, never secret values.
|
||||
|
||||
Sessions are stateless HS256 JWTs (sub=user.id, email, role, tenant_id,
|
||||
iat, exp=30d, jti) in the HttpOnly session cookie. No session table:
|
||||
logout clears the cookie; role/tenant changes apply at next login (or
|
||||
immediately for deleted users — the user row is reloaded per request).
|
||||
iat, exp=7d, jti) in the HttpOnly session cookie. No session table and no
|
||||
revocation list: logout clears the cookie client-side, and a stolen token
|
||||
is valid until its short expiry. Role/tenant changes apply at next login
|
||||
(deleted users fail closed — the user row is reloaded per request).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -28,7 +29,10 @@ from app.settings import settings
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
MAGIC_LINK_TTL = timedelta(minutes=15)
|
||||
SESSION_TTL = timedelta(days=30)
|
||||
# Session window is deliberately short: sessions are stateless JWTs with
|
||||
# no server-side revocation (logout only clears the cookie), so a stolen
|
||||
# token stays valid until expiry. 7 days bounds that window.
|
||||
SESSION_TTL = timedelta(days=7)
|
||||
|
||||
# Public request endpoint: at most this many requests per email per hour.
|
||||
REQUESTS_PER_EMAIL_PER_HOUR = 5
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ def _blocking_startup_sync() -> None:
|
|||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
task: asyncio.Task | None = None
|
||||
stop: asyncio.Event | None = None
|
||||
if not settings.jwt_secret:
|
||||
# Fail-closed sessions need a signing secret; without one every
|
||||
# login 500s. Warn loudly instead of crashing the whole API.
|
||||
logger.error("JWT_SECRET is empty — logins will fail until it is set")
|
||||
if settings.flows_watch_enabled:
|
||||
await asyncio.to_thread(_blocking_startup_sync)
|
||||
stop = asyncio.Event()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue