feat(auth): owner-bound bot tokens and self-service bot management

This commit is contained in:
2026-04-14 00:34:44 +09:00
parent 9787cf3159
commit 2dab17cbb1
16 changed files with 576 additions and 35 deletions
+78 -1
View File
@@ -14,6 +14,7 @@ from app.schemas.auth import (
CreateBotPrincipalRequest,
CreateBotTokenRequest,
CreateClientTokenRequest,
CreateSelfBotPrincipalRequest,
IssuedBotTokenResponse,
IssuedClientTokenResponse,
LoginRequest,
@@ -58,6 +59,7 @@ def _to_bot_principal_response(bot: BotPrincipal) -> BotPrincipalResponse:
return BotPrincipalResponse(
id=bot.id,
name=bot.name,
owner_user_id=bot.owner_user_id,
permission=bot.permission,
description=bot.description,
is_active=bot.is_active,
@@ -94,7 +96,7 @@ def _raise_auth_error(exc: ValueError) -> NoReturn:
detail = str(exc)
if "잘못된 사용자명 또는 비밀번호" in detail:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail) from exc
if "초과" in detail:
if "초과" in detail or "권한이 없습니다" in detail:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=detail) from exc
if "찾을 수 없습니다" in detail:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=detail) from exc
@@ -156,6 +158,81 @@ def create_bot_principal(
_raise_auth_error(exc)
@router.post("/me/bots", response_model=BotPrincipalResponse, status_code=status.HTTP_201_CREATED, dependencies=[Depends(require_read)])
def create_owned_bot_principal(
payload: CreateSelfBotPrincipalRequest,
auth_context: AuthContext = Depends(require_read),
service: AuthService = Depends(get_auth_service),
) -> BotPrincipalResponse:
try:
return _to_bot_principal_response(service.create_owned_bot_principal(auth_context, payload))
except ValueError as exc:
_raise_auth_error(exc)
@router.get("/me/bots", response_model=list[BotPrincipalResponse], dependencies=[Depends(require_read)])
def list_owned_bot_principals(
auth_context: AuthContext = Depends(require_read),
is_active: bool | None = None,
service: AuthService = Depends(get_auth_service),
) -> list[BotPrincipalResponse]:
try:
return [
_to_bot_principal_response(bot)
for bot in service.list_owned_bot_principals(auth_context, is_active=is_active)
]
except ValueError as exc:
_raise_auth_error(exc)
@router.post(
"/me/bots/{bot_id}/tokens",
response_model=IssuedBotTokenResponse,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(require_read)],
)
def issue_owned_bot_token(
bot_id: str,
payload: CreateBotTokenRequest,
auth_context: AuthContext = Depends(require_read),
service: AuthService = Depends(get_auth_service),
) -> IssuedBotTokenResponse:
try:
return _to_issued_bot_token_response(service.issue_owned_bot_token(auth_context, bot_id, payload))
except ValueError as exc:
_raise_auth_error(exc)
@router.get("/me/bots/{bot_id}/tokens", response_model=list[AuthTokenResponse], dependencies=[Depends(require_read)])
def list_owned_bot_tokens(
bot_id: str,
auth_context: AuthContext = Depends(require_read),
service: AuthService = Depends(get_auth_service),
) -> list[AuthTokenResponse]:
try:
return [_to_auth_token_response(token) for token in service.list_owned_bot_tokens(auth_context, bot_id)]
except ValueError as exc:
_raise_auth_error(exc)
@router.delete(
"/me/bots/{bot_id}/tokens/{token_id}",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(require_read)],
)
def revoke_owned_bot_token(
bot_id: str,
token_id: str,
auth_context: AuthContext = Depends(require_read),
service: AuthService = Depends(get_auth_service),
) -> Response:
try:
service.revoke_owned_bot_token(auth_context, bot_id, token_id)
except ValueError as exc:
_raise_auth_error(exc)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@router.get("/bots", response_model=list[BotPrincipalResponse], dependencies=[Depends(require_admin)])
def list_bot_principals(
is_active: bool | None = None,