Files
Metamorph/backend/app/api/setup.py

80 lines
2.2 KiB
Python
Raw Normal View History

2026-05-11 06:05:27 +02:00
"""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,
)