"""Bootstrap endpoint — consumes the install token to create the first admin.""" from __future__ import annotations import logging from flask import Blueprint, jsonify, make_response, request from pydantic import BaseModel, Field, ValidationError from app.api._validation import Email from sqlalchemy import select from app.core.rate_limit import limiter from app.db.session import session_scope from app.models.auth import User from app.services.bootstrap import ( BootstrapError, bootstrap_admin, ensure_system_groups, ) bp = Blueprint("setup", __name__, url_prefix="/setup") log = logging.getLogger("metamorph.api.setup") class SetupPayload(BaseModel): install_token: str = Field(min_length=20) email: Email password: str = Field(min_length=8) display_name: str | None = None @bp.get("") def setup_status(): """Tell the SPA whether the bootstrap has already been done. Used by the front to redirect to /setup vs /login on first paint. """ with session_scope() as s: any_user = s.scalar(select(User.id).limit(1)) is not None return jsonify({"completed": any_user}) @bp.post("") @limiter.limit("5 per minute") def setup(): try: payload = SetupPayload.model_validate(request.get_json(silent=True) or {}) except ValidationError as e: return jsonify({"error": "invalid_request", "details": e.errors()}), 400 try: result = bootstrap_admin( install_token=payload.install_token, email=payload.email, password=payload.password, display_name=payload.display_name, ) except BootstrapError as e: return jsonify({"error": "bootstrap_failed", "message": str(e)}), 409 except ValueError as e: return jsonify({"error": "invalid_request", "message": str(e)}), 400 log.warning( "metamorph.bootstrap.completed", extra={"user_id": str(result.user_id), "admin_group_id": str(result.admin_group_id)}, ) # Make sure the redteam/blueteam groups exist too (idempotent). ensure_system_groups() return make_response( jsonify( { "ok": True, "user_id": str(result.user_id), } ), 201, )