From 39e7309d01fffb03991e18e2724b009b4041ab11 Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Tue, 12 Aug 2025 16:17:50 +0000 Subject: [PATCH] docs(api): more exactly --- app/dependencies/user.py | 49 +++++++++++++++++++++------------- app/router/private/avatar.py | 4 +-- app/router/private/oauth.py | 16 +++++------ app/router/private/username.py | 4 +-- app/router/v2/beatmapset.py | 6 ++--- app/router/v2/relationship.py | 6 ++--- app/router/v2/room.py | 12 ++++----- app/router/v2/score.py | 20 +++++++------- 8 files changed, 64 insertions(+), 53 deletions(-) diff --git a/app/dependencies/user.py b/app/dependencies/user.py index 8ebde87..c85ab57 100644 --- a/app/dependencies/user.py +++ b/app/dependencies/user.py @@ -23,30 +23,45 @@ security = HTTPBearer() oauth2_password = OAuth2PasswordBearer( tokenUrl="oauth/token", - scopes={"*": "Allows access to all scopes."}, + refreshUrl="oauth/token", + scopes={"*": "允许访问全部 API。"}, + description="osu!lazer 或网页客户端密码登录认证,具有全部权限", + scheme_name="Password Grant", ) oauth2_code = OAuth2AuthorizationCodeBearer( authorizationUrl="oauth/authorize", tokenUrl="oauth/token", + refreshUrl="oauth/token", scopes={ - "chat.read": "Allows read chat messages on a user's behalf.", - "chat.write": "Allows sending chat messages on a user's behalf.", - "chat.write_manage": ( - "Allows joining and leaving chat channels on a user's behalf." - ), - "delegate": ( - "Allows acting as the owner of a client; " - "only available for Client Credentials Grant." - ), - "forum.write": "Allows creating and editing forum posts on a user's behalf.", - "friends.read": "Allows reading of the user's friend list.", - "identify": "Allows reading of the public profile of the user (/me).", - "public": "Allows reading of publicly available data on behalf of the user.", + "chat.read": "允许代表用户读取聊天消息。", + "chat.write": "允许代表用户发送聊天消息。", + "chat.write_manage": ("允许代表用户加入和离开聊天频道。"), + "delegate": ("允许作为客户端的所有者进行操作;仅适用于客户端凭证授权。"), + "forum.write": "允许代表用户创建和编辑论坛帖子。", + "friends.read": "允许读取用户的好友列表。", + "identify": "允许读取用户的公开资料 (/me)。", + "public": "允许代表用户读取公开数据。", }, + description="osu! OAuth 认证 (授权码认证)", + scheme_name="Authorization Code Grant", ) +async def get_client_user( + token: Annotated[str, Depends(oauth2_password)], + db: Annotated[AsyncSession, Depends(get_db)], +): + token_record = await get_token_by_access_token(db, token) + if not token_record: + raise HTTPException(status_code=401, detail="Invalid or expired token") + + user = (await db.exec(select(User).where(User.id == token_record.user_id))).first() + if not user: + raise HTTPException(status_code=401, detail="Invalid or expired token") + return user + + async def get_current_user( security_scopes: SecurityScopes, db: Annotated[AsyncSession, Depends(get_db)], @@ -67,11 +82,7 @@ async def get_current_user( settings.osu_web_client_id, ) - if security_scopes.scopes == ["*"]: - # client/web only - if not token_pw or not is_client: - raise HTTPException(status_code=401, detail="Not authenticated") - elif not is_client: + if not is_client: for scope in security_scopes.scopes: if scope not in token_record.scope.split(","): raise HTTPException( diff --git a/app/router/private/avatar.py b/app/router/private/avatar.py index 9ea3216..1e1a811 100644 --- a/app/router/private/avatar.py +++ b/app/router/private/avatar.py @@ -6,7 +6,7 @@ from io import BytesIO from app.database.lazer_user import User from app.dependencies.database import get_db from app.dependencies.storage import get_storage_service -from app.dependencies.user import get_current_user +from app.dependencies.user import get_client_user from app.storage.base import StorageService from .router import router @@ -22,7 +22,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession ) async def upload_avatar( content: bytes = File(...), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), storage: StorageService = Depends(get_storage_service), session: AsyncSession = Depends(get_db), ): diff --git a/app/router/private/oauth.py b/app/router/private/oauth.py index 3caf415..88f8587 100644 --- a/app/router/private/oauth.py +++ b/app/router/private/oauth.py @@ -5,7 +5,7 @@ import secrets from app.database.auth import OAuthClient, OAuthToken from app.database.lazer_user import User from app.dependencies.database import get_db, get_redis -from app.dependencies.user import get_current_user +from app.dependencies.user import get_client_user from .router import router @@ -24,7 +24,7 @@ async def create_oauth_app( name: str = Body(..., max_length=100, description="应用程序名称"), description: str = Body("", description="应用程序描述"), redirect_uris: list[str] = Body(..., description="允许的重定向 URI 列表"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), session: AsyncSession = Depends(get_db), ): result = await session.execute( # pyright: ignore[reportDeprecated] @@ -62,7 +62,7 @@ async def create_oauth_app( async def get_oauth_app( client_id: int, session: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): oauth_app = await session.get(OAuthClient, client_id) if not oauth_app: @@ -82,7 +82,7 @@ async def get_oauth_app( ) async def get_user_oauth_apps( session: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): oauth_apps = await session.exec( select(OAuthClient).where(OAuthClient.owner_id == current_user.id) @@ -107,7 +107,7 @@ async def get_user_oauth_apps( async def delete_oauth_app( client_id: int, session: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): oauth_client = await session.get(OAuthClient, client_id) if not oauth_client: @@ -138,7 +138,7 @@ async def update_oauth_app( description: str = Body("", description="应用程序新描述"), redirect_uris: list[str] = Body(..., description="新的重定向 URI 列表"), session: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): oauth_client = await session.get(OAuthClient, client_id) if not oauth_client: @@ -170,7 +170,7 @@ async def update_oauth_app( async def refresh_secret( client_id: int, session: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): oauth_client = await session.get(OAuthClient, client_id) if not oauth_client: @@ -204,7 +204,7 @@ async def refresh_secret( ) async def generate_oauth_code( client_id: int, - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), redirect_uri: str = Body(..., description="授权后重定向的 URI"), scopes: list[str] = Body(..., description="请求的权限范围列表"), session: AsyncSession = Depends(get_db), diff --git a/app/router/private/username.py b/app/router/private/username.py index ea9cbec..cad47bf 100644 --- a/app/router/private/username.py +++ b/app/router/private/username.py @@ -2,7 +2,7 @@ from __future__ import annotations from app.database.lazer_user import User from app.dependencies.database import get_db -from app.dependencies.user import get_current_user +from app.dependencies.user import get_client_user from .router import router @@ -18,7 +18,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession async def user_rename( new_name: str = Body(..., description="新的用户名"), session: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): """修改用户名 diff --git a/app/router/v2/beatmapset.py b/app/router/v2/beatmapset.py index 152129a..9d2fbae 100644 --- a/app/router/v2/beatmapset.py +++ b/app/router/v2/beatmapset.py @@ -5,7 +5,7 @@ from typing import Literal from app.database import Beatmap, Beatmapset, BeatmapsetResp, FavouriteBeatmapset, User from app.dependencies.database import get_db from app.dependencies.fetcher import get_fetcher -from app.dependencies.user import get_current_user +from app.dependencies.user import get_client_user, get_current_user from app.fetcher import Fetcher from .router import router @@ -68,7 +68,7 @@ async def get_beatmapset( async def download_beatmapset( beatmapset_id: int = Path(..., description="谱面集 ID"), no_video: bool = Query(True, alias="noVideo", description="是否下载无视频版本"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): if current_user.country_code == "CN": return RedirectResponse( @@ -92,7 +92,7 @@ async def favourite_beatmapset( action: Literal["favourite", "unfavourite"] = Form( description="操作类型:favourite 收藏 / unfavourite 取消收藏" ), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), db: AsyncSession = Depends(get_db), ): assert current_user.id is not None diff --git a/app/router/v2/relationship.py b/app/router/v2/relationship.py index 49b2939..e257a83 100644 --- a/app/router/v2/relationship.py +++ b/app/router/v2/relationship.py @@ -2,7 +2,7 @@ from __future__ import annotations from app.database import Relationship, RelationshipResp, RelationshipType, User from app.dependencies.database import get_db -from app.dependencies.user import get_current_user +from app.dependencies.user import get_client_user, get_current_user from .router import router @@ -69,7 +69,7 @@ class AddFriendResp(BaseModel): async def add_relationship( request: Request, target: int = Query(description="目标用户 ID"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), db: AsyncSession = Depends(get_db), ): assert current_user.id is not None @@ -143,7 +143,7 @@ async def add_relationship( async def delete_relationship( request: Request, target: int = Path(..., description="目标用户 ID"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), db: AsyncSession = Depends(get_db), ): relationship_type = ( diff --git a/app/router/v2/room.py b/app/router/v2/room.py index 8cc1e8a..ff62b4c 100644 --- a/app/router/v2/room.py +++ b/app/router/v2/room.py @@ -13,7 +13,7 @@ from app.database.room import APIUploadedRoom, Room, RoomResp from app.database.room_participated_user import RoomParticipatedUser from app.database.score import Score from app.dependencies.database import get_db, get_redis -from app.dependencies.user import get_current_user +from app.dependencies.user import get_client_user, get_current_user from app.models.room import RoomCategory, RoomStatus from app.service.room import create_playlist_room_from_api from app.signalr.hub import MultiplayerHubs @@ -149,7 +149,7 @@ async def _participate_room( async def create_room( room: APIUploadedRoom, db: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): assert current_user.id is not None user_id = current_user.id @@ -177,7 +177,7 @@ async def get_room( ), ), db: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), redis: Redis = Depends(get_redis), ): db_room = (await db.exec(select(Room).where(Room.id == room_id))).first() @@ -198,7 +198,7 @@ async def get_room( async def delete_room( room_id: int = Path(..., description="房间 ID"), db: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): db_room = (await db.exec(select(Room).where(Room.id == room_id))).first() if db_room is None: @@ -219,7 +219,7 @@ async def add_user_to_room( room_id: int = Path(..., description="房间 ID"), user_id: int = Path(..., description="用户 ID"), db: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): db_room = (await db.exec(select(Room).where(Room.id == room_id))).first() if db_room is not None: @@ -242,7 +242,7 @@ async def remove_user_from_room( room_id: int = Path(..., description="房间 ID"), user_id: int = Path(..., description="用户 ID"), db: AsyncSession = Depends(get_db), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), ): db_room = (await db.exec(select(Room).where(Room.id == room_id))).first() if db_room is not None: diff --git a/app/router/v2/score.py b/app/router/v2/score.py index 23a7d69..0065d09 100644 --- a/app/router/v2/score.py +++ b/app/router/v2/score.py @@ -33,7 +33,7 @@ from app.database.score import ( from app.dependencies.database import get_db, get_redis from app.dependencies.fetcher import get_fetcher from app.dependencies.storage import get_storage_service -from app.dependencies.user import get_current_user +from app.dependencies.user import get_client_user, get_current_user from app.fetcher import Fetcher from app.models.room import RoomCategory from app.models.score import ( @@ -263,7 +263,7 @@ async def create_solo_score( version_hash: str = Form("", description="游戏版本哈希"), beatmap_hash: str = Form(description="谱面文件哈希"), ruleset_id: int = Form(..., ge=0, le=3, description="ruleset 数字 ID (0-3)"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), db: AsyncSession = Depends(get_db), ): assert current_user.id is not None @@ -290,7 +290,7 @@ async def submit_solo_score( beatmap_id: int = Path(description="谱面 ID"), token: int = Path(description="成绩令牌 ID"), info: SoloScoreSubmissionInfo = Body(description="成绩提交信息"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), db: AsyncSession = Depends(get_db), redis: Redis = Depends(get_redis), fetcher=Depends(get_fetcher), @@ -313,7 +313,7 @@ async def create_playlist_score( beatmap_hash: str = Form(description="游戏版本哈希"), ruleset_id: int = Form(..., ge=0, le=3, description="ruleset 数字 ID (0-3)"), version_hash: str = Form("", description="谱面版本哈希"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), session: AsyncSession = Depends(get_db), ): assert current_user.id is not None @@ -386,7 +386,7 @@ async def submit_playlist_score( playlist_id: int, token: int, info: SoloScoreSubmissionInfo, - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), session: AsyncSession = Depends(get_db), redis: Redis = Depends(get_redis), fetcher: Fetcher = Depends(get_fetcher), @@ -509,7 +509,7 @@ async def show_playlist_score( room_id: int, playlist_id: int, score_id: int, - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), session: AsyncSession = Depends(get_db), redis: Redis = Depends(get_redis), ): @@ -580,7 +580,7 @@ async def get_user_playlist_score( room_id: int, playlist_id: int, user_id: int, - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), session: AsyncSession = Depends(get_db), ): score_record = None @@ -616,7 +616,7 @@ async def get_user_playlist_score( ) async def pin_score( score_id: int = Path(description="成绩 ID"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), db: AsyncSession = Depends(get_db), ): score_record = ( @@ -658,7 +658,7 @@ async def pin_score( ) async def unpin_score( score_id: int = Path(description="成绩 ID"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), db: AsyncSession = Depends(get_db), ): score_record = ( @@ -699,7 +699,7 @@ async def reorder_score_pin( score_id: int = Path(description="成绩 ID"), after_score_id: int | None = Body(default=None, description="放在该成绩之后"), before_score_id: int | None = Body(default=None, description="放在该成绩之前"), - current_user: User = Security(get_current_user, scopes=["*"]), + current_user: User = Security(get_client_user), db: AsyncSession = Depends(get_db), ): score_record = (