token 换登录调上游 getTokenInfo 时参数传错:原来发静态头 Authorization: Bearer admin + 令牌放 access_token cookie。上游实际 从 Authorization: Bearer <token> 头读取令牌。 - 鉴权头改为 "Bearer "+前端传入 token,删除 access_token cookie - service_authorization 配置项弃用(保留兼容,不再参与请求) - 新增回归测试 test_token_login_upstream_call.py - 同步更新 AUTH_LOGIN.md / CLAUDE.md / config.yaml 注释 / login_config 文档 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
from pydantic import BaseModel, Field
|
|
|
|
# Fallback shared password for the username-login gate. Used when
|
|
# ``UsernameLoginConfig.password`` is left empty but ``require_password`` is on.
|
|
DEFAULT_USERNAME_LOGIN_PASSWORD = "123ewq"
|
|
|
|
|
|
class UsernameLoginConfig(BaseModel):
|
|
"""Gate for the passwordless ``POST /api/v1/auth/login/username`` flow.
|
|
|
|
By default this flow logs a user in (auto-registering if needed) from a
|
|
username alone — convenient, but it lets anyone sign in as any username.
|
|
Turning ``require_password`` on forces callers to additionally pass a
|
|
shared secret as ``?password=...``; mismatches are rejected with 401.
|
|
"""
|
|
|
|
require_password: bool = Field(
|
|
default=False,
|
|
description="When true, login/username requires a matching ?password= query value.",
|
|
)
|
|
password: str = Field(
|
|
default="",
|
|
description=f"Shared secret for the username-login gate. Empty falls back to '{DEFAULT_USERNAME_LOGIN_PASSWORD}'.",
|
|
)
|
|
|
|
@property
|
|
def effective_password(self) -> str:
|
|
"""The password actually checked at login time (config value or default)."""
|
|
return self.password or DEFAULT_USERNAME_LOGIN_PASSWORD
|
|
|
|
|
|
class TokenLoginConfig(BaseModel):
|
|
"""Token-exchange login.
|
|
|
|
The frontend passes an upstream access token via the URL. The gateway calls
|
|
``token_info_url`` with that token placed directly in the
|
|
``Authorization: Bearer <token>`` header, reads ``data.username`` from the
|
|
response, then signs the matching local user in (auto-registering if the
|
|
username is not yet in the database).
|
|
"""
|
|
|
|
enabled: bool = Field(
|
|
default=False,
|
|
description="Master switch for the POST /api/v1/auth/login/token endpoint.",
|
|
)
|
|
token_info_url: str = Field(
|
|
default="",
|
|
description="Upstream endpoint that resolves an access token to a username.",
|
|
)
|
|
service_authorization: str = Field(
|
|
default="Bearer admin",
|
|
description=(
|
|
"Deprecated/unused: the Authorization header is now built from the "
|
|
"caller's token as 'Bearer <token>'. Retained for backward compat."
|
|
),
|
|
)
|
|
timeout_seconds: float = Field(
|
|
default=10.0,
|
|
gt=0,
|
|
description="HTTP timeout (seconds) for the token_info_url call.",
|
|
)
|
|
verify_ssl: bool = Field(
|
|
default=True,
|
|
description=(
|
|
"Whether to verify the upstream TLS certificate. Set false for "
|
|
"internal/UAT hosts with self-signed or private-CA certs. Leave "
|
|
"true in production."
|
|
),
|
|
)
|
|
ca_cert_path: str = Field(
|
|
default="",
|
|
description=(
|
|
"Optional path to a CA bundle (PEM) used to verify the upstream "
|
|
"certificate. Takes precedence over verify_ssl when set."
|
|
),
|
|
)
|
|
|
|
|
|
class AuthLoginConfig(BaseModel):
|
|
"""Container for the custom login modes layered on top of the base auth."""
|
|
|
|
username_login: UsernameLoginConfig = Field(default_factory=UsernameLoginConfig)
|
|
token_login: TokenLoginConfig = Field(default_factory=TokenLoginConfig)
|