Session keys hardened (namespaced hashes of tokens). Various cleanup.
This commit is contained in:
+37
-4
@@ -3,6 +3,7 @@ from contextlib import suppress
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from fastapi import (
|
||||
Body,
|
||||
Depends,
|
||||
FastAPI,
|
||||
HTTPException,
|
||||
@@ -13,7 +14,7 @@ from fastapi import (
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.security import HTTPBearer
|
||||
|
||||
from paskia import db
|
||||
from paskia import authcode, db
|
||||
from paskia._version import __version__
|
||||
from paskia.authsession import EXPIRES, expires, get_reset
|
||||
from paskia.fastapi import authz, session, user
|
||||
@@ -22,7 +23,7 @@ from paskia.fastapi.session import AUTH_COOKIE, AUTH_COOKIE_NAME, get_client_ip
|
||||
from paskia.globals import passkey as global_passkey
|
||||
from paskia.util import hostutil, htmlutil, passphrase, userinfo, vitedev
|
||||
|
||||
bearer_auth = HTTPBearer(auto_error=True)
|
||||
bearer_auth = HTTPBearer(auto_error=False)
|
||||
|
||||
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
|
||||
|
||||
@@ -67,6 +68,34 @@ async def general_exception_handler(
|
||||
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
|
||||
|
||||
|
||||
@app.post("/exchange")
|
||||
async def exchange_code(
|
||||
request: Request,
|
||||
response: Response,
|
||||
code: str = Body(..., embed=True),
|
||||
):
|
||||
"""Exchange a session code for setting the session cookie.
|
||||
|
||||
Called by frontend after WebSocket authentication.
|
||||
The code is ephemeral (60s TTL) and can only be used once.
|
||||
"""
|
||||
auth_code = authcode.codes.pop(code, None)
|
||||
if not auth_code:
|
||||
raise HTTPException(status_code=400, detail="Invalid or expired code")
|
||||
|
||||
secret = auth_code.session_key
|
||||
|
||||
# Verify the session exists
|
||||
host = hostutil.normalize_host(request.headers.get("host", ""))
|
||||
ctx = db.data().session_ctx(secret, host)
|
||||
if not ctx:
|
||||
raise HTTPException(status_code=400, detail="Session not found")
|
||||
|
||||
# Set the session cookie
|
||||
session.set_session_cookie(response, secret)
|
||||
return {"status": "ok", "user": str(ctx.user.uuid)}
|
||||
|
||||
|
||||
@app.post("/validate")
|
||||
async def validate_token(
|
||||
request: Request,
|
||||
@@ -91,7 +120,7 @@ async def validate_token(
|
||||
consumed = EXPIRES - (ctx.session.expiry - datetime.now(UTC))
|
||||
if not timedelta(0) < consumed < _REFRESH_INTERVAL:
|
||||
db.update_session(
|
||||
auth,
|
||||
ctx.session.key,
|
||||
ip=get_client_ip(request),
|
||||
user_agent=request.headers.get("user-agent") or "",
|
||||
expiry=expires(),
|
||||
@@ -230,6 +259,8 @@ async def api_user_info(
|
||||
@app.get("/token-info")
|
||||
async def token_info(credentials=Depends(bearer_auth)):
|
||||
"""Get reset/device-add token info. Pass token via Bearer header."""
|
||||
if not credentials or not credentials.credentials:
|
||||
raise HTTPException(401, "Bearer token required")
|
||||
token = credentials.credentials
|
||||
if not passphrase.is_well_formed(token):
|
||||
raise HTTPException(400, "Invalid token format")
|
||||
@@ -254,7 +285,7 @@ async def api_logout(request: Request, response: Response, auth=AUTH_COOKIE):
|
||||
if not ctx:
|
||||
return {"message": "Already logged out"}
|
||||
with suppress(Exception):
|
||||
db.delete_session(auth, ctx=ctx, action="logout")
|
||||
db.delete_session(ctx.session.key, ctx=ctx, action="logout")
|
||||
session.clear_session_cookie(response)
|
||||
return {"message": "Logged out successfully"}
|
||||
|
||||
@@ -263,6 +294,8 @@ async def api_logout(request: Request, response: Response, auth=AUTH_COOKIE):
|
||||
async def api_set_session(
|
||||
request: Request, response: Response, auth=Depends(bearer_auth)
|
||||
):
|
||||
if not auth or not auth.credentials:
|
||||
raise HTTPException(401, "Bearer token required")
|
||||
ctx = db.data().session_ctx(auth.credentials, request.headers.get("host"))
|
||||
if not ctx:
|
||||
raise HTTPException(401, "Session expired")
|
||||
|
||||
Reference in New Issue
Block a user