Files
beige/backend/app/services/auth_service.py
yangqianqian df1856d793 上线版: 产品表单统一+form嵌套修复+用户管理+部署+三套叙事
- 产品编辑入口统一走 ProductFormFull(卖点/风格/人群/品牌词全字段);
  修复开任务页 <form> 套 <form> 致"编辑产品"报错、改不了、跳回首个产品
- dashboard 入口卡片对齐实际路由: 系统管理(/config) 与 工作配置(/settings) 分开;
  settings ?tab=products 直达改用挂载后读 URL, 消除 hydration mismatch
- 新增用户管理(users API/admin service/改密页) + alembic 022/023/024
- 上线部署: Dockerfile / docker-compose.prod+https / nginx https / .env.example
- A8 三套正交叙事(痛点/场景/成分背书) + beige 调色去AI化 + 飞轮 text_import 高权重信号

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:08:13 +08:00

92 lines
3.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
app/services/auth_service.py — 认证 service
密码哈希校验、用户查找、响应格式化。
路由层不含业务逻辑,全在此。
"""
import logging
from passlib.context import CryptContext
from sqlalchemy.orm import Session
from app.core.config import get_settings
from app.core.response import raise_unauthorized
from app.middleware.workspace_guard import CurrentUser
from app.models.user import User
from app.models.workspace import WorkspaceMember
logger = logging.getLogger(__name__)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
DEFAULT_SEED_PASSWORD = "Clover2026!"
def hash_password(plain: str) -> str:
return pwd_context.hash(plain)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def authenticate_user(
db: Session, username: str, password: str
) -> tuple[User, int, str]:
"""
验证用户名+密码,返回 (user, workspace_id, role)。
失败抛 CloverHTTPException 40101。
"""
user = db.query(User).filter(
User.username == username, User.is_active == True
).first()
if not user or not verify_password(password, user.hashed_password):
raise_unauthorized("用户名或密码错误")
if get_settings().APP_ENV == "production" and password == DEFAULT_SEED_PASSWORD:
raise_unauthorized("默认密码已禁用,请联系管理员重置密码")
# 取用户所在的第一个 workspace手动建账号场景只有一个
member = (
db.query(WorkspaceMember)
.filter(WorkspaceMember.user_id == user.id)
.first()
)
if not member:
raise_unauthorized("用户未加入任何 workspace请联系管理员")
# 记录登录
try:
from app.models.user import LoginRecord
db.add(LoginRecord(user_id=user.id))
db.commit()
except Exception:
logger.warning("Failed to write login_record for user=%s", user.id)
db.rollback()
return user, member.workspace_id, member.role
def build_user_response(user: User, workspace_id: int, role: str) -> dict:
"""格式化用户响应体契约§4 DTO"""
return {
"id": user.id,
"username": user.username,
"email": user.email,
"current_workspace_id": workspace_id,
"role": role,
"must_change_password": bool(getattr(user, "must_change_password", False)),
}
def change_password(db: Session, user_id: int, current_password: str, new_password: str) -> None:
user = db.query(User).filter(User.id == user_id, User.is_active == True).first()
if not user or not verify_password(current_password, user.hashed_password):
raise_unauthorized("当前密码错误")
if len(new_password) < 8:
from app.core.response import raise_param_error
raise_param_error("新密码至少 8 位")
if new_password == DEFAULT_SEED_PASSWORD:
from app.core.response import raise_param_error
raise_param_error("不能使用默认密码")
user.hashed_password = hash_password(new_password)
user.must_change_password = False
db.commit()