Files

48 lines
1.6 KiB
Python

"""Identity: who am I, and admin-driven member onboarding."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from ..auth import Ctx, issue_token
from ..db import record_audit
from ..deps import get_ctx
from ..util import USERNAME_RE, now_iso
router = APIRouter()
class UserCreate(BaseModel):
username: str
role: str = "member"
@router.post("/v1/users")
def create_user(body: UserCreate, request: Request, ctx: Ctx = Depends(get_ctx)):
"""Admin onboards a member directly, bypassing the invite code."""
admin = ctx.require_admin(request)
if body.role not in ("admin", "member"):
raise HTTPException(400, "role must be 'admin' or 'member'")
if not USERNAME_RE.match(body.username or ""):
raise HTTPException(400, "invalid username")
if ctx.conn.execute(
"SELECT 1 FROM users WHERE username = %s", (body.username,)
).fetchone():
raise HTTPException(409, "username already exists")
cur = ctx.conn.execute(
"INSERT INTO users (username, role, created_at) VALUES (%s, %s, %s) RETURNING id",
(body.username, body.role, now_iso()),
)
user_id = cur.fetchone()["id"]
raw = issue_token(ctx.conn, user_id, "initial")
record_audit(ctx.conn, admin["id"], "user.create", {"username": body.username, "role": body.role})
return {
"token": raw,
"user": {"id": user_id, "username": body.username, "role": body.role},
}
@router.get("/v1/me")
def me(request: Request, ctx: Ctx = Depends(get_ctx)):
return ctx.require_user(request)