From f3a5ae9c40355a35c25052669e3a438290b688bf Mon Sep 17 00:00:00 2001 From: Yuerchu Date: Thu, 28 Aug 2025 02:23:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/migration.py | 3 +- models/response.py | 39 ++++++++++++++++++++++-- routers/controllers/callback.py | 42 ------------------------- routers/controllers/user.py | 54 +++++++++++---------------------- service/user/login.py | 12 ++++---- 5 files changed, 63 insertions(+), 87 deletions(-) diff --git a/models/migration.py b/models/migration.py index 2a747fc..64632fe 100644 --- a/models/migration.py +++ b/models/migration.py @@ -1,5 +1,6 @@ from .setting import Setting from pkg.conf.appmeta import BackendVersion +from .response import ThemeModel from pkg.password.pwd import Password from pkg.log import log @@ -71,7 +72,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti Setting(name="hot_share_num", value="10", type="share"), Setting(name="gravatar_server", value="https://www.gravatar.com/", type="avatar"), Setting(name="defaultTheme", value="#3f51b5", type="basic"), - Setting(name="themes", value={"#3f51b5":{"palette":{"primary":{"main":"#3f51b5"},"secondary":{"main":"#f50057"}}},"#2196f3":{"palette":{"primary":{"main":"#2196f3"},"secondary":{"main":"#FFC107"}}},"#673AB7":{"palette":{"primary":{"main":"#673AB7"},"secondary":{"main":"#2196F3"}}},"#E91E63":{"palette":{"primary":{"main":"#E91E63"},"secondary":{"main":"#42A5F5","contrastText":"#fff"}}},"#FF5722":{"palette":{"primary":{"main":"#FF5722"},"secondary":{"main":"#3F51B5"}}},"#FFC107":{"palette":{"primary":{"main":"#FFC107"},"secondary":{"main":"#26C6DA"}}},"#8BC34A":{"palette":{"primary":{"main":"#8BC34A","contrastText":"#fff"},"secondary":{"main":"#FF8A65","contrastText":"#fff"}}},"#009688":{"palette":{"primary":{"main":"#009688"},"secondary":{"main":"#4DD0E1","contrastText":"#fff"}}},"#607D8B":{"palette":{"primary":{"main":"#607D8B"},"secondary":{"main":"#F06292"}}},"#795548":{"palette":{"primary":{"main":"#795548"},"secondary":{"main":"#4CAF50","contrastText":"#fff"}}}}, type="basic"), + Setting(name="themes", value=ThemeModel().model_dump(), type="basic"), Setting(name="aria2_token", value="", type="aria2"), Setting(name="aria2_rpcurl", value="", type="aria2"), Setting(name="aria2_temp_path", value="", type="aria2"), diff --git a/models/response.py b/models/response.py index 4f021fb..3467edc 100644 --- a/models/response.py +++ b/models/response.py @@ -4,18 +4,41 @@ from datetime import datetime, timezone from uuid import uuid4 class ResponseModel(BaseModel): + ''' + 默认响应模型 + ''' code: int = Field(default=0, description="系统内部状态码, 0表示成功,其他表示失败", lt=60000, gt=0) data: Union[dict, list, str, int, float, None] = Field(None, description="响应数据") msg: Optional[str] = Field(default=None, description="响应消息,可以是错误消息或信息提示") instance_id: str = Field(default_factory=lambda: str(uuid4()), description="实例ID,用于标识请求的唯一性") + +class ThemeModel(BaseModel): + ''' + 主题模型 + ''' + primary: str = Field(default="#3f51b5", description="Primary color") + secondary: str = Field(default="#f50057", description="Secondary color") + accent: str = Field(default="#9c27b0", description="Accent color") + dark: str = Field(default="#1d1d1d", description="Dark color") + dark_page: str = Field(default="#121212", description="Dark page color") + positive: str = Field(default="#21ba45", description="Positive color") + negative: str = Field(default="#c10015", description="Negative color") + info: str = Field(default="#31ccec", description="Info color") + warning: str = Field(default="#f2c037", description="Warning color") class TokenModel(BaseModel): + ''' + 访问令牌模型 + ''' access_expires: datetime = Field(default=None, description="访问令牌的过期时间") access_token: str = Field(default=None, description="访问令牌") refresh_expires: datetime = Field(default=None, description="刷新令牌的过期时间") refresh_token: str = Field(default=None, description="刷新令牌") class groupModel(BaseModel): + ''' + 用户组模型 + ''' id: int = Field(default=None, description="用户组ID") name: str = Field(default=None, description="用户组名称") allowShare: bool = Field(default=False, description="是否允许分享") @@ -32,19 +55,25 @@ class groupModel(BaseModel): advanceDelete: bool = Field(default=False, description="是否允许高级删除") class userModel(BaseModel): + ''' + 用户模型 + ''' id: int = Field(default=None, description="用户ID") username: str = Field(default=None, description="用户名") nickname: str = Field(default=None, description="用户昵称") status: int = Field(default=0, description="用户状态") avatar: Literal['default', 'gravatar', 'file'] = Field(default='default', description="头像类型") created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), description="用户创建时间") - preferred_theme: str = Field(default="#607D8B", description="用户首选主题") + preferred_theme: ThemeModel = Field(default_factory=ThemeModel, description="用户首选主题") score: int = Field(default=0, description="用户积分") anonymous: bool = Field(default=False, description="是否为匿名用户") group: groupModel = Field(default_factory=None, description="用户所属用户组") tags: list = Field(default_factory=list, description="用户标签列表") class SiteConfigModel(ResponseModel): + ''' + 站点配置模型 + ''' title: str = Field(default="DiskNext", description="网站标题") themes: dict = Field(default_factory=dict, description="网站主题配置") default_theme: str = Field(default="default", description="默认主题RGB色号") @@ -56,13 +85,19 @@ class SiteConfigModel(ResponseModel): captcha_key: Optional[str] = Field(default=None, description="验证码密钥") class AuthnModel(BaseModel): + ''' + WebAuthn模型 + ''' id: str = Field(default=None, description="ID") fingerprint: str = Field(default=None, description="指纹") class UserSettingModel(BaseModel): + ''' + 用户设置模型 + ''' authn: Optional[AuthnModel] = Field(default=None, description="认证信息") group_expires: Optional[datetime] = Field(default=None, description="用户组过期时间") - prefer_theme: str = Field(default="#607D8B", description="用户首选主题") + prefer_theme: str = Field(default="#5898d4", description="用户首选主题") qq: str | bool = Field(default=False, description="QQ号") themes: dict = Field(default_factory=dict, description="用户主题配置") two_factor: bool = Field(default=False, description="是否启用两步验证") diff --git a/routers/controllers/callback.py b/routers/controllers/callback.py index 8c04520..32798fa 100644 --- a/routers/controllers/callback.py +++ b/routers/controllers/callback.py @@ -51,48 +51,6 @@ async def router_callback_github( code: str = Query(description="The token received from GitHub for authentication.")) -> PlainTextResponse: """ GitHub OAuth 回调处理 - - - Github 成功响应: - - JWT: {"access_token": "gho_xxxxxxxx", "token_type": "bearer", "scope": ""} - - User Info:{ - "code": "grfessg1312432313421fdgs", - "user_data": { - "login": "Yuerchu", - "id": 114514, - "node_id": "xxxxx", - "avatar_url": "https://avatars.githubusercontent.com/u/114514?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Yuerchu", - "html_url": "https://github.com/Yuerchu", - "followers_url": "https://api.github.com/users/Yuerchu/followers", - "following_url": "https://api.github.com/users/Yuerchu/following{/other_user}", - "gists_url": "https://api.github.com/users/Yuerchu/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Yuerchu/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Yuerchu/subscriptions", - "organizations_url": "https://api.github.com/users/Yuerchu/orgs", - "repos_url": "https://api.github.com/users/Yuerchu/repos", - "events_url": "https://api.github.com/users/Yuerchu/events{/privacy}", - "received_events_url": "https://api.github.com/users/Yuerchu/received_events", - "type": "User", - "user_view_type": "public", - "site_admin": false, - "name": "于小丘", - "company": null, - "blog": "https://www.yxqi.cn", - "location": "ChangSha, HuNan, China", - "email": "admin@yuxiaoqiu.cn", - "hireable": null, - "bio": null, - "twitter_username": null, - "notification_email": "admin@yuxiaoqiu.cn", - "public_repos": 17, - "public_gists": 0, - "followers": 8, - "following": 8, - "created_at": "2019-04-13T11:17:33Z", - "updated_at": "2025-08-20T03:03:16Z" - } - } - 错误响应示例: - { 'error': 'bad_verification_code', diff --git a/routers/controllers/user.py b/routers/controllers/user.py index cb99a34..fb7b998 100644 --- a/routers/controllers/user.py +++ b/routers/controllers/user.py @@ -30,18 +30,21 @@ async def router_user_session( username = form_data.username password = form_data.password - user = await service.user.Login(username=username, password=password) + is_login, detail = await service.user.Login(username=username, password=password) - if user is None: - raise HTTPException(status_code=400, detail="Invalid username or password") - elif user == 1: - raise HTTPException(status_code=400, detail="User account is not fully registered") - elif user == 2: - raise HTTPException(status_code=403, detail="User account is banned") - elif isinstance(user, TokenModel): - return user + if not is_login: + if detail in ["User not found", "Incorrect password"]: + raise HTTPException(status_code=400, detail="Invalid username or password") + elif detail == "Need to complete registration": + raise HTTPException(status_code=400, detail="User account is not fully registered") + elif detail == "Account is banned": + raise HTTPException(status_code=403, detail="User account is banned") + else: + raise HTTPException(status_code=500, detail="Internal server error during login") + if isinstance(detail, TokenModel): + return detail else: - log.error(f"Unexpected return type from login service: {type(user)}") + log.error(f"Unexpected return type from login service: {type(detail)}") raise HTTPException(status_code=500, detail="Internal server error during login") @user_router.post( @@ -73,13 +76,13 @@ def router_user_2fa() -> ResponseModel: pass @user_router.post( - path='/reset', - summary='发送密码重设邮件', - description='Send a password reset email.', + path='/code', + summary='发送验证码邮件', + description='Send a verification code email.', ) -def router_user_reset() -> ResponseModel: +def router_user_email_code() -> ResponseModel: """ - Send a password reset email. + Send a pas Returns: dict: A dictionary containing information about the password reset email. @@ -118,27 +121,6 @@ def router_user_qq() -> ResponseModel: """ pass -@deprecated( - version="0.0.1", - reason="邮件中带链接的激活易使得被收件服务器误判为垃圾邮件,新版更换为验证码方式" -) -@user_router.get( - path='/activate/{id}', - summary='邮件激活', - description='Activate user account via email link.', -) -def router_user_activate(id: str) -> ResponseModel: - """ - Activate user account via email link. - - Args: - id (str): The activation ID from the email link. - - Returns: - dict: A dictionary containing activation information. - """ - pass - @user_router.get( path='authn/{username}', summary='WebAuthn登录初始化', diff --git a/service/user/login.py b/service/user/login.py index ee19b16..6462397 100644 --- a/service/user/login.py +++ b/service/user/login.py @@ -9,7 +9,7 @@ async def Login( password: str, captcha: Optional[str] = None, twoFaCode: Optional[str] = None -) -> TokenModel | int | None: +) -> tuple[bool, TokenModel | str]: """ 根据账号密码进行登录。 @@ -41,20 +41,20 @@ async def Login( if not user: log.debug(f"Cannot find user with email: {username}") - return None + return False, "User not found" # 验证密码是否正确 if not Password.verify(user.password, password): log.debug(f"Password verification failed for user: {username}") - return None + return False, "Incorrect password" # 验证用户是否可登录 if user.status == 1: # 未完成注册 - return 1 + return False, "Need to complete registration" elif user.status == 2: # 账号已被封禁 - return 2 + return False, "Account is banned" # 创建令牌 from pkg.JWT.JWT import create_access_token, create_refresh_token @@ -62,7 +62,7 @@ async def Login( access_token, access_expire = create_access_token(data={'sub': user.email}) refresh_token, refresh_expire = create_refresh_token(data={'sub': user.email}) - return TokenModel( + return True, TokenModel( access_token=access_token, access_expires=access_expire, refresh_token=refresh_token,