feat(auth): add opaque bearer sessions
This commit is contained in:
@@ -6,10 +6,8 @@ from fastapi import Depends, Header, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.session import get_session_factory
|
||||
|
||||
|
||||
PLATFORM_ADMIN_IDENTITIES = {"admin", "superadmin"}
|
||||
ADMIN_HEADER_TRUE_VALUES = {"1", "true", "yes", "on"}
|
||||
from app.services.auth import AuthService
|
||||
from app.services.auth_sessions import AuthSessionService
|
||||
|
||||
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
@@ -33,82 +31,72 @@ class CurrentUserContext:
|
||||
grade: str = ""
|
||||
employee_no: str = ""
|
||||
manager_name: str = ""
|
||||
employee_id: str = ""
|
||||
auth_session_id: str = ""
|
||||
|
||||
|
||||
def get_current_user(
|
||||
x_auth_username: Annotated[
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
authorization: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录用户名。知识库接口至少需要提供用户名或姓名。"),
|
||||
] = None,
|
||||
x_auth_name: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录人展示姓名。未传时默认回退到用户名。"),
|
||||
] = None,
|
||||
x_auth_role_codes: Annotated[
|
||||
str | None,
|
||||
Header(description="角色编码列表,多个角色使用英文逗号分隔,例如 `manager,finance`。"),
|
||||
] = None,
|
||||
x_auth_is_admin: Annotated[
|
||||
str | None,
|
||||
Header(description="是否管理员,支持 `true/false/1/0`。"),
|
||||
] = None,
|
||||
x_auth_department: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录人的所属部门。"),
|
||||
] = None,
|
||||
x_auth_cost_center: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录人的成本中心。"),
|
||||
] = None,
|
||||
x_auth_position: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录人的岗位。"),
|
||||
] = None,
|
||||
x_auth_grade: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录人的职级。"),
|
||||
] = None,
|
||||
x_auth_employee_no: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录人的员工编号。"),
|
||||
] = None,
|
||||
x_auth_manager_name: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录人的直属领导。"),
|
||||
Header(description="登录接口签发的 `Authorization: Bearer <token>`。"),
|
||||
] = None,
|
||||
) -> CurrentUserContext:
|
||||
role_codes = [
|
||||
_normalize_role_code(item)
|
||||
for item in (x_auth_role_codes or "").split(",")
|
||||
if _normalize_role_code(item)
|
||||
]
|
||||
username = (x_auth_username or "").strip()
|
||||
name = (x_auth_name or username).strip()
|
||||
is_admin = _resolve_platform_admin_flag(
|
||||
username=username,
|
||||
name=name,
|
||||
role_codes=role_codes,
|
||||
header_value=x_auth_is_admin,
|
||||
)
|
||||
return _authenticate_bearer_user(db, authorization)
|
||||
|
||||
if not username and not name:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="请先登录后再访问知识库。",
|
||||
)
|
||||
|
||||
def get_optional_current_user(
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
authorization: Annotated[
|
||||
str | None,
|
||||
Header(description="系统已初始化时用于管理员校验的 Bearer Token。"),
|
||||
] = None,
|
||||
) -> CurrentUserContext | None:
|
||||
if not str(authorization or "").strip():
|
||||
return None
|
||||
return _authenticate_bearer_user(db, authorization)
|
||||
|
||||
|
||||
def _authenticate_bearer_user(db: Session, authorization: str | None) -> CurrentUserContext:
|
||||
access_token = _extract_bearer_token(authorization)
|
||||
auth_session = AuthSessionService(db).authenticate(access_token)
|
||||
if auth_session is None:
|
||||
raise _unauthorized("登录会话不存在、已过期或已退出,请重新登录。")
|
||||
|
||||
user = AuthService(db).get_session_user(auth_session)
|
||||
if user is None:
|
||||
raise _unauthorized("当前登录用户不存在、已停用或权限已失效。")
|
||||
|
||||
return CurrentUserContext(
|
||||
username=username or name,
|
||||
name=name or username,
|
||||
role_codes=role_codes,
|
||||
is_admin=is_admin,
|
||||
tenant_id="default",
|
||||
department_name=(x_auth_department or "").strip(),
|
||||
cost_center=(x_auth_cost_center or "").strip(),
|
||||
position=(x_auth_position or "").strip(),
|
||||
grade=(x_auth_grade or "").strip(),
|
||||
employee_no=(x_auth_employee_no or "").strip(),
|
||||
manager_name=(x_auth_manager_name or "").strip(),
|
||||
username=user.username,
|
||||
name=user.name,
|
||||
role_codes=[_normalize_role_code(item) for item in user.role_codes],
|
||||
is_admin=user.is_admin,
|
||||
tenant_id=user.tenant_id,
|
||||
department_name=user.department,
|
||||
cost_center=user.cost_center,
|
||||
position=user.position,
|
||||
grade=user.grade,
|
||||
employee_no=user.employee_no,
|
||||
manager_name=user.manager_name,
|
||||
employee_id=user.employee_id or "",
|
||||
auth_session_id=auth_session.id,
|
||||
)
|
||||
|
||||
|
||||
def _extract_bearer_token(authorization: str | None) -> str:
|
||||
normalized = str(authorization or "").strip()
|
||||
scheme, separator, token = normalized.partition(" ")
|
||||
if not separator or scheme.casefold() != "bearer" or not token.strip():
|
||||
raise _unauthorized("请先登录后再访问。")
|
||||
return token.strip()
|
||||
|
||||
|
||||
def _unauthorized(detail: str) -> HTTPException:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=detail,
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@@ -120,25 +108,11 @@ def _normalize_role_code(value: str | None) -> str:
|
||||
|
||||
|
||||
def _current_user_role_codes(current_user: CurrentUserContext) -> set[str]:
|
||||
return {_normalize_role_code(item) for item in current_user.role_codes if _normalize_role_code(item)}
|
||||
|
||||
|
||||
def _resolve_platform_admin_flag(
|
||||
*,
|
||||
username: str,
|
||||
name: str,
|
||||
role_codes: list[str],
|
||||
header_value: str | None,
|
||||
) -> bool:
|
||||
if str(header_value or "").strip().lower() in ADMIN_HEADER_TRUE_VALUES:
|
||||
return True
|
||||
|
||||
identities = {
|
||||
str(username or "").strip().lower(),
|
||||
str(name or "").strip().lower(),
|
||||
return {
|
||||
_normalize_role_code(item)
|
||||
for item in current_user.role_codes
|
||||
if _normalize_role_code(item)
|
||||
}
|
||||
normalized_role_codes = {_normalize_role_code(item) for item in role_codes}
|
||||
return bool(identities & PLATFORM_ADMIN_IDENTITIES) or bool(normalized_role_codes & PLATFORM_ADMIN_IDENTITIES)
|
||||
|
||||
|
||||
def require_admin_user(
|
||||
|
||||
Reference in New Issue
Block a user