Files
zhanghongyu_0626 3995cb44d8 Implement authentication and chat functionality with JWT support
- Added `auth.py` for mock login and JWT issuance.
- Introduced `chat.py` for handling chat requests with role-based access control.
- Enhanced `main.py` to include new routers and middleware for tracing.
- Implemented input validation in `input_guard.py` to prevent SQL injection.
- Created repositories for managing agent sessions and audit logs.
- Added exception handling for authorization errors.
- Updated settings to include JWT configuration.
- Introduced tests for authentication and input validation.
2026-09-07 17:20:42 +08:00

144 lines
4.1 KiB
Python

"""JWT 签发与校验(开发环境 HS256)。"""
from __future__ import annotations
import uuid
from datetime import UTC, datetime, timedelta
from typing import Any
from jose import JWTError, jwt
from app.config.settings import settings
from app.model.schemas import TokenType
from app.utils.exceptions import UnauthorizedError
ISSUER = "https://idp.jinrong.dev"
AUDIENCE = "agent-gateway"
ROLE_PERMISSIONS: dict[str, list[str]] = {
"customer": [
"agent:customer:chat",
"profile:l1:read",
"profile:l1:write",
"core:holding:read:self",
],
"advisor": [
"agent:advisor:chat",
"profile:l1:read",
"profile:l2:read",
"profile:l2:write",
"profile:l3:read",
"core:holding:read:assigned",
],
"analyst": [
"agent:analyst:chat",
"profile:l1:read",
"profile:l2:read",
"profile:l3:read",
"core:holding:read:scoped",
"sql:execute:readonly",
"risk:alert:read",
],
"risk_officer": [
"agent:risk:chat",
"profile:l1:read",
"profile:l2:read",
"profile:l3:read",
"profile:l3:write",
"risk:alert:write",
"risk:suitability:write",
"core:holding:read:all",
],
"compliance": ["audit:read:all", "agent:advisor:audit", "compliance:hit:read"],
"ops": ["agent:advisor:stats", "audit:read:aggregated"],
"service_risk": [
"agent:risk:suitability_check",
"risk:suitability:write",
"profile:l1:read",
"profile:l2:read",
"audit:write",
],
}
DEFAULT_ROLES_BY_ACTOR: dict[str, list[str]] = {
"STAFF-10086": ["advisor"],
"STAFF-10087": ["advisor"],
"STAFF-10088": ["advisor"],
"STAFF-10089": ["advisor"],
"STAFF-10090": ["advisor"],
"STAFF-10091": ["advisor", "compliance"],
"STAFF-20001": ["analyst"],
"STAFF-20002": ["analyst"],
"STAFF-30001": ["risk_officer"],
"STAFF-30002": ["risk_officer"],
"STAFF-40001": ["compliance"],
"STAFF-40002": ["compliance"],
"STAFF-50001": ["ops"],
"CUST-9527": ["customer"],
"CUST-1001": ["customer"],
"CUST-1002": ["customer"],
"CUST-1010": ["customer"],
}
def infer_roles(actor_id: str, token_type: TokenType, roles: list[str] | None) -> list[str]:
if roles:
return roles
if token_type == "customer":
return ["customer"]
return DEFAULT_ROLES_BY_ACTOR.get(actor_id, ["analyst"])
def permissions_for_roles(roles: list[str]) -> list[str]:
perms: list[str] = []
seen: set[str] = set()
for role in roles:
for perm in ROLE_PERMISSIONS.get(role, []):
if perm not in seen:
seen.add(perm)
perms.append(perm)
return perms
def issue_token(
actor_id: str,
token_type: TokenType,
roles: list[str] | None = None,
) -> tuple[str, int]:
role_list = infer_roles(actor_id, token_type, roles)
now = datetime.now(UTC)
expires = now + timedelta(hours=settings.jwt_dev_expire_hours)
payload: dict[str, Any] = {
"iss": ISSUER,
"sub": actor_id,
"aud": AUDIENCE,
"exp": int(expires.timestamp()),
"iat": int(now.timestamp()),
"jti": str(uuid.uuid4()),
"token_type": token_type,
"roles": role_list,
"permissions": permissions_for_roles(role_list),
"tenant_id": "default",
}
if token_type == "customer":
payload["customer_id"] = actor_id
if "advisor" in role_list and token_type == "staff":
payload["advisor_id"] = actor_id
token = jwt.encode(payload, settings.jwt_dev_secret, algorithm=settings.jwt_dev_algorithm)
return token, settings.jwt_dev_expire_hours * 3600
def decode_token(token: str) -> dict[str, Any]:
try:
payload = jwt.decode(
token,
settings.jwt_dev_secret,
algorithms=[settings.jwt_dev_algorithm],
audience=AUDIENCE,
issuer=ISSUER,
)
except JWTError as exc:
raise UnauthorizedError("Token 无效或已过期", error_code="AUTH_401_INVALID") from exc
return payload