158 lines
5.7 KiB
Python

"""
API endpoints for user management and session handling.
This module contains all the HTTP API endpoints for:
- User information retrieval
- User credentials management
- Session token validation and refresh
- Login/logout functionality
"""
from uuid import UUID
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_reset, get_session
from ..db import db
from ..util.tokens import session_key
from . import session
bearer_auth = HTTPBearer(auto_error=True)
def register_api_routes(app: FastAPI):
"""Register all API routes on the FastAPI app."""
@app.post("/auth/validate")
async def validate_token(auth=Cookie(None)):
"""Lightweight token validation endpoint."""
try:
s = await get_session(auth)
return {
"status": "success",
"valid": True,
"user_uuid": str(s.user_uuid),
}
except ValueError:
return {"status": "error", "valid": False}
@app.post("/auth/user-info")
async def api_user_info(response: Response, auth=Cookie(None)):
"""Get full user information for the authenticated user."""
try:
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)
credentials = []
user_aaguids = set()
for cred_id in credential_ids:
c = await db.instance.get_credential_by_id(cred_id)
# Convert AAGUID to string format
aaguid_str = str(c.aaguid)
user_aaguids.add(aaguid_str)
# Check if this is the current session credential
is_current_session = s.credential_uuid == c.uuid
credentials.append(
{
"credential_uuid": str(c.uuid),
"aaguid": aaguid_str,
"created_at": c.created_at.isoformat(),
"last_used": c.last_used.isoformat() if c.last_used else None,
"last_verified": c.last_verified.isoformat()
if c.last_verified
else None,
"sign_count": c.sign_count,
"is_current_session": is_current_session,
}
)
# Get AAGUID information for only the AAGUIDs that the user has
aaguid_info = aaguid.filter(user_aaguids)
# Sort credentials by creation date (earliest first, most recently created last)
credentials.sort(key=lambda cred: cred["created_at"])
return {
"status": "success",
"authenticated": not reset,
"session_type": s.info["type"],
"user": {
"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,
},
"credentials": credentials,
"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")
async def api_logout(response: Response, auth=Cookie(None)):
"""Log out the current user by clearing the session cookie and deleting from database."""
if not auth:
return {"status": "success", "message": "Already logged out"}
# 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"}
@app.post("/auth/set-session")
async def api_set_session(response: Response, auth=Depends(bearer_auth)):
"""Set session cookie from Authorization header. Fetched after login by WebSocket."""
try:
user = await get_session(auth.credentials)
if not user:
raise ValueError("Invalid Authorization header.")
session.set_session_cookie(response, auth.credentials)
return {
"status": "success",
"message": "Session cookie set successfully",
"user_uuid": str(user.user_uuid),
}
except ValueError as e:
response.status_code = 400
return {"error": str(e)}
except Exception:
response.status_code = 500
return {"error": "Failed to set session"}
@app.delete("/auth/credential/{uuid}")
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"}