sampro69's picture
deploy backend on Gradio SDK
3f72838
Raw
History Blame Contribute Delete
3.73 kB
"""Login endpoints — the only places that mint session tokens."""
import logging
from fastapi import APIRouter, HTTPException
import database
from config import ADMIN_BOOTSTRAP_SECRET
from database import verify_access_code
from models.schemas import (
AdminLoginRequest,
AdminLoginResponse,
BootstrapAdminRequest,
LoginRequest,
LoginResponse,
)
from services.admin_service import hash_password, verify_admin_credentials
from utils.auth import create_token
logger = logging.getLogger(__name__)
router = APIRouter(tags=["auth"])
@router.post("/login", response_model=LoginResponse)
def login(body: LoginRequest):
"""Verify a team's email + access code server-side (service role key) and
issue a signed session token. The frontend never queries the teams table
directly. Both must match the same team -- an access code alone is not
enough (it's shared by the whole team and could leak more easily than a
specific member's email)."""
row = verify_access_code(body.access_code.strip(), body.email.strip())
if not row:
raise HTTPException(
status_code=401, detail="That email and access code don't match a team."
)
token = create_token(role="team", team_id=row["team_id"], team_name=row["team_name"])
return LoginResponse(team_id=row["team_id"], team_name=row["team_name"], token=token)
@router.post("/admin/login", response_model=AdminLoginResponse)
def admin_login(body: AdminLoginRequest):
"""Verify an admin's email + password against the bcrypt hash stored in
Supabase (see services/admin_service.py) and issue a signed session
token. Replaces the old single shared ADMIN_PASSWORD env-var check --
each organizer now has their own account."""
admin = verify_admin_credentials(body.email.strip(), body.password)
if not admin:
raise HTTPException(status_code=401, detail="Wrong email or password.")
token = create_token(role="admin", admin_id=admin["admin_id"], admin_name=admin["admin_name"])
return AdminLoginResponse(admin_id=admin["admin_id"], admin_name=admin["admin_name"], token=token)
@router.post("/admin/bootstrap", response_model=AdminLoginResponse)
def bootstrap_admin(body: BootstrapAdminRequest):
"""Create the FIRST admin account. Only works if:
1. ADMIN_BOOTSTRAP_SECRET is set on the server, AND
2. the provided bootstrap_secret matches it, AND
3. no admin account exists yet.
Once at least one admin exists, this always 403s regardless of the
secret -- so a leaked bootstrap secret can't be used to keep minting
admin accounts after the first one. Further admins are created via the
authenticated POST /admin/admins endpoint instead (see routers/admin.py)."""
if not ADMIN_BOOTSTRAP_SECRET:
raise HTTPException(status_code=403, detail="Bootstrap is disabled on this server.")
if body.bootstrap_secret != ADMIN_BOOTSTRAP_SECRET:
raise HTTPException(status_code=403, detail="Invalid bootstrap secret.")
if database.count_admins() > 0:
raise HTTPException(
status_code=403,
detail="An admin account already exists — bootstrap is only for the very first one. "
"Use an existing admin account to create more from the Admin panel.",
)
password_hash = hash_password(body.password)
row = database.create_admin(body.admin_name.strip(), body.email.strip(), password_hash)
logger.info("Bootstrap: created first admin account admin_id=%s", row["admin_id"])
token = create_token(role="admin", admin_id=row["admin_id"], admin_name=row["admin_name"])
return AdminLoginResponse(admin_id=row["admin_id"], admin_name=row["admin_name"], token=token)