# retoor import logging from typing import Annotated from fastapi import Depends, APIRouter, Request from fastapi.responses import JSONResponse from devplacepy.database import get_table from devplacepy.utils import verify_password_async, get_current_user from devplacepy.models import LoginForm from devplacepy.dependencies import json_or_form from devplacepy.services.access_tokens import issue_token from devplacepy.services.audit import record as audit logger = logging.getLogger(__name__) router = APIRouter() @router.post("/token") async def token( request: Request, data: Annotated[LoginForm, Depends(json_or_form(LoginForm))], ): """Issue a DevPlace access token. Accepts ``email`` + ``password`` (JSON or form-encoded). Returns a JSON object with ``access_token``, ``token_type``, and ``expires_in`` on success, or a ``401`` error on bad credentials. """ identifier = data.email.strip().lower() password = data.password if not identifier or not password: return JSONResponse( {"error": "email and password are required"}, status_code=400, ) users = get_table("users") user = users.find_one(email=identifier) or users.find_one( username=identifier ) if not user or not await verify_password_async( password, user.get("password_hash", "") ): audit.record( request, "auth.token.failure", user=None, actor_kind="guest", result="failure", metadata={"identifier": identifier}, summary=f"failed token request for {identifier}", ) return JSONResponse( {"error": "Invalid credentials"}, status_code=401, ) if not user.get("is_active", True): audit.record( request, "auth.token.failure", user=None, actor_kind="guest", result="failure", metadata={"identifier": identifier, "reason": "inactive"}, summary=f"token request for inactive user {identifier}", ) return JSONResponse( {"error": "Account is deactivated"}, status_code=403, ) token = issue_token(user) logger.info("Token issued for %s", user["username"]) audit.record( request, "auth.token.issued", user=user, target_type="user", target_uid=user["uid"], target_label=user["username"], metadata={"token_uid": token["uid"], "expires_in": token["expires_in"]}, summary=f"access token issued for {user['username']}", links=[audit.target("user", user["uid"], user["username"])], ) return JSONResponse( { "access_token": token["access_token"], "token_type": token["token_type"], "expires_in": token["expires_in"], } )