From 627ad4f20bb12c1e195a7eea0bba740f35a42483 Mon Sep 17 00:00:00 2001 From: huangzhuang_3rd Date: Wed, 12 Aug 2026 13:30:39 +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/core/security.py | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 backend/app/core/security.py diff --git a/backend/app/core/security.py b/backend/app/core/security.py new file mode 100644 index 0000000..61a6ca1 --- /dev/null +++ b/backend/app/core/security.py @@ -0,0 +1,53 @@ +"""Security utilities: JWT tokens, password hashing. + +Uses bcrypt directly (passlib has compatibility issues with bcrypt>=4.1). +""" +from datetime import datetime, timedelta, timezone +from typing import Optional +from jose import jwt, JWTError +import bcrypt +from app.config import settings + + +def hash_password(password: str) -> str: + """Hash a plaintext password using bcrypt.""" + # bcrypt has a 72-byte limit, so we truncate + pwd_bytes = password.encode("utf-8")[:72] + salt = bcrypt.gensalt() + return bcrypt.hashpw(pwd_bytes, salt).decode("utf-8") + + +def verify_password(plain: str, hashed: str) -> bool: + """Verify a plaintext password against its bcrypt hash.""" + try: + pwd_bytes = plain.encode("utf-8")[:72] + hash_bytes = hashed.encode("utf-8") + return bcrypt.checkpw(pwd_bytes, hash_bytes) + except Exception: + return False + + +def create_access_token( + subject: str, + tenant_id: int, + role: str, + extra: Optional[dict] = None, +) -> str: + """Create a JWT access token.""" + now = datetime.now(timezone.utc) + expire = now + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + payload = { + "sub": subject, + "tid": tenant_id, + "role": role, + "iat": now, + "exp": expire, + } + if extra: + payload.update(extra) + return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256") + + +def decode_access_token(token: str) -> dict: + """Decode and validate a JWT token. Raises JWTError on failure.""" + return jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])