feat(auth): support change password
This commit is contained in:
14
app/auth.py
14
app/auth.py
@@ -60,6 +60,20 @@ def validate_username(username: str) -> list[str]:
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def validate_password(password: str) -> list[str]:
|
||||||
|
"""验证密码"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
if not password:
|
||||||
|
errors.append("Password is required")
|
||||||
|
return errors
|
||||||
|
|
||||||
|
if len(password) < 8:
|
||||||
|
errors.append("Password must be at least 8 characters long")
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
def verify_password_legacy(plain_password: str, bcrypt_hash: str) -> bool:
|
def verify_password_legacy(plain_password: str, bcrypt_hash: str) -> bool:
|
||||||
"""
|
"""
|
||||||
验证密码 - 使用 osu! 的验证方式
|
验证密码 - 使用 osu! 的验证方式
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from app.auth import (
|
|||||||
get_token_by_refresh_token,
|
get_token_by_refresh_token,
|
||||||
get_user_by_authorization_code,
|
get_user_by_authorization_code,
|
||||||
store_token,
|
store_token,
|
||||||
|
validate_password,
|
||||||
validate_username,
|
validate_username,
|
||||||
)
|
)
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
@@ -68,20 +69,6 @@ def validate_email(email: str) -> list[str]:
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
def validate_password(password: str) -> list[str]:
|
|
||||||
"""验证密码"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
if not password:
|
|
||||||
errors.append("Password is required")
|
|
||||||
return errors
|
|
||||||
|
|
||||||
if len(password) < 8:
|
|
||||||
errors.append("Password must be at least 8 characters long")
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(tags=["osu! OAuth 认证"])
|
router = APIRouter(tags=["osu! OAuth 认证"])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
53
app/router/private/password.py
Normal file
53
app/router/private/password.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from app.auth import (
|
||||||
|
authenticate_user,
|
||||||
|
get_password_hash,
|
||||||
|
validate_password,
|
||||||
|
)
|
||||||
|
from app.database.auth import OAuthToken
|
||||||
|
from app.database.verification import LoginSession, TrustedDevice
|
||||||
|
from app.dependencies.database import Database
|
||||||
|
from app.dependencies.user import ClientUser
|
||||||
|
from app.log import log
|
||||||
|
|
||||||
|
from .router import router
|
||||||
|
|
||||||
|
from fastapi import Depends, Form, HTTPException
|
||||||
|
from fastapi_limiter.depends import RateLimiter
|
||||||
|
from sqlmodel import col, delete
|
||||||
|
|
||||||
|
logger = log("Auth")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/password/change",
|
||||||
|
name="更改密码",
|
||||||
|
tags=["验证", "g0v0 API"],
|
||||||
|
status_code=204,
|
||||||
|
dependencies=[Depends(RateLimiter(times=3, minutes=5))],
|
||||||
|
)
|
||||||
|
async def change_password(
|
||||||
|
current_user: ClientUser,
|
||||||
|
session: Database,
|
||||||
|
current_password: Annotated[str, Form(..., description="当前密码")],
|
||||||
|
new_password: Annotated[str, Form(..., description="新密码")],
|
||||||
|
):
|
||||||
|
"""更改用户密码
|
||||||
|
|
||||||
|
同时删除所有的已登录会话和信任设备
|
||||||
|
|
||||||
|
速率限制: 5 分钟内最多 3 次
|
||||||
|
"""
|
||||||
|
if not await authenticate_user(session, current_user.username, current_password):
|
||||||
|
raise HTTPException(status_code=403, detail="Password incorrect")
|
||||||
|
if errors := validate_password(new_password):
|
||||||
|
raise HTTPException(status_code=400, detail="; ".join(errors))
|
||||||
|
|
||||||
|
async with session.begin():
|
||||||
|
current_user.pw_bcrypt = get_password_hash(new_password)
|
||||||
|
|
||||||
|
await session.execute(delete(TrustedDevice).where(col(TrustedDevice.user_id) == current_user.id))
|
||||||
|
await session.execute(delete(LoginSession).where(col(LoginSession.user_id) == current_user.id))
|
||||||
|
await session.execute(delete(OAuthToken).where(col(OAuthToken.user_id) == current_user.id))
|
||||||
|
logger.info(f"User {current_user.id} changed password and sessions revoked")
|
||||||
Reference in New Issue
Block a user