Files
ds-chat/backend/app/routers/signup.py
T
ksmithandClaude Sonnet 5 b26643527d Add per-device active sessions with revocation (#69)
Replaces the stateless signed-cookie session (bare user_id) with a
real server-side sessions table -- the cookie now just carries an
opaque session id, resolved against the DB on every request. Each
session records IP address (respects X-Forwarded-For), a parsed
device label, and last-seen time (throttled updates, not written on
every request).

New GET/DELETE /api/auth/sessions endpoints and an "Active sessions"
section in Profile settings let a user see every device they're
logged in from and revoke one they don't recognize -- including their
own current session, which just signs them out.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 20:22:50 -06:00

45 lines
1.5 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.schemas.site_invite import SignupComplete, SignupValidateRead
from app.schemas.user import UserRead
from app.services.auth_service import DuplicateUserError
from app.services.session_service import start_session
from app.services.site_invite_service import (
SiteInviteInvalidError,
complete_signup,
validate_signup_token,
)
router = APIRouter(prefix="/api/signup", tags=["signup"])
@router.get("/validate", response_model=SignupValidateRead)
async def validate_signup_endpoint(
token: str = Query(...),
db: AsyncSession = Depends(get_db),
):
try:
invite = await validate_signup_token(db, token)
except SiteInviteInvalidError:
raise HTTPException(status_code=400, detail="This invite link is invalid or has expired")
return SignupValidateRead(email=invite.email)
@router.post("", response_model=UserRead)
async def complete_signup_endpoint(
data: SignupComplete,
request: Request,
db: AsyncSession = Depends(get_db),
):
try:
user = await complete_signup(db, data.token, data.username, data.password)
except SiteInviteInvalidError:
raise HTTPException(status_code=400, detail="This invite link is invalid or has expired")
except DuplicateUserError:
raise HTTPException(status_code=409, detail="That username or email is already taken")
await start_session(request, db, user.id)
return user