Files
ai-contract-platform/backend/app/services/auth_service.py
T
2026-05-03 12:28:38 +08:00

169 lines
6.8 KiB
Python

from typing import Optional
from uuid import uuid4
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from app.core.config import settings
from app.core.security import create_access_token, decode_access_token, get_password_hash, verify_password
from app.models.user import User
from app.repositories.user_repository import UserRepository
from app.services.audit_log_service import AuditLogService
from app.services.tenant_service import DEFAULT_TENANT_ID, TenantService
DEFAULT_ADMIN_USERNAME = "admin@example.com"
DEFAULT_ADMIN_PASSWORD = "123456"
DEFAULT_ADMIN_DISPLAY_NAME = "管理员"
DEFAULT_TENANT_ID = "tenant-demo-001"
class AuthService:
def __init__(self, db: Session):
self.db = db
self.repository = UserRepository(db)
self.audit_log_service = AuditLogService(db)
def register(self, username: str, password: str, display_name: str) -> dict:
normalized_username = username.strip().lower()
normalized_display_name = display_name.strip()
normalized_password = password.strip()
if not normalized_display_name:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="display name is required")
if len(normalized_password) < 6:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="password must be at least 6 characters")
if self.repository.get_by_username(normalized_username) is not None:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="username already exists")
TenantService(self.db).ensure_default_tenant()
user = User(
id=f"user-{uuid4().hex[:12]}",
tenant_id=DEFAULT_TENANT_ID,
username=normalized_username,
display_name=normalized_display_name,
password_hash=get_password_hash(normalized_password),
role="member",
status="active",
)
created = self.repository.create(user)
self.audit_log_service.record_event(
tenant_id=created.tenant_id,
user_id=created.id,
action="register",
resource_type="user",
resource_id=created.id,
detail_json={"username": created.username, "display_name": created.display_name, "role": created.role},
)
return self._to_user_info(created)
def login(self, account: str, password: str) -> dict:
normalized_account = account.strip().lower()
user = self.repository.get_by_username(normalized_account)
if user is None or not verify_password(password, user.password_hash):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="账号或密码错误")
token = create_access_token(user.id)
self.audit_log_service.record_event(
tenant_id=user.tenant_id,
user_id=user.id,
action="login",
resource_type="user",
resource_id=user.id,
detail_json={"username": user.username},
)
return {
"access_token": token,
"token_type": "Bearer",
"expires_in": settings.access_token_expire_minutes * 60,
"user": self._to_user_info(user),
}
def get_current_user(self, authorization: Optional[str]) -> dict:
user = self._require_user_from_authorization(authorization)
return self._to_user_info(user)
def update_current_user(self, authorization: Optional[str], display_name: str, password: Optional[str]) -> dict:
user = self._require_user_from_authorization(authorization)
normalized_display_name = display_name.strip()
if not normalized_display_name:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="display name is required")
user.display_name = normalized_display_name
password_updated = False
if password is not None and password.strip():
normalized_password = password.strip()
if len(normalized_password) < 6:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="password must be at least 6 characters")
user.password_hash = get_password_hash(normalized_password)
password_updated = True
updated = self.repository.update(user)
self.audit_log_service.record_event(
tenant_id=updated.tenant_id,
user_id=updated.id,
action="update_profile",
resource_type="user",
resource_id=updated.id,
detail_json={
"username": updated.username,
"display_name": updated.display_name,
"password_updated": password_updated,
},
)
return self._to_user_info(updated)
def require_current_user(self, authorization: Optional[str]) -> dict:
user = self._require_user_from_authorization(authorization)
return self._to_user_info(user)
def seed_default_admin(self) -> dict:
TenantService(self.db).ensure_default_tenant()
existing = self.repository.get_by_username(DEFAULT_ADMIN_USERNAME)
if existing is not None:
return {
"username": DEFAULT_ADMIN_USERNAME,
"password": None,
"created": False,
}
admin = User(
id="user-demo-001",
tenant_id=DEFAULT_TENANT_ID,
username=DEFAULT_ADMIN_USERNAME,
display_name=DEFAULT_ADMIN_DISPLAY_NAME,
password_hash=get_password_hash(DEFAULT_ADMIN_PASSWORD),
role="admin",
status="active",
)
self.repository.create(admin)
return {
"username": DEFAULT_ADMIN_USERNAME,
"password": DEFAULT_ADMIN_PASSWORD,
"created": True,
}
def _require_user_from_authorization(self, authorization: Optional[str]) -> User:
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="missing bearer token")
token = authorization.split(" ", 1)[1].strip()
subject = decode_access_token(token)
if subject is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid token")
user = self.repository.get_by_id(subject)
if user is None or user.status != "active":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found")
return user
def _to_user_info(self, user: User) -> dict:
return {
"id": user.id,
"tenant_id": user.tenant_id,
"username": user.username,
"display_name": user.display_name,
"role": user.role,
"status": user.status,
}