From 44748b11cab4e3801ccdab4299c795c610009649 Mon Sep 17 00:00:00 2001 From: huangzhuang_3rd Date: Wed, 12 Aug 2026 13:30:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=20Worker=20=E5=B9=B3=E5=8F=B0=20MVP?= =?UTF-8?q?=20v1.0.0=20-=20=E5=A4=9A=E7=A7=9F=E6=88=B7/=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86/AI=20Worker/=E4=BB=BB=E5=8A=A1=E7=BC=96?= =?UTF-8?q?=E6=8E=92/HITL=E5=AE=A1=E6=A0=B8/=E6=88=90=E6=9C=AC=E6=B2=BB?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/auth_service.py | 86 ++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 backend/app/services/auth_service.py diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py new file mode 100644 index 0000000..8c6baf1 --- /dev/null +++ b/backend/app/services/auth_service.py @@ -0,0 +1,86 @@ +"""Authentication service: login, register, token management.""" +from sqlalchemy.orm import Session +from app.models.user import User +from app.models.tenant import Tenant +from app.core.security import hash_password, verify_password, create_access_token +from app.core.exceptions import AppException, NotFoundError, PermissionDeniedError +from app.schemas.auth import LoginRequest, RegisterRequest +from app.schemas.common import TokenResponse, UserBrief + + +class AuthService: + @staticmethod + def login(db: Session, req: LoginRequest) -> TokenResponse: + """Authenticate user and return JWT token.""" + user = db.query(User).filter(User.email == req.email).first() + if not user or not user.hashed_password: + raise AppException("Invalid email or password", 401) + if not user.is_active: + raise AppException("Account is disabled", 403) + if not verify_password(req.password, user.hashed_password): + raise AppException("Invalid email or password", 401) + + token = create_access_token( + subject=str(user.id), + tenant_id=user.tenant_id, + role=user.role, + ) + return TokenResponse( + access_token=token, + user=UserBrief.model_validate(user), + ) + + @staticmethod + def register(db: Session, req: RegisterRequest) -> TokenResponse: + """Register a new user. Creates tenant if tenant_name is provided.""" + # Check if email already exists + existing = db.query(User).filter(User.email == req.email).first() + if existing: + raise AppException("Email already registered", 409) + + # Find or create tenant + tenant = db.query(Tenant).filter(Tenant.slug == req.tenant_slug).first() + if not tenant: + if not req.tenant_name: + raise AppException( + f"Tenant '{req.tenant_slug}' not found. Provide tenant_name to create one.", + 404, + ) + tenant = Tenant(name=req.tenant_name, slug=req.tenant_slug) + db.add(tenant) + db.flush() + tenant_id = tenant.id + # First user in a new tenant becomes tenant_admin + role = "tenant_admin" + else: + tenant_id = tenant.id + role = req.role + + user = User( + email=req.email, + username=req.username, + hashed_password=hash_password(req.password), + role=role, + user_type="human", + tenant_id=tenant_id, + ) + db.add(user) + db.commit() + db.refresh(user) + + token = create_access_token( + subject=str(user.id), + tenant_id=user.tenant_id, + role=user.role, + ) + return TokenResponse( + access_token=token, + user=UserBrief.model_validate(user), + ) + + @staticmethod + def get_current_user(db: Session, user_id: int) -> User: + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise NotFoundError("User", user_id) + return user