feat(auth)!: migrate customer metadata auth

Remove local auth and user-management endpoints in favor of Keycloak-backed metadata users, project context, admin metadata APIs, and agent auth context.
This commit is contained in:
2026-06-13 15:00:58 +08:00
parent 4b02118286
commit c7947a7481
28 changed files with 1562 additions and 1155 deletions
+8 -12
View File
@@ -11,17 +11,8 @@ class Settings(BaseSettings):
NETWORK_NAME: str = "default_network"
# JWT 配置
SECRET_KEY: str = (
"your-secret-key-here-change-in-production-use-openssl-rand-hex-32"
)
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
# 数据加密密钥 (使用 Fernet)
ENCRYPTION_KEY: str = "" # 必须从环境变量设置
DATABASE_ENCRYPTION_KEY: str = "" # project_databases.dsn_encrypted 专用
# 敏感配置加密密钥 (Fernet)
DATABASE_ENCRYPTION_KEY: str = ""
# Database Config (PostgreSQL)
DB_NAME: str = "tjwater"
@@ -36,6 +27,11 @@ class Settings(BaseSettings):
TIMESCALEDB_DB_PORT: str = "5433"
TIMESCALEDB_DB_USER: str = "postgres"
TIMESCALEDB_DB_PASSWORD: str = "password"
# InfluxDB
INFLUXDB_URL: str = "http://localhost:8086"
INFLUXDB_TOKEN: str = "token"
INFLUXDB_ORG: str = "org"
INFLUXDB_BUCKET: str = "bucket"
# Metadata Database Config (PostgreSQL)
METADATA_DB_NAME: str = "system_hub"
@@ -54,7 +50,7 @@ class Settings(BaseSettings):
PROJECT_TS_POOL_MIN_SIZE: int = 1
PROJECT_TS_POOL_MAX_SIZE: int = 10
# Keycloak JWT (optional override)
# Keycloak access token verification
KEYCLOAK_PUBLIC_KEY: str = ""
KEYCLOAK_ALGORITHM: str = "RS256"
KEYCLOAK_AUDIENCE: str = ""
+3 -7
View File
@@ -20,10 +20,10 @@ class Encryptor:
key: 加密密钥,如果为 None 则从环境变量读取
"""
if key is None:
key_str = os.getenv("ENCRYPTION_KEY") or settings.ENCRYPTION_KEY
key_str = os.getenv("DATABASE_ENCRYPTION_KEY") or settings.DATABASE_ENCRYPTION_KEY
if not key_str:
raise ValueError(
"ENCRYPTION_KEY not found in environment variables or .env. "
"DATABASE_ENCRYPTION_KEY not found in environment variables or .env. "
"Generate one using: Encryptor.generate_key()"
)
key = key_str.encode()
@@ -80,15 +80,13 @@ _database_encryptor: Optional[Encryptor] = None
def is_encryption_configured() -> bool:
return bool(os.getenv("ENCRYPTION_KEY") or settings.ENCRYPTION_KEY)
return is_database_encryption_configured()
def is_database_encryption_configured() -> bool:
return bool(
os.getenv("DATABASE_ENCRYPTION_KEY")
or settings.DATABASE_ENCRYPTION_KEY
or os.getenv("ENCRYPTION_KEY")
or settings.ENCRYPTION_KEY
)
@@ -107,8 +105,6 @@ def get_database_encryptor() -> Encryptor:
key_str = (
os.getenv("DATABASE_ENCRYPTION_KEY")
or settings.DATABASE_ENCRYPTION_KEY
or os.getenv("ENCRYPTION_KEY")
or settings.ENCRYPTION_KEY
)
if not key_str:
raise ValueError(
-95
View File
@@ -1,95 +0,0 @@
from datetime import datetime, timedelta, timezone
from typing import Optional, Union, Any
from jose import jwt
from passlib.context import CryptContext
from app.core.config import settings
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def _utc_now() -> datetime:
return datetime.now(timezone.utc)
def create_access_token(
subject: Union[str, Any], expires_delta: Optional[timedelta] = None
) -> str:
"""
创建 JWT Access Token
Args:
subject: 用户标识(通常是用户名或用户ID)
expires_delta: 过期时间增量
Returns:
JWT token 字符串
"""
if expires_delta:
expire = _utc_now() + expires_delta
else:
expire = _utc_now() + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
to_encode = {
"exp": expire,
"sub": str(subject),
"type": "access",
"iat": _utc_now(),
}
encoded_jwt = jwt.encode(
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
)
return encoded_jwt
def create_refresh_token(subject: Union[str, Any]) -> str:
"""
创建 JWT Refresh Token(长期有效)
Args:
subject: 用户标识
Returns:
JWT refresh token 字符串
"""
expire = _utc_now() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
to_encode = {
"exp": expire,
"sub": str(subject),
"type": "refresh",
"iat": _utc_now(),
}
encoded_jwt = jwt.encode(
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
)
return encoded_jwt
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""
验证密码
Args:
plain_password: 明文密码
hashed_password: 密码哈希
Returns:
是否匹配
"""
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
"""
生成密码哈希
Args:
password: 明文密码
Returns:
bcrypt 哈希字符串
"""
return pwd_context.hash(password)