Checkpoint, fixing reset token handling broken in earlier edits.

This commit is contained in:
Leo Vasanko
2025-08-06 09:55:14 -06:00
parent c42864794a
commit cf138d90c5
11 changed files with 392 additions and 170 deletions

View File

@@ -24,7 +24,7 @@ def main():
host=args.host,
port=args.port,
reload=args.dev,
log_level="debug" if args.dev else "info",
log_level="info",
)

View File

@@ -10,11 +10,13 @@ This module contains all the HTTP API endpoints for:
from uuid import UUID
from fastapi import Cookie, Depends, FastAPI, Request, Response
from fastapi import Cookie, Depends, FastAPI, Response
from fastapi.security import HTTPBearer
from passkey.util import passphrase
from .. import aaguid
from ..authsession import delete_credential, get_session
from ..authsession import delete_credential, get_reset, get_session
from ..db import db
from ..util.tokens import session_key
from . import session
@@ -26,7 +28,7 @@ def register_api_routes(app: FastAPI):
"""Register all API routes on the FastAPI app."""
@app.post("/auth/validate")
async def validate_token(request: Request, response: Response, auth=Cookie(None)):
async def validate_token(auth=Cookie(None)):
"""Lightweight token validation endpoint."""
try:
s = await get_session(auth)
@@ -39,11 +41,12 @@ def register_api_routes(app: FastAPI):
return {"status": "error", "valid": False}
@app.post("/auth/user-info")
async def api_user_info(auth=Cookie(None)):
async def api_user_info(response: Response, auth=Cookie(None)):
"""Get full user information for the authenticated user."""
try:
s = await get_session(auth, reset_allowed=True)
u = await db.instance.get_user_by_user_uuid(s.user_uuid)
reset = passphrase.is_well_formed(auth)
s = await (get_reset if reset else get_session)(auth)
u = await db.instance.get_user_by_uuid(s.user_uuid)
# Get all credentials for the user
credential_ids = await db.instance.get_credentials_by_user_uuid(s.user_uuid)
@@ -82,10 +85,11 @@ def register_api_routes(app: FastAPI):
return {
"status": "success",
"authenticated": not reset,
"session_type": s.info["type"],
"user": {
"user_uuid": str(u.user_uuid),
"user_name": u.user_name,
"user_uuid": str(u.uuid),
"user_name": u.display_name,
"created_at": u.created_at.isoformat() if u.created_at else None,
"last_seen": u.last_seen.isoformat() if u.last_seen else None,
"visits": u.visits,
@@ -94,8 +98,11 @@ def register_api_routes(app: FastAPI):
"aaguid_info": aaguid_info,
}
except ValueError as e:
response.status_code = 400
return {"error": f"Failed to get user info: {e}"}
except Exception:
response.status_code = 500
return {"error": "Failed to get user info"}
@app.post("/auth/logout")
@@ -103,7 +110,11 @@ def register_api_routes(app: FastAPI):
"""Log out the current user by clearing the session cookie and deleting from database."""
if not auth:
return {"status": "success", "message": "Already logged out"}
await db.instance.delete_session(session_key(auth))
# Remove from database if possible
try:
await db.instance.delete_session(session_key(auth))
except Exception:
...
response.delete_cookie("auth")
return {"status": "success", "message": "Logged out successfully"}
@@ -123,18 +134,24 @@ def register_api_routes(app: FastAPI):
}
except ValueError as e:
response.status_code = 400
return {"error": str(e)}
except Exception as e:
return {"error": f"Failed to set session: {e}"}
except Exception:
response.status_code = 500
return {"error": "Failed to set session"}
@app.delete("/auth/credential/{uuid}")
async def api_delete_credential(uuid: UUID, auth: str = Cookie(None)):
async def api_delete_credential(
response: Response, uuid: UUID, auth: str = Cookie(None)
):
"""Delete a specific credential for the current user."""
try:
await delete_credential(uuid, auth)
return {"status": "success", "message": "Credential deleted successfully"}
except ValueError as e:
response.status_code = 400
return {"error": str(e)}
except Exception:
response.status_code = 500
return {"error": "Failed to delete credential"}

View File

@@ -3,9 +3,7 @@ from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import Cookie, FastAPI, Request, Response
from fastapi.responses import (
FileResponse,
)
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from ..authsession import get_session
@@ -35,24 +33,21 @@ app = FastAPI(lifespan=lifespan)
# Mount the WebSocket subapp
app.mount("/auth/ws", ws.app)
# Register API routes
register_api_routes(app)
register_reset_routes(app)
@app.get("/auth/forward-auth")
async def forward_authentication(request: Request, auth=Cookie(None)):
"""A validation endpoint to use with Caddy forward_auth or Nginx auth_request."""
with contextlib.suppress(ValueError):
s = await get_session(auth)
# If authenticated, return a success response
if s.info and s.info["type"] == "authenticated":
return Response(
status_code=204,
headers={
"x-auth-user-uuid": str(s.user_uuid),
},
)
if auth:
with contextlib.suppress(ValueError):
s = await get_session(auth)
# If authenticated, return a success response
if s.info and s.info["type"] == "authenticated":
return Response(
status_code=204,
headers={
"x-auth-user-uuid": str(s.user_uuid),
},
)
# Serve the index.html of the authentication app if not authenticated
return FileResponse(
@@ -72,3 +67,8 @@ app.mount(
async def redirect_to_index():
"""Serve the main authentication app."""
return FileResponse(STATIC_DIR / "index.html")
# Register API routes
register_api_routes(app)
register_reset_routes(app)

View File

@@ -97,30 +97,39 @@ async def websocket_register_new(
@app.websocket("/add_credential")
async def websocket_register_add(ws: WebSocket, auth=Cookie(None)):
"""Register a new credential for an existing user."""
print(auth)
await ws.accept()
origin = ws.headers.get("origin")
origin = ws.headers["origin"]
try:
s = await get_session(auth, reset_allowed=True)
user_uuid = s.user_uuid
# Get user information to get the user_name
user = await db.instance.get_user_by_user_uuid(user_uuid)
user_name = user.user_name
user = await db.instance.get_user_by_uuid(user_uuid)
user_name = user.display_name
challenge_ids = await db.instance.get_credentials_by_user_uuid(user_uuid)
# WebAuthn registration
credential = await register_chat(
ws, user_uuid, user_name, challenge_ids, origin
)
if s.info["type"] == "authenticated":
token = auth
else:
# Replace reset session with a new session
await db.instance.delete_session(s.key)
token = await create_session(
user_uuid, credential.uuid, infodict(ws, "authenticated")
)
assert isinstance(token, str) and len(token) == 16
# Store the new credential in the database
await db.instance.create_credential(credential)
await ws.send_json(
{
"status": "success",
"user_uuid": str(user_uuid),
"credential_id": credential.credential_id.hex(),
"user_uuid": str(user.uuid),
"credential_uuid": str(credential.uuid),
"session_token": token,
"message": "New credential added successfully",
}
)
@@ -136,7 +145,7 @@ async def websocket_register_add(ws: WebSocket, auth=Cookie(None)):
@app.websocket("/authenticate")
async def websocket_authenticate(ws: WebSocket):
await ws.accept()
origin = ws.headers.get("origin")
origin = ws.headers["origin"]
try:
options, challenge = passkey.auth_generate_options()
await ws.send_json(options)