Major cleanup and refactoring of the backend (frontend not fully updated).

This commit is contained in:
2025-08-02 00:32:27 +00:00
parent f86406b4d4
commit 9b82f77729
16 changed files with 451 additions and 920 deletions
+43 -83
View File
@@ -1,114 +1,74 @@
"""
Device addition API handlers for WebAuthn authentication.
import logging
This module provides endpoints for authenticated users to:
- Generate device addition links with human-readable tokens
- Validate device addition tokens
- Add new passkeys to existing accounts via tokens
"""
from uuid import UUID
from fastapi import FastAPI, Path, Request
from fastapi import Cookie, HTTPException, Request
from fastapi.responses import RedirectResponse
from ..db import sql
from ..util.passphrase import generate
from ..util.session import get_client_info
from .session import get_current_user, is_device_addition_session, set_session_cookie
from ..util import passphrase, tokens
from . import session
def register_reset_routes(app: FastAPI):
def register_reset_routes(app):
"""Register all device addition/reset routes on the FastAPI app."""
@app.post("/auth/create-device-link")
async def api_create_device_link(request: Request):
@app.post("/auth/create-link")
async def api_create_link(request: Request, auth=Cookie(None)):
"""Create a device addition link for the authenticated user."""
try:
# Require authentication
user = await get_current_user(request)
if not user:
return {"error": "Authentication required"}
s = await session.get_session(auth)
# Generate a human-readable token
token = generate(n=4, sep=".") # e.g., "able-ocean-forest-dawn"
# Create session token in database with credential_id=None for device addition
client_info = get_client_info(request)
await sql.create_session(user.user_id, None, token, client_info)
token = passphrase.generate() # e.g., "cross.rotate.yin.note.evoke"
await sql.create_session(
user_uuid=s.user_uuid,
key=tokens.reset_key(token),
expires=session.expires(),
info=session.infodict(request, "device addition"),
)
# Generate the device addition link with pretty URL
addition_link = f"{request.headers.get('origin', '')}/auth/{token}"
path = request.url.path.removesuffix("create-link") + token
url = f"{request.headers['origin']}{path}"
return {
"status": "success",
"message": "Device addition link generated successfully",
"addition_link": addition_link,
"expires_in_hours": 24,
"message": "Registration link generated successfully",
"url": url,
"expires": session.expires().isoformat(),
}
except ValueError:
return {"error": "Authentication required"}
except Exception as e:
return {"error": f"Failed to create device addition link: {str(e)}"}
return {"error": f"Failed to create registration link: {str(e)}"}
@app.get("/auth/device-session-check")
async def check_device_session(request: Request):
"""Check if the current session is for device addition."""
is_device_session = await is_device_addition_session(request)
return {"device_addition_session": is_device_session}
@app.get("/auth/{passphrase}")
@app.get("/auth/{reset_token}")
async def reset_authentication(
request: Request,
passphrase: str = Path(pattern=r"^\w+(\.\w+){2,}$"),
reset_token: str,
):
"""Verifies the token and redirects to auth app for credential registration."""
# This route should only match to exact passphrases
print(f"Reset handler called with url: {request.url.path}")
if not passphrase.is_well_formed(reset_token):
raise HTTPException(status_code=404)
try:
# Get session token to validate it exists and get user_id
session = await sql.get_session(passphrase)
if not session:
# Token doesn't exist, redirect to home
return RedirectResponse(url="/", status_code=303)
key = tokens.reset_key(reset_token)
sess = await sql.get_session(key)
if not sess:
raise ValueError("Invalid or expired registration token")
# Check if this is a device addition session (credential_id is None)
if session.credential_id is not None:
# Not a device addition session, redirect to home
return RedirectResponse(url="/", status_code=303)
# Create a device addition session token for the user
client_info = get_client_info(request)
session_token = await sql.create_session(
UUID(bytes=session.user_id), None, None, client_info
)
# Create response and set session cookie
response = RedirectResponse(url="/auth/", status_code=303)
set_session_cookie(response, session_token)
session.set_session_cookie(response, reset_token)
return response
except Exception:
# On any error, redirect to home
return RedirectResponse(url="/", status_code=303)
async def use_reset_token(token: str) -> dict:
"""Delete a device addition token after successful use."""
try:
# Get session token first to validate it exists and is not expired
session = await sql.get_session(token)
if not session:
return {"error": "Invalid or expired device addition token"}
# Check if this is a device addition session (credential_id is None)
if session.credential_id is not None:
return {"error": "Invalid device addition token"}
# Delete the token (it's now used)
await sql.delete_session(token)
return {
"status": "success",
"message": "Device addition token used successfully",
}
except Exception as e:
return {"error": f"Failed to use device addition token: {str(e)}"}
except Exception as e:
# On any error, redirect to auth app
if isinstance(e, ValueError):
msg = str(e)
else:
logging.exception("Internal Server Error in reset_authentication")
msg = "Internal Server Error"
return RedirectResponse(url=f"/auth/#{msg}", status_code=303)