turbo-agent-auth
# 回滚最后一次迁移
uv run python -m prisma migrate reset
```
旧版信息已归档
技术栈
- FastAPI + Uvicorn: 提供 HTTP API
- Typer: 命令行入口
- Prisma (Python 包名
prisma): 访问 PostgreSQL(尚未定义具体模型) - uv: 依赖与环境管理
- Pytest + httpx(testclient): 基础测试
安装依赖
使用 uv 管理,不要手动修改 pyproject.toml dependencies。
uv sync
运行 API (Typer CLI)
支持以下方式:
# 方式1:直接运行 main.py(初始化阶段用)
uv run python main.py run-api --host 0.0.0.0 --port 8000
# 方式2:模块方式(推荐,使用 __main__)
uv run python -m turbo_agent_auth run-api --host 0.0.0.0 --port 8000
# 方式3:控制台脚本(已配置 project.scripts)
# 推荐:分组命令
uv run turbo-agent-auth serve run --host 0.0.0.0 --port 8000
# 兼容:旧版根级子命令(仍可用)
uv run turbo-agent-auth run-api --host 0.0.0.0 --port 8000
访问健康检查:
curl http://127.0.0.1:8000/health
curl http://127.0.0.1:8000/v1/ping
安全:API Key (Authorization Bearer)
通过环境变量或 CLI 启动参数配置一个全局 API Key;配置后所有 /v1/* 路由需要在请求头携带 Authorization: Bearer <key>,未配置时不启用鉴权(/health 永远开放)。
- 环境变量:
AUTH_API_KEY=yourkey;或 CLI--api-key yourkey。 - 请求头:
Authorization: Bearer yourkey - 失败返回:
401 {"detail":"Invalid API key"}
示例:
AUTH_API_KEY=abc123 uv run turbo-agent-auth serve run --host 0.0.0.0 --port 8000
# 或 CLI 参数方式(内部会写入环境变量)
uv run turbo-agent-auth serve run --api-key abc123 --host 0.0.0.0 --port 8000
curl -H 'Authorization: Bearer abc123' http://127.0.0.1:8000/v1/ping
日志
- 依赖:
loguru - 环境变量:
LOG_LEVEL(默认INFO)LOG_FORMAT=json|plain(默认plain)LOG_FILE:设置则输出到文件LOG_ROTATION(默认10 MB)、LOG_RETENTION(默认7 days)LOG_SENSITIVE_DEBUG:设置为1且LOG_LEVEL=DEBUG时允许输出未脱敏的敏感登录字段(如 password/secret/token),否则仅输出脱敏版
- CLI 参数(与环境变量等价,优先级更高):
uv run turbo-agent-auth serve run \
--log-level DEBUG \
--json \
--log-file logs/app.log \
--rotation "10 MB" \
--retention "7 days"
应用内日志工具:from turbo_agent_auth import logger, setup_logging
Prisma 初始化
-
确认已设置数据库环境变量:
DATABASE_URL=postgresql://user:pass@host:5432/dbname -
生成客户端(首次或修改 schema 后):
uv run prisma generate
-
迁移(首次将创建/应用迁移):
uv run prisma migrate dev --name init
(已实现)模型与迁移:请在设置 DATABASE_URL 后执行迁移与生成客户端。
# 1. 生成客户端
uv run prisma generate
# 2. 应用迁移(首次将创建 auth_init 迁移)
uv run prisma migrate dev --name auth_init
# 从旧版本升级到“多秘钥/外部ID/SMTP/POP3”版本:
uv run prisma migrate dev --name auth_update_multi_secrets_external_ids_smtp_pop3
uv run prisma generate
# 生产/部署环境(非交互)
uv run prisma migrate deploy && uv run prisma generate
# 后续新增:用户绑定与凭证保存/提醒列字段
uv run prisma migrate dev --name secret_user_bind_and_credentials
uv run prisma generate
运行测试
uv run pytest -q
架构概览
turbo-agent-auth 采用三层架构设计,实现了业务逻辑与核心机制的解耦:
-
核心层 (auth_core.py): 纯粹的认证流程执行引擎,无数据库依赖
AuthFlowExecutor: 基于生成器的多阶段流程编排器parse_response_data: 统一的响应解析引擎(支持 JSON/Headers/Cookies/HTML)prepare_login_request_config: 请求配置构建器(模板渲染、参数注入)
-
服务层 (services/auth_service.py): 数据库驱动的业务服务
- 平台/授权方式/秘钥的 CRUD 操作
- 集成
auth_core执行登录/刷新流程 - 持久化认证结果与执行轨迹
-
客户端层 (client_helper.py): 独立的 HTTP 客户端工具
AuthClient/AsyncAuthClient: 同步/异步认证客户端- 支持
login()/refresh()/get/post/put/patch/delete等完整 HTTP 方法 - 无需数据库连接,可在脚本/服务间复用
项目结构
main.py # Typer CLI 入口
src/turbo_agent_auth/
app.py # FastAPI 应用与路由聚合
auth_core.py # 核心认证引擎(无DB依赖)
client_helper.py # 独立HTTP客户端(支持sync/async)
logging.py # 日志配置(setup_logging / patch_standard_logging)
schemas.py # Pydantic 模型
api/
routes.py # API 路由(平台/授权方式/秘钥/默认schema)
services/
auth_service.py # 业务服务(集成 auth_core + 数据库)
config/
auth_schemas.yaml # 各授权方式默认字段Schema与解析映射
prisma/schema.prisma # Prisma 数据模型
tests/test_health.py # 基础健康与 ping 测试
Docker 构建与部署
已提供示例 Dockerfile(基于 uv 官方 python3.12 debian slim 镜像)与 docker-compose.yml。
Dockerfile 关键点:
- 使用 uv 安装依赖并生成 Prisma Client。
- 仅单阶段构建,减小复杂度。
- 通过环境变量注入
DATABASE_URL与可选AUTH_API_KEY。
构建与运行:
docker build -t turbo-agent-auth:latest .
docker run --rm -e DATABASE_URL='postgresql://user:pass@host:5432/dbname' -e AUTH_API_KEY=abc123 -p 8000:8000 turbo-agent-auth:latest
docker-compose 示例(见仓库根目录):
docker compose up -d --build
curl -H 'X-API-Key: changeme' http://127.0.0.1:8000/v1/ping
Compose 中包含 Postgres 服务与示例环境变量;首次启动后可执行迁移:
docker compose exec auth uv run prisma migrate deploy && docker compose exec auth uv run prisma generate
一键清理:
docker compose down -v
变更摘要(2025-11-20 新增)
- 启动可配置 API Key(
AUTH_API_KEY/ CLI--api-key),保护所有/v1/*路由(Bearer 格式)。 - 新增
Dockerfile与docker-compose.yml示例,简化部署与本地联调。 - 访问需鉴权接口使用
Authorization: Bearer <key>;未配置则保持兼容性不拦截。
变更摘要(2025-11-19)
本次迭代围绕“可观测性 / 生命周期 / 安全策略”完善:
- 多阶段登录:
loginFlow.steps支持步骤级responseMapping,可解析 HTML/Headers/Body/Cookies; - 步骤间注入:
requestPlacements将上一步解析结果注入下一步请求(headers/query/cookies/body); - 统一过期推导:综合
expires_in/expires_at与Set-Cookie取最早到期写入Secret.expiresAt; - 两类放置明确:登录阶段用
requestPlacements,业务阶段用authFieldPlacements; - 登录可观测性:新增
POST /v1/secrets/{id}/login/trace与PATCH /v1/secrets/{id}/with-execution,返回步骤快照与最终解析;普通登录仍用/login; - 首次创建即授权:
POST /v1/auth-methods/{id}/secret返回secret + execution复合体; - Secret 唯一性调整:取消
(authMethodId,name)唯一,仅保留id唯一;同名取最新; - 失效与通知:新增
/invalidate与/invalid-notified;登录失败自动置isValid=false,status=invalid,invalidNotified=false; - 有效性字段:
isValid+invalidNotified支持外部事件闭环与告警去重; - 隐私策略:
autoLoginEnabled=false不持久化loginPayload,登录接口必须即时携带; - 敏感字段日志:仅在
LOG_LEVEL=DEBUG且LOG_SENSITIVE_DEBUG=1时输出原文,否则 DEBUG 级别输出脱敏版;步骤执行 INFO / 4xx WARNING / 5xx ERROR; - 新增日志辅助函数(内部):统一输出
login.step / login.step.parsed / login.final / login.failure; - 文档:补充“秘钥生命周期与接口选择”(详见
docs/manuals.md)。
设计思路
- 配置驱动:Schema(表单)、Flow(请求模板)、Mapping(解析)、Placements(注入)组合实现通用授权。
- 两阶段“放置”语义清晰:
- 登录阶段(steps 间):
requestPlacements。 - 业务阶段(最终请求):
authFieldPlacements。
- 登录阶段(steps 间):
- 解析统一:步骤级与顶层
responseMapping共用解析器,支持 body.jsonPath、headers、cookies(含 Set-Cookie 聚合)、html 选择器/regex。 - 有效期:综合 token 与 cookie 的到期时间,选取最早者写入
Secret.expiresAt。
系统综述(故事版)
把 Auth 看成一个“可编排的流水线”:你先为某个平台声明一套“如何登录”(loginFlow)和“如何读回结果”(responseMapping)的规则,系统就会按照这套规则去请求远端接口、解析响应、计算过期时间,并把得到的“最终凭证”落到 Secret。整个过程无需写代码,像把乐高块拼起来:
- 你告诉系统“登录请求长这样”(url/method/headers/query/body_template),或者“先 A 再 B”(steps);
- 系统把你在前端表单里填的“登录字段”(loginPayload)代入模板发出请求;
- 收到响应后,它按
responseMapping去 JSON/Headers/Cookies/HTML 里找值,拼出你要的字段; - 如果是多步流,它把上一步解析到的值,按
requestPlacements放进下一步请求的 Header/Query/Cookie/Body; - 全部步骤跑完,再用顶层
responseMapping产出最终凭证,存到 Secret.data,并推导expiresAt; - 当你真正要调用业务接口时,再用
authFieldPlacements把 Secret.data 填回到业务请求里。
它像一个“低代码登录机器人”:你给出“表单结构 + 请求配方 + 结果采集 + 值的落位”,它就替你把浏览器里那一套操作流程复刻一遍,但更可靠、更可观察。
使用方法(快速)
- 创建平台与授权方式:
- 单步(如微信):
loginFlow直接含url/method/headers/body_template; - 多步(如 Superset):
loginFlow.steps[*]各自定义请求、可解析并注入下一步请求; - 完整样例见
docs/auth_method.md。
- 单步(如微信):
- 提交 Secret:
POST /v1/auth-methods/{id}/secret,可携带loginPayload与autoLoginEnabled: true触发登录。 - 解析调试:
POST /v1/auth-methods/{id}/parse校验responseMapping效果(支持 body/headers/cookies/html)。 - 结果查看:
GET /v1/secrets/{secret_id}查看data、authDataSources、expiresAt。
内部运行流程(login)
- 构建上下文:以
loginPayload为基础,自动注入runtime.callback_url与runtime.ip_allow_list; - 若配置
steps:逐步请求;在每步responseMapping解析后,将产物合并回上下文; - 下一步请求前应用
requestPlacements将上一步产物注入 headers/query/cookies/body; - 全部步骤末尾,对最终响应应用“顶层”
responseMapping,得到写入Secret.data的字段与expiresAt; - 更新
Secret.data、authDataSources、expiresAt、lastRefreshed、status。
端到端演练:Superset 二阶段(叙述)
以 Superset 为例:第 1 步你把用户名/密码交给系统。它先向 /login/ 发一遍表单请求,拿到两样东西:
- 响应头里的
Set-Cookie: session=...(系统会自动把每一个Set-Cookie折叠成 cookies map); - 登录页 HTML 里一个隐藏的
<input id="csrf_token" value="...">。
接着,系统执行第 2 步:仍请求 /login/,但这次会把“上一轮拿到的 session 放到 Cookie 里、csrf_token 放到表单里”。这正是 requestPlacements 的作用:
{
"requestPlacements": {
"cookies": {"session": "cookies.session"},
"body": {"username": "username", "password": "password", "csrf_token": "csrf_token"}
}
}
系统按这份说明,把“上下文里的 cookies.session / username / password / csrf_token”各就各位地植入请求。最后一跳收到 302/成功状态后,系统再把所有 Cookies 聚合到 Secret.data.cookies,并根据 Max-Age/Expires 与 token 的到期时间取一个最早者作为 expiresAt。这时 Secret 就“活”了:它知道自己什么时候过期、每个字段是从哪儿来的(authDataSources),也能被 authFieldPlacements 投放进你真实的业务请求。
requestPlacements 的心智模型
你可以把它理解成“把上下文里的值搬来搬去”的路由表:
- 上下文初始只有
loginPayload(外加系统自动注入的runtime.callback_url等); - 每一步解析成功后,上下文会追加新键,比如
cookies.session、csrf_token; - 下一步请求前,
requestPlacements读取这些键,把它们按你指定的位置写入 headers/query/cookies/body; - 如果需要拼接格式,可以写
{ "sourceField": "cookies.session", "format": "session={{value}}" }。
这就是“把上一步结果放到第二步请求里”的统一做法,它与最终的 authFieldPlacements 各司其职、互不干扰。
Secret 最终输出字段
data:最终凭证,如access_token、cookies.session等;authDataSources:字段来源(body.* / headers.* / cookies.* / html.* / manual-input);expiresAt:根据 token 与 cookie 取最早到期;lastRefreshed:最后更新时间;status:状态。usageMapping:等同于关联AuthMethod.authFieldPlacements,用于客户端把Secret.data注入到业务请求的headers/query/cookies/body。type:与AuthMethod.type一致(如OAuth1、OAuth2、Cookies等),客户端可据此选择构造请求的方式(例如OAuth1Session、Bearer 头或携带 Cookies)。
API 使用说明
-
平台
Platform:POST /v1/platformsGET /v1/platformsGET /v1/platforms/{platform_id}PATCH /v1/platforms/{platform_id}
-
授权方式
AuthMethod:POST /v1/auth-methods- Body:
{id, platformId, name, type, description?, loginFlow?, loginFieldsSchema?, refreshFlow?, refreshFieldsSchema?, responseMapping?, defaultValiditySeconds?, refreshBeforeSeconds?}
- Body:
GET /v1/platforms/{platform_id}/auth-methodsGET /v1/auth-methods/{auth_method_id}PATCH /v1/auth-methods/{auth_method_id}
-
默认 Schema:
GET /v1/auth-types/default-schemas: 获取所有类型默认的description,loginFlow,loginFieldsSchema等配置。GET /v1/auth-types/default-schemas/{auth_type}: 获取指定类型的默认配置。
-
密钥
Secret:POST /v1/auth-methods/{auth_method_id}/secret: 创建或更新一个密钥。GET /v1/auth-methods/{auth_method_id}/secret?name=xxx: 按名称查询密钥。GET /v1/auth-methods/{auth_method_id}/secrets: 列出该授权方式下的所有密钥。GET /v1/secrets/{secret_id}: 按ID查询密钥。PATCH /v1/secrets/{secret_id}POST /v1/secrets/{secret_id}/remind: 记录提醒(占位)。POST /v1/secrets/{secret_id}/login: 使用已保存的登录字段自动登录并刷新密钥(仅返回 Secret)。POST /v1/secrets/{secret_id}/login/trace: 触发登录,返回每一步执行快照与最终解析结果。PATCH /v1/secrets/{secret_id}/with-execution: 更新秘钥(可带loginPayload/autoLoginEnabled),并返回登录执行详情。DELETE /v1/secrets/{secret_id}: 删除秘钥。POST /v1/secrets/{secret_id}/invalidate: 外部将秘钥标记为失效,可选提供reason。POST /v1/secrets/{secret_id}/invalid-notified: 将失效通知状态置为已通知/未通知,Body:{ "notified": true }。
-
辅助接口:
POST /v1/auth-methods/{auth_method_id}/parse: 根据responseMapping提取令牌并计算到期时间,支持 body/headers/cookies/html。
自动登录与 Token 获取
Secret新增loginPayload、autoLoginEnabled、lastLoginAt、lastLoginStatus、lastLoginError等字段,可保存首次登录所需字段并追踪登录状态。- 创建或更新秘钥时传入
autoLoginEnabled: true会在服务端立即调用loginFlow,并将响应通过responseMapping写回data与expiresAt。 - 新增接口
POST /v1/secrets/{secret_id}/login,可随时触发重登;请求体可指定loginPayload重写登录字段以及storeLoginPayload是否同步保存。 - 登录成功会刷新
lastRefreshed、expiresAt(若响应含expires_in/expires_at或 Cookie 头包含过期时间)并记录lastLoginAt=UTC,设置isValid=true、lastLoginStatus=success。 - 登录失败会将
isValid=false、status=invalid、lastLoginStatus=failed并写入lastLoginError便于排查。 - Cookies 模式下会自动解析
Set-Cookie的Max-Age/Expires计算秘钥过期时间,GET /v1/secrets/{id}将返回即将过期时间供刷新决策。 AuthMethod可选定义authFieldsSchema(JSON Schema)与authFieldPlacements,用于声明用户可直接填写的授权字段以及这些字段在后续请求中应放置的 header/query/cookie/body 键位;Secret.data会据此校验并在authDataSources中记录字段来源(手动输入/接口解析等)。parse解析结果会透出fieldSources,帮助定位 access_token 等字段的来源路径;当响应提供expires_in(秒或时间戳)或expires_at时,会自动换算为 UTC 时间并存储。
登录流程可观测性(新)
- 日志输出:
login.step:步骤索引、方法、URL、状态码、耗时(ms),状态码 4xx→WARNING,5xx→ERROR。login.step.parsed:DEBUG 级别;步骤解析出的字段键名、步骤内推导的到期时间(如有)。login.final:最终解析后的字段键名与到期时间(INFO)。login.failure:异常信息(ERROR),并自动标记秘钥失效。
- API 回传:
POST /v1/secrets/{id}/login/trace返回结构:execution.steps[*]:每一步的request{method,url,headers,params,cookies,json,data}、statusCode、durationMs、extracted、fieldSources、ok。execution.finalExtracted/fieldSources/finalExpiresAt/success:最终结果汇总。
PATCH /v1/secrets/{id}/with-execution:先PATCH更新,再按上述方式回传执行详情。
秘钥生命周期(概要)
| 操作 | 接口 | 适用场景 | 返回执行轨迹 | 备注 |
|---|---|---|---|---|
| 创建/替换 | POST /auth-methods/{id}/secret |
首次建立或更换账号 | 是(如提供 loginPayload 且有 loginFlow) | autoLoginEnabled=false 不持久化登录表单 |
| 普通更新 | PATCH /secrets/{id} |
修改名称/标签/策略等 | 否 | 可附带 loginPayload 触发登录(若自动登录开启) |
| 更新+调试 | PATCH /secrets/{id}/with-execution |
验证表单改动/排障 | 是 | 登录失败自动失效 |
| 手动登录 | POST /secrets/{id}/login |
周期刷新 | 否 | 可提供 loginPayload 覆盖,本次可存储 |
| 手动登录(调试) | POST /secrets/{id}/login/trace |
排障/回归 | 是 | DEBUG 级字段来源更易定位 |
| 标记失效 | POST /secrets/{id}/invalidate |
外部吊销/异常检测 | 否 | 置 isValid=false,status=invalid,invalidNotified=false |
| 标记已通知 | POST /secrets/{id}/invalid-notified |
告警闭环 | 否 | 防止重复通知 |
| 删除 | DELETE /secrets/{id} |
账号迁移/废弃 | 否 | 彻底移除 |
更多细节与 curl 示例参见 docs/manuals.md。
示例:创建平台与 OAuth2 授权方式
# 1. 创建平台
curl -X POST http://127.0.0.1:8000/v1/platforms \
-H 'content-type: application/json' \
-d '{
"id":"plat_x",
"orgId":"org_demo",
"nameId":"x_com",
"name":"X (Twitter)",
"code":"x"
}'
# 2. 查看 OAuth2 的默认配置模板
curl -s http://127.0.0.1:8000/v1/auth-types/default-schemas/OAuth2 | jq
# 3. 创建一个 OAuth2 授权方式
# 注意:我们直接使用了默认模板中的 refreshFlow 和 responseMapping,
# 只需提供 loginFieldsSchema 中定义的字段即可。
curl -X POST http://127.0.0.1:8000/v1/auth-methods \
-H 'content-type: application/json' \
-d '{
"id":"am_x_oauth2",
"platformId":"plat_x",
"name":"oauth2_user_context",
"type":"OAuth2",
"loginFieldsSchema": {
"type": "object",
"properties": {
"client_id": { "type": "string", "title": "Client ID" },
"client_secret": { "type": "string", "title": "Client Secret" },
"authorize_url": { "type": "string", "format": "uri", "title": "Authorize URL" },
"token_url": { "type": "string", "format": "uri", "title": "Token URL" },
"scopes": { "type": "array", "items": { "type": "string" }, "title": "Scopes" }
},
"required": ["client_id", "client_secret", "authorize_url", "token_url", "scopes"]
},
"defaultValiditySeconds": 7200,
"refreshBeforeSeconds": 300
}'
# 4. 创建一个密钥 (Secret)
# 此时,用户需要根据 loginFieldsSchema 提供具体的值,
# 这些值将用于后续的认证流程(例如,重定向到 authorize_url)。
# data 字段存储了实际的认证配置。
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_x_oauth2/secret \
-H 'content-type: application/json' \
-d '{
"id":"sec_x_user_A",
"name":"user_A_secrets",
"tags":["test_account"],
"data": {
"client_id":"<YOUR_CLIENT_ID>",
"client_secret":"<YOUR_CLIENT_SECRET>",
"authorize_url":"https://twitter.com/i/oauth2/authorize",
"token_url":"https://api.twitter.com/2/oauth2/token",
"scopes":["tweet.read","users.read","tweet.write","offline.access"]
}
}'
示例:创建 JWT 登录授权方式
# 1. 创建一个 JWT 授权方式
# 这里我们自定义了 loginFlow,指向一个具体的登录端点。
curl -X POST http://127.0.0.1:8000/v1/auth-methods \
-H 'content-type: application/json' \
-d '{
"id":"am_my_service_jwt",
"platformId":"<your_platform_id>",
"name":"jwt_login",
"type":"JWT",
"description": "使用您在 MyService 的用户名和密码登录。",
"loginFlow": {
"url": "https://my-service.com/api/auth/login",
"method": "POST",
"headers": { "Content-Type": "application/json" },
"body_template": {
"login_id": "{{username}}",
"password": "{{password}}"
}
}
}'
# 2. 创建一个密钥 (Secret)
# 用户只需根据默认的 loginFieldsSchema 提供 username 和 password(通过 loginPayload)。
# 凭证不会被持久化,仅用于服务端登录。
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_my_service_jwt/secret \
-H 'content-type: application/json' \
-d '{
"id":"sec_my_service_user_B",
"name":"user_B_creds",
"data": {},
"loginPayload": {"username":"user_b","password":"a_very_secure_password"},
"autoLoginEnabled": true
}'
示例:Superset Cookies 授权方式(CSRF + 会话)
以下示例基于 http://43.140.209.117:8088/ 的 Superset 实例,演示如何收集两阶段登录得到的 Cookie,并将结果回填到 Cookies 类型的授权方式中。
- 创建
Cookies类型授权方式,声明需要存储的字段:
curl -X POST http://127.0.0.1:8000/v1/auth-methods \
-H 'content-type: application/json' \
-d '{
"id":"am_superset_cookies",
"platformId":"plat_superset",
"name":"superset_form_login",
"type":"Cookies",
"description":"Superset 表单登录,需要先获取 csrf_token 再提交一次表单完成会话初始化。",
"authFieldsSchema":{
"type":"object",
"properties":{
"cookies":{
"type":"object",
"title":"Superset Cookies",
"properties":{
"session":{"type":"string","title":"session"},
"csrf_token":{"type":"string","title":"csrf_token","description":"部分接口需要同步提交"}
},
"required":["session"]
}
},
"required":["cookies"]
}
}'
- 运行下面的 Python 片段完成“两次表单提交”并提取
csrf_token与最终的sessionCookie:
import json
import re
import requests
BASE_URL = "http://43.140.209.117:8088"
USERNAME = "admin"
PASSWORD = "your_password"
session = requests.Session()
# 第一次提交,只为了拿隐藏的 csrf_token 和初始 session
first = session.post(
f"{BASE_URL}/login/",
data={"username": USERNAME, "password": PASSWORD},
allow_redirects=False,
)
csrf_match = re.search(r'name="csrf_token" value="([^"]+)"', first.text)
if not csrf_match:
raise RuntimeError("未能在登录页解析到 csrf_token")
csrf_token = csrf_match.group(1)
initial_session = session.cookies.get("session")
# 第二次提交,携带 csrf_token 与初始 session 完成登录
second = session.post(
f"{BASE_URL}/login/",
data={
"username": USERNAME,
"password": PASSWORD,
"csrf_token": csrf_token,
},
headers={"Cookie": f"session={initial_session}"},
allow_redirects=False,
)
second.raise_for_status()
final_session = session.cookies.get("session")
payload = {
"body": {"csrf_token": csrf_token},
"headers": {"Set-Cookie": second.headers.get("Set-Cookie")},
"cookies": {"session": final_session},
}
print("解析请求示例:")
print(json.dumps(payload, indent=2, ensure_ascii=False))
print("Set-Cookie:", second.headers.get("Set-Cookie"))
- 将脚本输出填入解析与入库请求:
# 解析登录响应,计算过期时间并写入字段来源
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_superset_cookies/parse \
-H 'content-type: application/json' \
-d '{
"body": {"csrf_token": "<CSRF_TOKEN>"},
"headers": {"Set-Cookie": "session=<SESSION_COOKIE>; Expires=Wed, 19 Nov 2025 12:00:00 GMT; Path=/; HttpOnly"},
"cookies": {"session": "<SESSION_COOKIE>"}
}'
# 将 cookies 与 csrf_token 保存为 Secret,expiresAt 会根据解析结果写入
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_superset_cookies/secret \
-H 'content-type: application/json' \
-d '{
"id":"sec_superset_admin",
"name":"superset_admin",
"tags":["superset"],
"data":{
"cookies":{
"session":"<SESSION_COOKIE>",
"csrf_token":"<CSRF_TOKEN>"
}
}
}'
经过上述步骤,Secret 会同时保存 session 与 csrf_token;若后续响应同时返回 Token 与 Cookies,系统会自动选取最早过期的时间更新 expiresAt,避免凭证提前失效。
### 授权配置样例
#### 1. X 平台:OAuth1 授权方法 + 秘钥
```bash
# 创建授权方式(OAuth1)
curl -X POST http://127.0.0.1:8000/v1/auth-methods \
-H 'content-type: application/json' \
-d '{
"id":"am_x_oauth1",
"platformId":"plat_x",
"name":"oauth1_bot",
"type":"OAuth1",
"description":"X 平台 OAuth1 三阶段授权样例",
"loginFlow":{
"url":"https://api.x.com/oauth/request_token",
"method":"POST",
"headers":{"Content-Type":"application/json"},
"body_template":{
"consumer_key":"{{consumer_key}}",
"consumer_secret":"{{consumer_secret}}",
"callback_url":"{{callback_url}}"
}
},
"refreshFlow":{
"url":"https://api.x.com/oauth/access_token",
"method":"POST",
"headers":{"Content-Type":"application/json"},
"body_template":{
"oauth_token":"{{oauth_token}}",
"oauth_verifier":"{{oauth_verifier}}"
}
},
"authFieldsSchema":{
"type":"object",
"title":"OAuth1 授权凭证",
"properties":{
"oauth_token":{"type":"string","title":"OAuth Token"},
"oauth_token_secret":{"type":"string","title":"OAuth Token Secret"},
"access_token":{"type":"string","title":"Access Token"},
"access_token_secret":{"type":"string","title":"Access Token Secret"},
"expires_at":{"type":["integer","null"],"title":"过期时间戳"}
},
"required":["oauth_token","oauth_token_secret","access_token","access_token_secret"]
},
"authFieldPlacements":{
"headers":{
"Authorization":{"type":"Bearer","tokenField":"access_token"},
"oauth_token":{"type":"plain","valueField":"oauth_token"},
"oauth_token_secret":{"type":"plain","valueField":"oauth_token_secret"},
"x-token-secret":{"type":"plain","valueField":"access_token_secret"}
},
"cookies":{
"AUTHORIZATION":{"sourceField":"access_token","template":"token={{value}}"}
},
"metadata":{
"tokenType":"OAuth1",
"defaultExpiresField":"expires_at"
}
},
"responseMapping":{
"oauth_token":"oauth_token",
"oauth_token_secret":"oauth_token_secret",
"access_token":"access_token",
"access_token_secret":"access_token_secret",
"expires_in":"expires_in"
}
}'
# 写入秘钥并启用自动登录获取 request token
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_x_oauth1/secret \
-H 'content-type: application/json' \
-d '{
"id":"sec_x_oauth1_bot",
"name":"bot_account",
"data":{},
"loginPayload":{
"consumer_key":"<X_CONSUMER_KEY>",
"consumer_secret":"<X_CONSUMER_SECRET>",
"callback_url":"https://your-app.example.com/oauth/callback"
},
"autoLoginEnabled":true
}'
# 随时可通过 login 接口发起 refresh,完成 access token 替换
curl -X POST http://127.0.0.1:8000/v1/secrets/sec_x_oauth1_bot/login \
-H 'content-type: application/json' \
-d '{"storeLoginPayload":false,
"loginPayload":{"oauth_token":"<OAUTH_TOKEN>","oauth_verifier":"<OAUTH_VERIFIER>"}}'
```
#### 2. 微信公众平台:OAuth1(稳定版接口)示例
- 第一阶段:管理员在前端表单中根据 `loginFieldsSchema` 填写 `AppID`/`AppSecret`,系统自动注入环境变量提供的回调地址;
- 第二阶段:服务端根据 `loginFlow` 调用微信接口 `POST https://api.weixin.qq.com/cgi-bin/stable_token`,按要求传入 `grant_type=client_credential` 等字段获取稳定版 `access_token`;
- 接口返回的 `access_token` 与 `expires_in` 会通过 `responseMapping` 保存到 `Secret.data` 中,并折算出到期时间。
示例授权方式(仅展示关键字段):
```bash
curl -X POST http://127.0.0.1:8000/v1/auth-methods \
-H 'content-type: application/json' \
-d '{
"id": "am_wechat_official_oauth1",
"platformId": "plat_wechat_official",
"name": "wechat_official_oauth1",
"type": "OAuth1",
"description": "微信公众平台稳定版 access_token 获取流程,两步:配置账号与调用 stable_token 接口。",
"loginFieldsSchema": {
"type": "object",
"required": ["appid", "secret"],
"properties": {
"appid": {"type": "string", "title": "AppID"},
"secret": {"type": "string", "title": "AppSecret"},
"grant_type": {
"type": "string",
"title": "grant_type",
"default": "client_credential"
},
"force_refresh": {
"type": "boolean",
"title": "force_refresh",
"default": false
},
"callback_url": {
"type": "string",
"title": "回调地址(只读)",
"readOnly": true,
"description": "由 turbo-agent-auth 环境变量提供,禁止人工填写"
}
}
},
"loginFlow": {
"url": "https://api.weixin.qq.com/cgi-bin/stable_token",
"method": "POST",
"headers": {"Content-Type": "application/json"},
"body_template": {
"grant_type": "{{grant_type}}",
"appid": "{{appid}}",
"secret": "{{secret}}",
"force_refresh": "{{force_refresh}}"
}
},
"responseMapping": {
"fields": {
"access_token": "access_token",
"expires_in": "expires_in"
}
},
"defaultValiditySeconds": 7200,
"refreshBeforeSeconds": 300
}'
```
创建 Secret 时只需提供账号描述与标签,`data` 会由登录响应填充。例如:
```bash
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_wechat_official_oauth1/secret \
-H 'content-type: application/json' \
-d '{
"id": "sec_wechat_demo",
"name": "wechat_official_demo",
"tags": ["wechat", "official"],
"data": {},
"loginPayload": {
"appid": "<你的公众号 AppID>",
"secret": "<你的公众号 AppSecret>",
"force_refresh": false
},
"autoLoginEnabled": true
}'
```
> 提示:如需向微信侧备案 IP 白名单,可调用 `/v1/runtime/ip-allowlist` 获取当前出口地址。
> OAuth1 流程通常需要在外部完成用户授权,本示例中 `loginPayload` 第一次写入 `consumer_key/secret` 获取 request token,回调后将 `oauth_token` / `oauth_verifier` 传回,再次调用 `/login` 即可落库 access token。
#### 2. Superset 平台:JWT(登录 + 刷新)
```bash
# 创建 Superset JWT 授权方式
curl -X POST http://127.0.0.1:8000/v1/auth-methods \
-H 'content-type: application/json' \
-d '{
"id":"am_superset_jwt",
"platformId":"plat_superset",
"name":"jwt_service_account",
"type":"JWT",
"description":"Superset API JWT 登录/刷新",
"loginFlow":{
"url":"https://superset.example.com/api/v1/security/login",
"method":"POST",
"headers":{"Content-Type":"application/json"},
"body_template":{
"username":"{{username}}",
"password":"{{password}}",
"provider":"db",
"refresh":true
}
},
"refreshFlow":{
"url":"https://superset.example.com/api/v1/security/refresh",
"method":"POST",
"headers":{
"Content-Type":"application/json",
"Authorization":"Bearer {{access_token}}"
},
"body_template":{
"refresh_token":"{{refresh_token}}"
}
},
"responseMapping":{
"access_token":"access_token",
"refresh_token":"refresh_token",
"expires_in":"expires_in"
}
}'
# 上传登录凭证并自动获取 access_token
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_superset_jwt/secret \
-H 'content-type: application/json' \
-d '{
"id":"sec_superset_jwt_admin",
"name":"admin_jwt",
"data":{},
"loginPayload":{
"username":"admin",
"password":"<SUPERSET_PASSWORD>"
},
"autoLoginEnabled":true
}'
# 当 refresh_token 即将过期时再次执行登录
curl -X POST http://127.0.0.1:8000/v1/secrets/sec_superset_jwt_admin/login
```
#### 3. Superset 平台:Cookies (登录 + 自动过期检测)
```bash
# 创建基于 Cookie 的授权方式
curl -X POST http://127.0.0.1:8000/v1/auth-methods \
-H 'content-type: application/json' \
-d '{
"id":"am_superset_cookie",
"platformId":"plat_superset",
"name":"cookie_session",
"type":"Cookies",
"description":"Superset 会话 Cookie 登录,自动解析 Set-Cookie 的过期时间",
"loginFlow":{
"url":"https://superset.example.com/api/v1/security/login",
"method":"POST",
"headers":{"Content-Type":"application/json"},
"body_template":{
"username":"{{username}}",
"password":"{{password}}",
"provider":"db"
}
},
"refreshFlow":{
"url":"https://superset.example.com/api/v1/security/refresh",
"method":"POST",
"headers":{
"Content-Type":"application/json",
"Authorization":"Bearer {{access_token}}"
},
"body_template":{
"refresh_token":"{{refresh_token}}"
}
},
"responseMapping":{}
}'
# 写入账号信息并自动发起登录,Secret 中会保存 cookies 与 expiresAt
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_superset_cookie/secret \
-H 'content-type: application/json' \
-d '{
"id":"sec_superset_cookie_admin",
"name":"admin_cookie",
"data":{},
"loginPayload":{
"username":"admin",
"password":"<SUPERSET_PASSWORD>"
},
"autoLoginEnabled":true
}'
# 查询秘钥即可看到 cookies 及 expiresAt,过期后返回 401 时可调用 login 重新获取
curl http://127.0.0.1:8000/v1/secrets/sec_superset_cookie_admin | jq
```
> Superset 登录接口会同时返回 Token 与会话 Cookie,本方案使用 Cookie 方式以覆盖需要浏览器态会话的场景;`parse_response` 会读取 `Set-Cookie` 的 `Max-Age/Expires` 并自动推算秘钥过期时间。
Metadata
Release files for turbo-agent-auth 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| turbo_agent_auth-0.1.2.tar.gz | 108.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| turbo_agent_auth-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 160.1 kB
Release files / turbo_agent_auth-0.1.2.tar.gz
| Download URL | turbo_agent_auth-0.1.2.tar.gz |
|---|---|
| Size | 108.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4e0be12659a86d98fa58990640bf5a0f4e2f85613a934a7ec6a9641639b456cb
|
|
BLAKE2b-256 checksum How to use checksums |
ce67d97f7e19522fa50527c1e7fe3aad0c8260f65e3d9dd2cb1f71de544308bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Release files / turbo_agent_auth-0.1.2-py3-none-any.whl
| Download URL | turbo_agent_auth-0.1.2-py3-none-any.whl |
|---|---|
| Size | 52.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
44ce40b6955f87bce0ec435a2c38ba44afa881c66c5a85c850ef4fcef20ad1c7
|
|
BLAKE2b-256 checksum How to use checksums |
d95962fde91300a011fa33a3282de3006d6d56421b5bf93275853636e02fe5f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|