Spaces:
Runtime error
Runtime error
| """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"]) | |
| 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) | |
| 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) | |
| 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) | |