From 6aa71a4da813058f4b64974d8f3bb9b7061f2a47 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 4 Jun 2026 11:10:06 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E6=9C=BA=E5=88=B6=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20OPENFLARE=5FTOKEN=20=E6=9B=BF=E4=BB=A3=20Bearer=20Token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/deployment/server.md | 2 +- docs/design/architecture.md | 2 +- docs/guide/troubleshooting.md | 6 +- docs/guildline/development-constraints.md | 8 +- docs/reference/api.md | 12 +- docs/reference/configuration.md | 4 +- openflare_server/controller/access_log.go | 12 +- openflare_server/controller/acme_account.go | 2 +- openflare_server/controller/agent.go | 6 +- openflare_server/controller/auth_source.go | 10 +- openflare_server/controller/config_version.go | 16 +-- openflare_server/controller/dashboard.go | 2 +- openflare_server/controller/database.go | 2 +- openflare_server/controller/dns_account.go | 8 +- openflare_server/controller/github.go | 15 +-- openflare_server/controller/managed_domain.go | 10 +- openflare_server/controller/node.go | 22 ++-- openflare_server/controller/proxy_route.go | 10 +- .../controller/tls_certificate.go | 22 ++-- openflare_server/controller/update.go | 2 +- openflare_server/controller/uptimekuma.go | 2 +- openflare_server/controller/user.go | 57 ++++++--- openflare_server/docs/docs.go | 120 +++++++++--------- openflare_server/docs/swagger.json | 120 +++++++++--------- openflare_server/docs/swagger.yaml | 120 +++++++++--------- openflare_server/main.go | 6 +- openflare_server/middleware/auth.go | 88 ++++--------- openflare_server/middleware/cors.go | 2 +- openflare_server/router/api_phase1_test.go | 8 +- openflare_server/router/api_phase2_test.go | 21 +-- .../router/api_uptimekuma_test.go | 6 +- openflare_server/router/update_test.go | 54 +++++--- .../components/providers/auth-provider.tsx | 2 + .../web/features/auth/api/auth.ts | 34 ++++- .../web/features/settings/api/settings.ts | 6 +- openflare_server/web/lib/api/auth-token.ts | 22 ++++ openflare_server/web/lib/api/client.ts | 7 + openflare_server/web/types/auth.ts | 1 + 38 files changed, 462 insertions(+), 387 deletions(-) create mode 100644 openflare_server/web/lib/api/auth-token.ts diff --git a/docs/deployment/server.md b/docs/deployment/server.md index a7ee8e89..4ce2c9ae 100644 --- a/docs/deployment/server.md +++ b/docs/deployment/server.md @@ -91,7 +91,7 @@ docker run -d \ 启动参数说明: * **`-p 3000:3000`**:映射宿主机 `3000` 端口到容器内 `3000` 端口。 * **`-v $(pwd)/openflare-data:/data`**:挂载本地目录到容器的 `/data`,确保数据库文件 `openflare.db` 在重启或重建容器时不丢失。 -* **`SESSION_SECRET`**:必须配置的 Session 密钥签名哈希。 +* **`SESSION_SECRET`**:建议配置的临时 Session 签名密钥,主要用于 OAuth 状态等非管理端 API 鉴权流程;管理端 API 登录凭证通过 `OPENFLARE_TOKEN` 请求头传递。 --- diff --git a/docs/design/architecture.md b/docs/design/architecture.md index 2189ca9d..bfe1ed3a 100644 --- a/docs/design/architecture.md +++ b/docs/design/architecture.md @@ -79,7 +79,7 @@ Agent 本地 Pages 部署目录 * Gin 提供 HTTP 服务。 * GORM 访问 SQLite 或 PostgreSQL。 -* 现有登录体系提供管理端 Session。 +* 现有登录体系签发管理端用户 Token,管理端 API 通过 `OPENFLARE_TOKEN` 请求头鉴权。 * 认证源与外部账号绑定支持 GitHub OAuth 和标准 OIDC。 * Go Server 托管 `openflare_server/web` 静态构建产物。 diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md index dee2169c..1c1146f5 100644 --- a/docs/guide/troubleshooting.md +++ b/docs/guide/troubleshooting.md @@ -9,7 +9,7 @@ | 现象 | 先看哪里 | | --- | --- | | 管理端打不开 | Server 容器或进程日志、端口监听 | -| 登录异常 | 默认账号、Session Secret、浏览器请求、Server 日志 | +| 登录异常 | 默认账号、OPENFLARE_TOKEN、浏览器请求、Server 日志 | | 数据无法保存 | 数据库连接、SQLite 文件权限、PostgreSQL 健康状态 | | Agent 离线 | Agent 日志、Token、Server 地址、网络连通性 | | 发布后节点未更新 | 激活版本、节点 heartbeat、应用记录 | @@ -85,8 +85,8 @@ NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev 1. 确认连接的是预期数据库,避免 `SQLITE_PATH` 或 `DSN` 指向了另一个环境。 2. 查看 Server 日志中使用的是 `sqlite` 还是 `postgres`。 -3. 如果部署在多副本或反向代理后,确认 `SESSION_SECRET` 固定且各实例一致。 -4. 清理浏览器 Cookie 后重新登录。 +3. 在浏览器开发者工具中确认管理端 API 请求携带 `OPENFLARE_TOKEN` 请求头。 +4. 清理浏览器本地存储中的旧 `openflare_token` 后重新登录。 ### 应急重置管理员密码 diff --git a/docs/guildline/development-constraints.md b/docs/guildline/development-constraints.md index 1e851af4..3621d171 100644 --- a/docs/guildline/development-constraints.md +++ b/docs/guildline/development-constraints.md @@ -167,19 +167,19 @@ v1-v7 视为历史初始基线,不再维护逐版本升级文件。v8-v17 是 - Client 心跳返回 tunnel 配置版本摘要。 - Client 可拉取完整配置(relay 列表 + frpc 代理定义)。 - Client 上报配置应用结果。 -* **Admin Tunnel 管理 API** - `/api/tunnels/*`,要求 Admin Session。 +* **Admin Tunnel 管理 API** - `/api/tunnels/*`,要求管理端 `OPENFLARE_TOKEN`。 - CRUD tunnel 实体(创建、查询、更新、删除)。 - Token 管理(生成、轮换)。 - 强制同步(触发 Client 立即拉取新配置)。 -* **Admin Pages 管理 API** - `/api/pages/*`,要求 Admin Session。 +* **Admin Pages 管理 API** - `/api/pages/*`,要求管理端 `OPENFLARE_TOKEN`。 - CRUD Pages 项目,包括 SPA fallback 启用状态与回退路径。 - 上传 zip 部署包、查看部署历史、激活部署、删除非激活部署。 * **Agent Pages 下载 API** - `/api/agent/pages/*`,使用 `X-Agent-Token` 认证。 - Agent 仅能按激活配置引用的部署 ID 拉取静态部署包,不提供任意文件读取或远程命令入口。 * 总览与节点详情优先使用专用聚合接口。 * 管理端变更类接口统一使用 `POST`;只读接口使用 `GET`。 -* 管理端继续复用现有登录、角色与 Session。 -* 第三方登录统一通过认证源 API 进入,认证源管理接口必须要求 Root Session。 +* 管理端登录成功后返回用户 token;管理端 API 只允许从 `OPENFLARE_TOKEN` 请求头读取登录凭证,不得通过 Cookie Session 放行。 +* 第三方登录统一通过认证源 API 进入,认证源管理接口必须要求 Root 级 `OPENFLARE_TOKEN`。 * `/api/status` 只能返回已启用认证源的公开字段,不得返回 Client Secret。 * 第三方账号未绑定且注册关闭时,应提供绑定已有账号流程,不得自动创建用户。 * Agent/Relay/Client 正式请求统一使用对应的专属 token(`agent_token` / `relay_token`(即 agent_token) / `tunnel_token`)。 diff --git a/docs/reference/api.md b/docs/reference/api.md index 393a220d..fa962252 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -20,7 +20,7 @@ OpenFlare 的管理端 API 与 Agent API 都使用 JSON。 | 类型 | 约定 | | --- | --- | -| 管理端 API | 由管理端 Session 鉴权 | +| 管理端 API | 由 `OPENFLARE_TOKEN` 请求头鉴权 | | Agent API | 固定放在 `/api/agent/*` | | Relay API | 固定放在 `/api/relay/*`,使用 `X-Agent-Token` 鉴权(与 Agent 复用同一 token) | | OpenFlared API | 固定放在 `/api/flared/*`,使用 `X-Tunnel-Token` 鉴权(独立的 tunnel_token) | @@ -29,7 +29,7 @@ OpenFlare 的管理端 API 与 Agent API 都使用 JSON。 ## WAF IP 组接口 -管理端 WAF IP 组接口统一要求管理端 Session 鉴权: +管理端 WAF IP 组接口统一要求管理端 `OPENFLARE_TOKEN` 鉴权: | 方法 | 路径 | 说明 | | --- | --- | --- | @@ -47,7 +47,13 @@ IP 组 `type` 支持 `manual`、`automatic`、`subscription`。自动型 IP 组 ## 鉴权 -管理端继续复用现有登录、角色与 Session。 +管理端登录成功后返回用户 token,后续所有管理端 API 必须在请求头中携带: + +```http +OPENFLARE_TOKEN: +``` + +Server 只从 `OPENFLARE_TOKEN` 读取管理端登录凭证,不再通过 Cookie Session 放行管理端 API。角色和用户状态仍以数据库中的当前用户记录为准。 Agent 正式请求统一使用节点专属 `agent_token`,首次接入可使用全局 `discovery_token`。Agent 请求头固定为: diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 64f94e62..45a11d67 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -64,7 +64,7 @@ go run . --port 3000 --log-dir ./logs | `PORT` | Server 监听端口 | `3000` | | `GIN_MODE` | Gin 运行模式 | 非 `debug` 时按 release | | `LOG_LEVEL` | 日志等级 | `info` | -| `SESSION_SECRET` | Session 签名密钥 | 启动时随机生成 | +| `SESSION_SECRET` | 临时 Session 签名密钥,主要用于 OAuth 状态等非管理端 API 鉴权流程 | 启动时随机生成 | | `SQLITE_PATH` | SQLite 数据库文件路径 | `openflare.db` | | `DSN` | PostgreSQL DSN,设置后优先于 SQLite | 空 | | `SQL_DSN` | 兼容旧命名的 PostgreSQL DSN,优先级低于 `DSN` | 空 | @@ -76,7 +76,7 @@ go run . --port 3000 --log-dir ./logs * `DSN` 与 `SQL_DSN` 同时存在时优先使用 `DSN`。 * `DSN` 或 `SQL_DSN` 与 `SQLITE_PATH` 同时存在时优先使用 PostgreSQL。 * 当目标 PostgreSQL 数据库为空且本地 `SQLITE_PATH` 文件存在时,Server 启动阶段会自动迁移 SQLite 数据,并在日志中输出按表迁移进度。 -* `SESSION_SECRET` 生产环境必须显式配置。 +* `SESSION_SECRET` 生产环境建议显式配置,避免 OAuth 授权状态等临时会话在重启后失效;管理端 API 登录凭证不再通过 Cookie Session 传递,而是使用 `OPENFLARE_TOKEN` 请求头。 * `REDIS_CONN_STRING` 未配置时,相关能力回退为进程内实现。 ## 运行时 Option diff --git a/openflare_server/controller/access_log.go b/openflare_server/controller/access_log.go index 8aea06ae..f2fe875e 100644 --- a/openflare_server/controller/access_log.go +++ b/openflare_server/controller/access_log.go @@ -11,7 +11,7 @@ import ( // @Summary List access logs // @Tags AccessLogs // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param node_id query string false "Node ID" // @Param remote_addr query string false "Remote address" // @Param host query string false "Host" @@ -35,7 +35,7 @@ func GetAccessLogs(c *gin.Context) { // @Summary List folded access logs // @Tags AccessLogs // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param node_id query string false "Node ID" // @Param remote_addr query string false "Remote address" // @Param host query string false "Host" @@ -62,7 +62,7 @@ func GetFoldedAccessLogs(c *gin.Context) { // @Summary List folded access log IP summaries // @Tags AccessLogs // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param node_id query string false "Node ID" // @Param remote_addr query string false "Remote address" // @Param host query string false "Host" @@ -99,7 +99,7 @@ func GetFoldedAccessLogIPs(c *gin.Context) { // @Summary List access log IP summaries // @Tags AccessLogs // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param node_id query string false "Node ID" // @Param remote_addr query string false "Remote address" // @Param host query string false "Host" @@ -130,7 +130,7 @@ func GetAccessLogIPSummaries(c *gin.Context) { // @Summary Get access log IP trend // @Tags AccessLogs // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param node_id query string false "Node ID" // @Param remote_addr query string true "Remote address" // @Param host query string false "Host" @@ -158,7 +158,7 @@ func GetAccessLogIPTrend(c *gin.Context) { // @Tags AccessLogs // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/access-logs/cleanup [post] func CleanupAccessLogs(c *gin.Context) { diff --git a/openflare_server/controller/acme_account.go b/openflare_server/controller/acme_account.go index 5133a9d6..42e73dc3 100644 --- a/openflare_server/controller/acme_account.go +++ b/openflare_server/controller/acme_account.go @@ -10,7 +10,7 @@ import ( // @Summary Get default ACME account // @Tags AcmeAccounts // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/acme-accounts/default [get] func GetDefaultAcmeAccount(c *gin.Context) { diff --git a/openflare_server/controller/agent.go b/openflare_server/controller/agent.go index 5c9b45cf..6de1c541 100644 --- a/openflare_server/controller/agent.go +++ b/openflare_server/controller/agent.go @@ -296,7 +296,7 @@ func handleAgentWSStatus(c *gin.Context, node *model.Node, message service.Agent // @Summary List nodes // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/nodes/ [get] func GetNodes(c *gin.Context) { @@ -312,7 +312,7 @@ func GetNodes(c *gin.Context) { // @Summary List apply logs // @Tags ApplyLogs // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param node_id query string false "Node ID" // @Success 200 {object} map[string]interface{} // @Router /api/apply-logs/ [get] @@ -334,7 +334,7 @@ func GetApplyLogs(c *gin.Context) { // @Tags ApplyLogs // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/apply-logs/cleanup [post] func CleanupApplyLogs(c *gin.Context) { diff --git a/openflare_server/controller/auth_source.go b/openflare_server/controller/auth_source.go index 7af625f6..53f57578 100644 --- a/openflare_server/controller/auth_source.go +++ b/openflare_server/controller/auth_source.go @@ -199,10 +199,8 @@ func OAuthCallback(c *gin.Context) { return } var currentUserID *int - if value := session.Get("id"); value != nil { - if idValue, ok := value.(int); ok { - currentUserID = &idValue - } + if currentUser := currentUserFromOpenFlareToken(c); currentUser != nil { + currentUserID = ¤tUser.Id } result, pending, err := service.CompleteOAuthLogin(source, profile, currentUserID) if err != nil { @@ -224,7 +222,7 @@ func OAuthCallback(c *gin.Context) { return } if result.User != nil { - cleanUser, err := setLoginSession(result.User, c) + cleanUser, err := setLoginToken(result.User) if err != nil { respondFailure(c, "无法保存会话信息,请重试") return @@ -261,7 +259,7 @@ func LinkExistingOAuthAccount(c *gin.Context) { respondFailure(c, "无法更新会话信息,请重试") return } - cleanUser, err := setLoginSession(user, c) + cleanUser, err := setLoginToken(user) if err != nil { respondFailure(c, "无法保存会话信息,请重试") return diff --git a/openflare_server/controller/config_version.go b/openflare_server/controller/config_version.go index b14e913f..e1568578 100644 --- a/openflare_server/controller/config_version.go +++ b/openflare_server/controller/config_version.go @@ -10,7 +10,7 @@ import ( // @Summary List config versions // @Tags ConfigVersions // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/config-versions/ [get] func GetConfigVersions(c *gin.Context) { @@ -26,7 +26,7 @@ func GetConfigVersions(c *gin.Context) { // @Summary Get config version detail // @Tags ConfigVersions // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Version ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -48,7 +48,7 @@ func GetConfigVersion(c *gin.Context) { // @Summary Get active config version // @Tags ConfigVersions // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/config-versions/active [get] func GetActiveConfigVersion(c *gin.Context) { @@ -64,7 +64,7 @@ func GetActiveConfigVersion(c *gin.Context) { // @Summary Preview config rendering // @Tags ConfigVersions // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/config-versions/preview [get] func PreviewConfigVersion(c *gin.Context) { @@ -80,7 +80,7 @@ func PreviewConfigVersion(c *gin.Context) { // @Summary Diff current draft against active version // @Tags ConfigVersions // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/config-versions/diff [get] func DiffConfigVersion(c *gin.Context) { @@ -96,7 +96,7 @@ func DiffConfigVersion(c *gin.Context) { // @Summary Publish a new config version // @Tags ConfigVersions // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/config-versions/publish [post] func PublishConfigVersion(c *gin.Context) { @@ -114,7 +114,7 @@ func PublishConfigVersion(c *gin.Context) { // @Summary Activate an existing config version // @Tags ConfigVersions // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Version ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -140,7 +140,7 @@ type CleanupConfigVersionRequest struct { // @Summary Cleanup old config versions // @Tags ConfigVersions // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param request body CleanupConfigVersionRequest true "Cleanup request" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} diff --git a/openflare_server/controller/dashboard.go b/openflare_server/controller/dashboard.go index 8dab9d82..232ec25e 100644 --- a/openflare_server/controller/dashboard.go +++ b/openflare_server/controller/dashboard.go @@ -33,7 +33,7 @@ type dashboardTrendsPayload struct { // @Summary Get dashboard overview // @Tags Dashboard // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} // @Router /api/dashboard/overview [get] diff --git a/openflare_server/controller/database.go b/openflare_server/controller/database.go index a1ef3f51..3275c010 100644 --- a/openflare_server/controller/database.go +++ b/openflare_server/controller/database.go @@ -11,7 +11,7 @@ import ( // @Tags Options // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/option/database/cleanup [post] func CleanupDatabaseObservability(c *gin.Context) { diff --git a/openflare_server/controller/dns_account.go b/openflare_server/controller/dns_account.go index acc8697a..0a9705f6 100644 --- a/openflare_server/controller/dns_account.go +++ b/openflare_server/controller/dns_account.go @@ -16,7 +16,7 @@ type DnsAccountInput struct { // @Summary List DNS accounts // @Tags DnsAccounts // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/dns-accounts/ [get] func GetDnsAccounts(c *gin.Context) { @@ -33,7 +33,7 @@ func GetDnsAccounts(c *gin.Context) { // @Tags DnsAccounts // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param payload body DnsAccountInput true "DNS account payload" // @Success 200 {object} map[string]interface{} // @Router /api/dns-accounts/ [post] @@ -62,7 +62,7 @@ func CreateDnsAccount(c *gin.Context) { // @Tags DnsAccounts // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "DNS Account ID" // @Param payload body DnsAccountInput true "DNS account payload" // @Success 200 {object} map[string]interface{} @@ -100,7 +100,7 @@ func UpdateDnsAccount(c *gin.Context) { // @Summary Delete DNS account // @Tags DnsAccounts // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "DNS Account ID" // @Success 200 {object} map[string]interface{} // @Router /api/dns-accounts/{id}/delete [post] diff --git a/openflare_server/controller/github.go b/openflare_server/controller/github.go index b62b85fc..124b7fac 100644 --- a/openflare_server/controller/github.go +++ b/openflare_server/controller/github.go @@ -11,7 +11,6 @@ import ( "openflare/model" "time" - "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" ) @@ -79,9 +78,7 @@ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) { } func GitHubOAuth(c *gin.Context) { - session := sessions.Default(c) - username := session.Get("username") - if username != nil { + if currentUserFromOpenFlareToken(c) != nil { GitHubBind(c) return } @@ -135,10 +132,12 @@ func GitHubBind(c *gin.Context) { respondFailure(c, "该 GitHub 账户已被绑定") return } - session := sessions.Default(c) - id := session.Get("id") - // id := c.GetInt("id") // critical bug! - user.Id = id.(int) + currentUser := currentUserFromOpenFlareToken(c) + if currentUser == nil { + respondFailure(c, "无权进行此操作,未登录或 token 无效") + return + } + user.Id = currentUser.Id err = user.FillUserById() if err != nil { respondFailure(c, err.Error()) diff --git a/openflare_server/controller/managed_domain.go b/openflare_server/controller/managed_domain.go index 34c21ec3..5b192f01 100644 --- a/openflare_server/controller/managed_domain.go +++ b/openflare_server/controller/managed_domain.go @@ -11,7 +11,7 @@ import ( // @Summary List managed domains // @Tags ManagedDomains // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/managed-domains/ [get] func GetManagedDomains(c *gin.Context) { @@ -28,7 +28,7 @@ func GetManagedDomains(c *gin.Context) { // @Tags ManagedDomains // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param payload body service.ManagedDomainInput true "Managed domain payload" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -51,7 +51,7 @@ func CreateManagedDomain(c *gin.Context) { // @Tags ManagedDomains // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Managed domain ID" // @Param payload body service.ManagedDomainInput true "Managed domain payload" // @Success 200 {object} map[string]interface{} @@ -78,7 +78,7 @@ func UpdateManagedDomain(c *gin.Context) { // @Summary Delete managed domain // @Tags ManagedDomains // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Managed domain ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -99,7 +99,7 @@ func DeleteManagedDomain(c *gin.Context) { // @Summary Match certificate for domain // @Tags ManagedDomains // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param domain query string true "Domain" // @Success 200 {object} map[string]interface{} // @Router /api/managed-domains/match [get] diff --git a/openflare_server/controller/node.go b/openflare_server/controller/node.go index ff88fa21..eaf6224c 100644 --- a/openflare_server/controller/node.go +++ b/openflare_server/controller/node.go @@ -21,7 +21,7 @@ type nodeObservabilityQuery struct { // @Tags Nodes // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param payload body service.NodeInput true "Node payload" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -44,7 +44,7 @@ func CreateNode(c *gin.Context) { // @Summary Get global discovery token // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/nodes/bootstrap-token [get] func GetNodeBootstrapToken(c *gin.Context) { @@ -60,7 +60,7 @@ func GetNodeBootstrapToken(c *gin.Context) { // @Summary Rotate global discovery token // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/nodes/bootstrap-token/rotate [post] func RotateNodeBootstrapToken(c *gin.Context) { @@ -77,7 +77,7 @@ func RotateNodeBootstrapToken(c *gin.Context) { // @Tags Nodes // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Node ID" // @Param payload body service.NodeInput true "Node payload" // @Success 200 {object} map[string]interface{} @@ -106,7 +106,7 @@ func UpdateNode(c *gin.Context) { // @Summary Delete node // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Node ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -128,7 +128,7 @@ func DeleteNode(c *gin.Context) { // @Summary Request agent self-update on node // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Node ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -162,7 +162,7 @@ func RequestNodeAgentUpdate(c *gin.Context) { // @Summary Request openresty restart on node // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Node ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -185,7 +185,7 @@ func RequestNodeOpenrestyRestart(c *gin.Context) { // @Summary Request force sync config on node // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Node ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -208,7 +208,7 @@ func RequestNodeForceSync(c *gin.Context) { // @Summary Check latest agent release for node // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Node ID" // @Param channel query string false "stable or preview" // @Success 200 {object} map[string]interface{} @@ -232,7 +232,7 @@ func GetNodeAgentRelease(c *gin.Context) { // @Summary Get node observability details // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Node ID" // @Param hours query int false "Lookback window in hours" // @Param limit query int false "Max records per section" @@ -266,7 +266,7 @@ func GetNodeObservability(c *gin.Context) { // @Summary Cleanup node health events // @Tags Nodes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Node ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} diff --git a/openflare_server/controller/proxy_route.go b/openflare_server/controller/proxy_route.go index fb3d54b0..7be2d13a 100644 --- a/openflare_server/controller/proxy_route.go +++ b/openflare_server/controller/proxy_route.go @@ -10,7 +10,7 @@ import ( // @Summary List proxy routes // @Tags ProxyRoutes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/proxy-routes/ [get] func GetProxyRoutes(c *gin.Context) { @@ -26,7 +26,7 @@ func GetProxyRoutes(c *gin.Context) { // @Summary Get proxy route detail // @Tags ProxyRoutes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Route ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -49,7 +49,7 @@ func GetProxyRoute(c *gin.Context) { // @Tags ProxyRoutes // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param payload body service.ProxyRouteInput true "Proxy route payload" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -72,7 +72,7 @@ func CreateProxyRoute(c *gin.Context) { // @Tags ProxyRoutes // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Route ID" // @Param payload body service.ProxyRouteInput true "Proxy route payload" // @Success 200 {object} map[string]interface{} @@ -99,7 +99,7 @@ func UpdateProxyRoute(c *gin.Context) { // @Summary Delete proxy route // @Tags ProxyRoutes // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Route ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} diff --git a/openflare_server/controller/tls_certificate.go b/openflare_server/controller/tls_certificate.go index 8031fe5e..e6aec555 100644 --- a/openflare_server/controller/tls_certificate.go +++ b/openflare_server/controller/tls_certificate.go @@ -10,7 +10,7 @@ import ( // @Summary List TLS certificates // @Tags TLSCertificates // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/tls-certificates/ [get] func GetTLSCertificates(c *gin.Context) { @@ -26,7 +26,7 @@ func GetTLSCertificates(c *gin.Context) { // @Summary Get TLS certificate detail // @Tags TLSCertificates // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Certificate ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -49,7 +49,7 @@ func GetTLSCertificate(c *gin.Context) { // @Summary Get TLS certificate PEM content // @Tags TLSCertificates // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Certificate ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -73,7 +73,7 @@ func GetTLSCertificateContent(c *gin.Context) { // @Tags TLSCertificates // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param payload body service.TLSCertificateInput true "TLS certificate payload" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -96,7 +96,7 @@ func CreateTLSCertificate(c *gin.Context) { // @Tags TLSCertificates // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Certificate ID" // @Param payload body service.TLSCertificateInput true "TLS certificate payload" // @Success 200 {object} map[string]interface{} @@ -126,7 +126,7 @@ func UpdateTLSCertificate(c *gin.Context) { // @Tags TLSCertificates // @Accept multipart/form-data // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param name formData string true "Certificate name" // @Param remark formData string false "Remark" // @Param cert_file formData file true "Certificate file" @@ -159,7 +159,7 @@ func ImportTLSCertificateFile(c *gin.Context) { // @Summary Delete TLS certificate // @Tags TLSCertificates // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Certificate ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -181,7 +181,7 @@ func DeleteTLSCertificate(c *gin.Context) { // @Tags TLSCertificates // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param payload body service.TLSApplyInput true "TLS apply payload" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} @@ -204,7 +204,7 @@ func ApplyTLSCertificate(c *gin.Context) { // @Tags TLSCertificates // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Certificate ID" // @Param payload body service.TLSApplyInput true "TLS apply payload" // @Success 200 {object} map[string]interface{} @@ -233,7 +233,7 @@ func UpdateAcmeCertificate(c *gin.Context) { // @Tags TLSCertificates // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Certificate ID" // @Param payload body service.TLSApplyInput true "TLS apply payload" // @Success 200 {object} map[string]interface{} @@ -261,7 +261,7 @@ func ConvertTLSCertificateToAcme(c *gin.Context) { // @Summary Renew TLS certificate // @Tags TLSCertificates // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Param id path int true "Certificate ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]interface{} diff --git a/openflare_server/controller/update.go b/openflare_server/controller/update.go index 55bcaf51..1ec26c69 100644 --- a/openflare_server/controller/update.go +++ b/openflare_server/controller/update.go @@ -21,7 +21,7 @@ type serverUpgradeRequest struct { // @Summary Get latest GitHub release // @Tags Update // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/update/latest-release [get] func GetLatestRelease(c *gin.Context) { diff --git a/openflare_server/controller/uptimekuma.go b/openflare_server/controller/uptimekuma.go index c3b1cef4..ed3aa858 100644 --- a/openflare_server/controller/uptimekuma.go +++ b/openflare_server/controller/uptimekuma.go @@ -11,7 +11,7 @@ import ( // @Tags UptimeKuma // @Accept json // @Produce json -// @Security BearerAuth +// @Security OpenFlareTokenAuth // @Success 200 {object} map[string]interface{} // @Router /api/uptimekuma/sync [post] func SyncUptimeKuma(c *gin.Context) { diff --git a/openflare_server/controller/user.go b/openflare_server/controller/user.go index f2aa927f..e80df2ee 100644 --- a/openflare_server/controller/user.go +++ b/openflare_server/controller/user.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "openflare/common" "openflare/model" "openflare/utils/security" @@ -8,7 +9,6 @@ import ( "strconv" "strings" - "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/google/uuid" ) @@ -45,14 +45,9 @@ func Login(c *gin.Context) { setupLogin(&user, c) } -// setup session & cookies and then return user info -func setLoginSession(user *model.User, c *gin.Context) (*model.User, error) { - session := sessions.Default(c) - session.Set("id", user.Id) - session.Set("username", user.Username) - session.Set("role", user.Role) - session.Set("status", user.Status) - err := session.Save() +// setup token and then return user info +func setLoginToken(user *model.User) (*model.User, error) { + token, err := ensureUserOpenFlareToken(user) if err != nil { return nil, err } @@ -62,12 +57,13 @@ func setLoginSession(user *model.User, c *gin.Context) (*model.User, error) { DisplayName: user.DisplayName, Role: user.Role, Status: user.Status, + Token: token, } return cleanUser, nil } func setupLogin(user *model.User, c *gin.Context) { - cleanUser, err := setLoginSession(user, c) + cleanUser, err := setLoginToken(user) if err != nil { respondFailure(c, "无法保存会话信息,请重试") return @@ -76,16 +72,45 @@ func setupLogin(user *model.User, c *gin.Context) { } func Logout(c *gin.Context) { - session := sessions.Default(c) - session.Clear() - err := session.Save() - if err != nil { - respondFailure(c, err.Error()) - return + token := c.GetHeader("OPENFLARE_TOKEN") + if token != "" { + user := model.ValidateUserToken(token) + if user != nil && user.Id != 0 { + if err := model.DB.Model(user).Update("token", "").Error; err != nil { + respondFailure(c, err.Error()) + return + } + } } respondSuccessMessage(c, "") } +func ensureUserOpenFlareToken(user *model.User) (string, error) { + if user.Token != "" { + return user.Token, nil + } + for i := 0; i < 3; i++ { + token := strings.Replace(uuid.New().String(), "-", "", -1) + if model.DB.Where("token = ?", token).First(&model.User{}).RowsAffected != 0 { + continue + } + if err := model.DB.Model(user).Update("token", token).Error; err != nil { + return "", err + } + user.Token = token + return token, nil + } + return "", errors.New("生成登录凭证失败,请重试") +} + +func currentUserFromOpenFlareToken(c *gin.Context) *model.User { + token := c.GetHeader("OPENFLARE_TOKEN") + if token == "" { + return nil + } + return model.ValidateUserToken(token) +} + func Register(c *gin.Context) { respondFailure(c, "非法请求") } diff --git a/openflare_server/docs/docs.go b/openflare_server/docs/docs.go index bb97a66d..8b3cb268 100644 --- a/openflare_server/docs/docs.go +++ b/openflare_server/docs/docs.go @@ -19,7 +19,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -94,7 +94,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -122,7 +122,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -203,7 +203,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -292,7 +292,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -361,7 +361,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -419,7 +419,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -667,7 +667,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -700,7 +700,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -728,7 +728,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -753,7 +753,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -778,7 +778,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -821,7 +821,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -846,7 +846,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -871,7 +871,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -896,7 +896,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -937,7 +937,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -978,7 +978,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1010,7 +1010,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1033,7 +1033,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1072,7 +1072,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1106,7 +1106,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1276,7 +1276,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1299,7 +1299,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1345,7 +1345,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1379,7 +1379,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1420,7 +1420,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1473,7 +1473,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1496,7 +1496,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1542,7 +1542,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1567,7 +1567,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1592,7 +1592,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1639,7 +1639,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1680,7 +1680,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1721,7 +1721,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1762,7 +1762,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1815,7 +1815,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1856,7 +1856,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1897,7 +1897,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1970,7 +1970,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2121,7 +2121,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2144,7 +2144,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2190,7 +2190,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2231,7 +2231,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2272,7 +2272,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2405,7 +2405,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2428,7 +2428,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2474,7 +2474,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2520,7 +2520,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2584,7 +2584,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2625,7 +2625,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2666,7 +2666,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2719,7 +2719,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2760,7 +2760,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2801,7 +2801,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2854,7 +2854,7 @@ const docTemplate = `{ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2907,7 +2907,7 @@ const docTemplate = `{ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -3761,10 +3761,10 @@ const docTemplate = `{ "name": "X-Agent-Token", "in": "header" }, - "BearerAuth": { - "description": "管理端可使用 Bearer Token,例如:Bearer \u003ctoken\u003e", + "OpenFlareTokenAuth": { + "description": "管理端 API 使用登录后返回的用户 Token", "type": "apiKey", - "name": "Authorization", + "name": "OPENFLARE_TOKEN", "in": "header" } } diff --git a/openflare_server/docs/swagger.json b/openflare_server/docs/swagger.json index ddc618fd..1f914f3c 100644 --- a/openflare_server/docs/swagger.json +++ b/openflare_server/docs/swagger.json @@ -16,7 +16,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -91,7 +91,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -119,7 +119,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -200,7 +200,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -289,7 +289,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -358,7 +358,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -416,7 +416,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -664,7 +664,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -697,7 +697,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -725,7 +725,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -750,7 +750,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -775,7 +775,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -818,7 +818,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -843,7 +843,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -868,7 +868,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -893,7 +893,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -934,7 +934,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -975,7 +975,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1007,7 +1007,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1030,7 +1030,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1069,7 +1069,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1103,7 +1103,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1273,7 +1273,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1296,7 +1296,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1342,7 +1342,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1376,7 +1376,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1417,7 +1417,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1470,7 +1470,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1493,7 +1493,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1539,7 +1539,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1564,7 +1564,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1589,7 +1589,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1636,7 +1636,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1677,7 +1677,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1718,7 +1718,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1759,7 +1759,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1812,7 +1812,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1853,7 +1853,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -1894,7 +1894,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -1967,7 +1967,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2118,7 +2118,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2141,7 +2141,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2187,7 +2187,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2228,7 +2228,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2269,7 +2269,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2402,7 +2402,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2425,7 +2425,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2471,7 +2471,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2517,7 +2517,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2581,7 +2581,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2622,7 +2622,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2663,7 +2663,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2716,7 +2716,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2757,7 +2757,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -2798,7 +2798,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2851,7 +2851,7 @@ "post": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "consumes": [ @@ -2904,7 +2904,7 @@ "get": { "security": [ { - "BearerAuth": [] + "OpenFlareTokenAuth": [] } ], "produces": [ @@ -3758,10 +3758,10 @@ "name": "X-Agent-Token", "in": "header" }, - "BearerAuth": { - "description": "管理端可使用 Bearer Token,例如:Bearer \u003ctoken\u003e", + "OpenFlareTokenAuth": { + "description": "管理端 API 使用登录后返回的用户 Token", "type": "apiKey", - "name": "Authorization", + "name": "OPENFLARE_TOKEN", "in": "header" } } diff --git a/openflare_server/docs/swagger.yaml b/openflare_server/docs/swagger.yaml index 15269372..02a91b39 100644 --- a/openflare_server/docs/swagger.yaml +++ b/openflare_server/docs/swagger.yaml @@ -541,7 +541,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List access logs tags: - AccessLogs @@ -558,7 +558,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Cleanup access logs by retention days tags: - AccessLogs @@ -610,7 +610,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List folded access logs tags: - AccessLogs @@ -668,7 +668,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List folded access log IP summaries tags: - AccessLogs @@ -712,7 +712,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List access log IP summaries tags: - AccessLogs @@ -749,7 +749,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get access log IP trend tags: - AccessLogs @@ -764,7 +764,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get default ACME account tags: - AcmeAccounts @@ -923,7 +923,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List apply logs tags: - ApplyLogs @@ -940,7 +940,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Cleanup apply logs tags: - ApplyLogs @@ -955,7 +955,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List config versions tags: - ConfigVersions @@ -981,7 +981,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get config version detail tags: - ConfigVersions @@ -1007,7 +1007,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Activate an existing config version tags: - ConfigVersions @@ -1022,7 +1022,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get active config version tags: - ConfigVersions @@ -1049,7 +1049,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Cleanup old config versions tags: - ConfigVersions @@ -1064,7 +1064,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Diff current draft against active version tags: - ConfigVersions @@ -1079,7 +1079,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Preview config rendering tags: - ConfigVersions @@ -1094,7 +1094,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Publish a new config version tags: - ConfigVersions @@ -1114,7 +1114,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get dashboard overview tags: - Dashboard @@ -1129,7 +1129,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List DNS accounts tags: - DnsAccounts @@ -1152,7 +1152,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Create DNS account tags: - DnsAccounts @@ -1173,7 +1173,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Delete DNS account tags: - DnsAccounts @@ -1202,7 +1202,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Update DNS account tags: - DnsAccounts @@ -1293,7 +1293,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List managed domains tags: - ManagedDomains @@ -1321,7 +1321,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Create managed domain tags: - ManagedDomains @@ -1347,7 +1347,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Delete managed domain tags: - ManagedDomains @@ -1381,7 +1381,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Update managed domain tags: - ManagedDomains @@ -1402,7 +1402,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Match certificate for domain tags: - ManagedDomains @@ -1417,7 +1417,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List nodes tags: - Nodes @@ -1445,7 +1445,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Create node tags: - Nodes @@ -1475,7 +1475,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Check latest agent release for node tags: - Nodes @@ -1501,7 +1501,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Request agent self-update on node tags: - Nodes @@ -1527,7 +1527,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Delete node tags: - Nodes @@ -1553,7 +1553,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Request force sync config on node tags: - Nodes @@ -1587,7 +1587,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get node observability details tags: - Nodes @@ -1613,7 +1613,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Cleanup node health events tags: - Nodes @@ -1639,7 +1639,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Request openresty restart on node tags: - Nodes @@ -1673,7 +1673,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Update node tags: - Nodes @@ -1688,7 +1688,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get global discovery token tags: - Nodes @@ -1703,7 +1703,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Rotate global discovery token tags: - Nodes @@ -1733,7 +1733,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Cleanup observability tables tags: - Options @@ -1829,7 +1829,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List proxy routes tags: - ProxyRoutes @@ -1857,7 +1857,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Create proxy route tags: - ProxyRoutes @@ -1883,7 +1883,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get proxy route detail tags: - ProxyRoutes @@ -1909,7 +1909,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Delete proxy route tags: - ProxyRoutes @@ -1943,7 +1943,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Update proxy route tags: - ProxyRoutes @@ -2008,7 +2008,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: List TLS certificates tags: - TLSCertificates @@ -2036,7 +2036,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Create TLS certificate from PEM tags: - TLSCertificates @@ -2062,7 +2062,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get TLS certificate detail tags: - TLSCertificates @@ -2088,7 +2088,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get TLS certificate PEM content tags: - TLSCertificates @@ -2122,7 +2122,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Convert uploaded TLS certificate to ACME managed certificate tags: - TLSCertificates @@ -2148,7 +2148,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Delete TLS certificate tags: - TLSCertificates @@ -2174,7 +2174,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Renew TLS certificate tags: - TLSCertificates @@ -2208,7 +2208,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Update TLS certificate from PEM tags: - TLSCertificates @@ -2242,7 +2242,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Update ACME TLS certificate tags: - TLSCertificates @@ -2271,7 +2271,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Apply TLS certificate via ACME tags: - TLSCertificates @@ -2313,7 +2313,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Import TLS certificate from files tags: - TLSCertificates @@ -2328,7 +2328,7 @@ paths: additionalProperties: true type: object security: - - BearerAuth: [] + - OpenFlareTokenAuth: [] summary: Get latest GitHub release tags: - Update @@ -2390,9 +2390,9 @@ securityDefinitions: in: header name: X-Agent-Token type: apiKey - BearerAuth: - description: 管理端可使用 Bearer Token,例如:Bearer + OpenFlareTokenAuth: + description: 管理端 API 使用登录后返回的用户 Token in: header - name: Authorization + name: OPENFLARE_TOKEN type: apiKey swagger: "2.0" diff --git a/openflare_server/main.go b/openflare_server/main.go index 7bff9b25..fe0f6dff 100644 --- a/openflare_server/main.go +++ b/openflare_server/main.go @@ -33,10 +33,10 @@ var indexPage []byte // @description OpenFlare Server 管理端与 Agent API 文档。 // @BasePath / // @schemes http https -// @securityDefinitions.apikey BearerAuth +// @securityDefinitions.apikey OpenFlareTokenAuth // @in header -// @name Authorization -// @description 管理端可使用 Bearer Token,例如:Bearer +// @name OPENFLARE_TOKEN +// @description 管理端 API 使用登录后返回的用户 Token // @securityDefinitions.apikey AccessTokenAuth // @in header // @name X-Agent-Token diff --git a/openflare_server/middleware/auth.go b/openflare_server/middleware/auth.go index 4c7debc6..1c05a37c 100644 --- a/openflare_server/middleware/auth.go +++ b/openflare_server/middleware/auth.go @@ -1,49 +1,35 @@ package middleware import ( - "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "net/http" "openflare/common" "openflare/model" ) +const OpenFlareTokenHeader = "OPENFLARE_TOKEN" + func authHelper(c *gin.Context, minRole int) { - session := sessions.Default(c) - username := session.Get("username") - role := session.Get("role") - id := session.Get("id") - status := session.Get("status") - authByToken := false - if username == nil { - // Check token - token := c.Request.Header.Get("Authorization") - if token == "" { - c.JSON(http.StatusUnauthorized, gin.H{ - "success": false, - "message": "无权进行此操作,未登录或 token 无效", - }) - c.Abort() - return - } - user := model.ValidateUserToken(token) - if user != nil && user.Username != "" { - // Token is valid - username = user.Username - role = user.Role - id = user.Id - status = user.Status - } else { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无权进行此操作,token 无效", - }) - c.Abort() - return - } - authByToken = true + token := c.GetHeader(OpenFlareTokenHeader) + if token == "" { + c.JSON(http.StatusUnauthorized, gin.H{ + "success": false, + "message": "无权进行此操作,未登录或 token 无效", + }) + c.Abort() + return } - if status.(int) == common.UserStatusDisabled { + + user := model.ValidateUserToken(token) + if user == nil || user.Username == "" { + c.JSON(http.StatusUnauthorized, gin.H{ + "success": false, + "message": "无权进行此操作,token 无效", + }) + c.Abort() + return + } + if user.Status == common.UserStatusDisabled { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "用户已被封禁", @@ -51,7 +37,7 @@ func authHelper(c *gin.Context, minRole int) { c.Abort() return } - if role.(int) < minRole { + if user.Role < minRole { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无权进行此操作,权限不足", @@ -59,10 +45,10 @@ func authHelper(c *gin.Context, minRole int) { c.Abort() return } - c.Set("username", username) - c.Set("role", role) - c.Set("id", id) - c.Set("authByToken", authByToken) + c.Set("username", user.Username) + c.Set("role", user.Role) + c.Set("id", user.Id) + c.Set("authByToken", true) c.Next() } @@ -84,34 +70,16 @@ func RootAuth() func(c *gin.Context) { } } -// NoTokenAuth You should always use this after normal auth middlewares. +// NoTokenAuth is kept as a compatibility no-op because admin APIs now always use OPENFLARE_TOKEN. func NoTokenAuth() func(c *gin.Context) { return func(c *gin.Context) { - authByToken := c.GetBool("authByToken") - if authByToken { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "本接口不支持使用 token 进行验证", - }) - c.Abort() - return - } c.Next() } } -// TokenOnlyAuth You should always use this after normal auth middlewares. +// TokenOnlyAuth is kept as a compatibility no-op because admin APIs now always use OPENFLARE_TOKEN. func TokenOnlyAuth() func(c *gin.Context) { return func(c *gin.Context) { - authByToken := c.GetBool("authByToken") - if !authByToken { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "本接口仅支持使用 token 进行验证", - }) - c.Abort() - return - } c.Next() } } diff --git a/openflare_server/middleware/cors.go b/openflare_server/middleware/cors.go index 9933d552..eebfa2b8 100644 --- a/openflare_server/middleware/cors.go +++ b/openflare_server/middleware/cors.go @@ -11,7 +11,7 @@ import ( func CORS() gin.HandlerFunc { config := cors.DefaultConfig() config.AllowCredentials = true - config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "X-Agent-Token", "Accept"} + config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "OPENFLARE_TOKEN", "X-Agent-Token", "Accept"} config.AllowOriginFunc = func(origin string) bool { serverAddr := strings.TrimRight(common.ServerAddress, "/") if serverAddr == "" { diff --git a/openflare_server/router/api_phase1_test.go b/openflare_server/router/api_phase1_test.go index cf979daf..86e32bf2 100644 --- a/openflare_server/router/api_phase1_test.go +++ b/openflare_server/router/api_phase1_test.go @@ -95,7 +95,7 @@ func TestPhase1PublishLifecycle(t *testing.T) { } repeatPublishReq := httptest.NewRequest(http.MethodPost, "/api/config-versions/publish", nil) - repeatPublishReq.Header.Set("Authorization", "Bearer "+token) + repeatPublishReq.Header.Set("OPENFLARE_TOKEN", token) repeatPublishRecorder := httptest.NewRecorder() engine.ServeHTTP(repeatPublishRecorder, repeatPublishReq) if repeatPublishRecorder.Code != http.StatusOK { @@ -519,7 +519,7 @@ func performJSONRequest(t *testing.T, engine http.Handler, token string, method if body != nil { req.Header.Set("Content-Type", "application/json") } - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("OPENFLARE_TOKEN", token) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { @@ -549,7 +549,7 @@ func performJSONRequestNoFatal(t *testing.T, engine http.Handler, token string, if body != nil { req.Header.Set("Content-Type", "application/json") } - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("OPENFLARE_TOKEN", token) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK && recorder.Code != http.StatusBadRequest { @@ -596,7 +596,7 @@ func performMultipartRequest(t *testing.T, engine http.Handler, token string, pa } req := httptest.NewRequest(http.MethodPost, path, &body) req.Header.Set("Content-Type", writer.FormDataContentType()) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("OPENFLARE_TOKEN", token) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { diff --git a/openflare_server/router/api_phase2_test.go b/openflare_server/router/api_phase2_test.go index 4ddbe147..3cda6631 100644 --- a/openflare_server/router/api_phase2_test.go +++ b/openflare_server/router/api_phase2_test.go @@ -127,7 +127,7 @@ func TestPhase2BatchOptionUpdateIsAtomic(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/api/option/update-batch", bytes.NewReader(payload)) req.Header.Set("Content-Type", "application/json") - req.AddCookie(loginCookie) + req.Header.Set("OPENFLARE_TOKEN", loginCookie) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) @@ -320,7 +320,7 @@ func TestExternalAccountBindingsCanBeListedAndDeleted(t *testing.T) { } } -func loginAsRoot(t *testing.T, engine http.Handler) *http.Cookie { +func loginAsRoot(t *testing.T, engine http.Handler) string { t.Helper() payload, err := json.Marshal(map[string]any{ "username": "root", @@ -346,16 +346,17 @@ func loginAsRoot(t *testing.T, engine http.Handler) *http.Cookie { t.Fatalf("root login failed: %s", resp.Message) } - for _, cookie := range recorder.Result().Cookies() { - if cookie.Name == "session" { - return cookie - } + var user model.User + if err = json.Unmarshal(resp.Data, &user); err != nil { + t.Fatalf("failed to decode login user: %v", err) } - t.Fatal("expected session cookie after root login") - return nil + if user.Token == "" { + t.Fatal("expected OPENFLARE_TOKEN after root login") + } + return user.Token } -func performSessionJSONRequest(t *testing.T, engine http.Handler, sessionCookie *http.Cookie, method string, path string, body any) apiResponse { +func performSessionJSONRequest(t *testing.T, engine http.Handler, token string, method string, path string, body any) apiResponse { t.Helper() var payload []byte var err error @@ -370,7 +371,7 @@ func performSessionJSONRequest(t *testing.T, engine http.Handler, sessionCookie if body != nil { req.Header.Set("Content-Type", "application/json") } - req.AddCookie(sessionCookie) + req.Header.Set("OPENFLARE_TOKEN", token) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) diff --git a/openflare_server/router/api_uptimekuma_test.go b/openflare_server/router/api_uptimekuma_test.go index 4af20b64..8998a9a0 100644 --- a/openflare_server/router/api_uptimekuma_test.go +++ b/openflare_server/router/api_uptimekuma_test.go @@ -133,7 +133,7 @@ func TestUptimeKumaSyncDisabled(t *testing.T) { // Request sync, should fail req := httptest.NewRequest(http.MethodPost, "/api/uptimekuma/sync", nil) - req.AddCookie(loginCookie) + req.Header.Set("OPENFLARE_TOKEN", loginCookie) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) @@ -265,7 +265,7 @@ func TestUptimeKumaSyncSuccess(t *testing.T) { loginCookie := loginAsRoot(t, engine) req := httptest.NewRequest(http.MethodPost, "/api/uptimekuma/sync", nil) - req.AddCookie(loginCookie) + req.Header.Set("OPENFLARE_TOKEN", loginCookie) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) @@ -405,7 +405,7 @@ func TestUptimeKumaSyncSelectedScope(t *testing.T) { loginCookie := loginAsRoot(t, engine) req := httptest.NewRequest(http.MethodPost, "/api/uptimekuma/sync", nil) - req.AddCookie(loginCookie) + req.Header.Set("OPENFLARE_TOKEN", loginCookie) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) diff --git a/openflare_server/router/update_test.go b/openflare_server/router/update_test.go index ea05f83c..bff297e2 100644 --- a/openflare_server/router/update_test.go +++ b/openflare_server/router/update_test.go @@ -77,13 +77,22 @@ func TestLatestReleaseProxy(t *testing.T) { if loginRecorder.Code != http.StatusOK { t.Fatalf("unexpected login status code: %d", loginRecorder.Code) } - loginResult := loginRecorder.Result() - defer loginResult.Body.Close() + var loginResp apiResponse + if err = json.Unmarshal(loginRecorder.Body.Bytes(), &loginResp); err != nil { + t.Fatalf("failed to decode login response: %v", err) + } + var loginUser struct { + Token string `json:"token"` + } + if err = json.Unmarshal(loginResp.Data, &loginUser); err != nil { + t.Fatalf("failed to decode login user: %v", err) + } + if loginUser.Token == "" { + t.Fatal("expected OPENFLARE_TOKEN after login") + } req := httptest.NewRequest(http.MethodGet, "/api/update/latest-release", nil) - for _, cookieValue := range loginResult.Cookies() { - req.AddCookie(cookieValue) - } + req.Header.Set("OPENFLARE_TOKEN", loginUser.Token) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) @@ -111,7 +120,7 @@ func TestLatestReleaseProxy(t *testing.T) { } } -func loginRootAndBuildEngine(t *testing.T) (*gin.Engine, []*http.Cookie) { +func loginRootAndBuildEngine(t *testing.T) (*gin.Engine, string) { t.Helper() gin.SetMode(gin.TestMode) common.RedisEnabled = false @@ -135,10 +144,21 @@ func loginRootAndBuildEngine(t *testing.T) (*gin.Engine, []*http.Cookie) { if loginRecorder.Code != http.StatusOK { t.Fatalf("unexpected login status code: %d", loginRecorder.Code) } - loginResult := loginRecorder.Result() - defer loginResult.Body.Close() + var loginResp apiResponse + if err = json.Unmarshal(loginRecorder.Body.Bytes(), &loginResp); err != nil { + t.Fatalf("failed to decode login response: %v", err) + } + var loginUser struct { + Token string `json:"token"` + } + if err = json.Unmarshal(loginResp.Data, &loginUser); err != nil { + t.Fatalf("failed to decode login user: %v", err) + } + if loginUser.Token == "" { + t.Fatal("expected OPENFLARE_TOKEN after login") + } - return engine, loginResult.Cookies() + return engine, loginUser.Token } func fakeManualServerBinary(version string) (string, []byte) { @@ -157,7 +177,7 @@ func TestManualUploadRoute(t *testing.T) { service.SetServerUpgradeDispatchDelayForTest(500 * time.Millisecond) }) - engine, cookies := loginRootAndBuildEngine(t) + engine, token := loginRootAndBuildEngine(t) fileName, content := fakeManualServerBinary("v0.5.0") body := &bytes.Buffer{} @@ -175,9 +195,7 @@ func TestManualUploadRoute(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/api/update/manual-upload", body) req.Header.Set("Content-Type", writer.FormDataContentType()) - for _, cookieValue := range cookies { - req.AddCookie(cookieValue) - } + req.Header.Set("OPENFLARE_TOKEN", token) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) @@ -225,7 +243,7 @@ func TestManualUpgradeConfirmRoute(t *testing.T) { service.SetServerUpgradeDispatchDelayForTest(originalDelay) }) - engine, cookies := loginRootAndBuildEngine(t) + engine, token := loginRootAndBuildEngine(t) fileName, content := fakeManualServerBinary("v0.5.0") body := &bytes.Buffer{} @@ -243,9 +261,7 @@ func TestManualUpgradeConfirmRoute(t *testing.T) { uploadReq := httptest.NewRequest(http.MethodPost, "/api/update/manual-upload", body) uploadReq.Header.Set("Content-Type", writer.FormDataContentType()) - for _, cookieValue := range cookies { - uploadReq.AddCookie(cookieValue) - } + uploadReq.Header.Set("OPENFLARE_TOKEN", token) uploadRecorder := httptest.NewRecorder() engine.ServeHTTP(uploadRecorder, uploadReq) @@ -276,9 +292,7 @@ func TestManualUpgradeConfirmRoute(t *testing.T) { } confirmReq := httptest.NewRequest(http.MethodPost, "/api/update/manual-upgrade", bytes.NewReader(confirmBody)) confirmReq.Header.Set("Content-Type", "application/json") - for _, cookieValue := range cookies { - confirmReq.AddCookie(cookieValue) - } + confirmReq.Header.Set("OPENFLARE_TOKEN", token) confirmRecorder := httptest.NewRecorder() engine.ServeHTTP(confirmRecorder, confirmReq) diff --git a/openflare_server/web/components/providers/auth-provider.tsx b/openflare_server/web/components/providers/auth-provider.tsx index 80ebcfe4..5a007156 100644 --- a/openflare_server/web/components/providers/auth-provider.tsx +++ b/openflare_server/web/components/providers/auth-provider.tsx @@ -14,6 +14,7 @@ import { logout as logoutRequest, getCurrentUser, } from '@/features/auth/api/auth'; +import { clearStoredOpenFlareToken } from '@/lib/api/auth-token'; import type { AuthUser } from '@/types/auth'; interface AuthContextValue { @@ -41,6 +42,7 @@ export function AuthProvider({ children }: AuthProviderProps) { setUserState(nextUser); return nextUser; } catch { + clearStoredOpenFlareToken(); setUserState(null); return null; } finally { diff --git a/openflare_server/web/features/auth/api/auth.ts b/openflare_server/web/features/auth/api/auth.ts index 4cd37c5a..648aae58 100644 --- a/openflare_server/web/features/auth/api/auth.ts +++ b/openflare_server/web/features/auth/api/auth.ts @@ -1,4 +1,8 @@ import { apiRequest } from '@/lib/api/client'; +import { + clearStoredOpenFlareToken, + setStoredOpenFlareToken, +} from '@/lib/api/auth-token'; import type { AuthUser, LoginPayload, @@ -14,11 +18,18 @@ export function login(payload: LoginPayload) { return apiRequest('/user/login', { method: 'POST', body: JSON.stringify(payload), + }).then((user) => { + if (user.token) { + setStoredOpenFlareToken(user.token); + } + return user; }); } export function logout() { - return apiRequest('/user/logout'); + return apiRequest('/user/logout').finally(() => { + clearStoredOpenFlareToken(); + }); } export function register(payload: RegisterPayload) { @@ -48,7 +59,14 @@ export function resetPassword(payload: PasswordResetRequestPayload) { } export function exchangeGitHubCode(code: string) { - return apiRequest(`/oauth/github?code=${encodeURIComponent(code)}`); + return apiRequest( + `/oauth/github?code=${encodeURIComponent(code)}`, + ).then((user) => { + if (user.token) { + setStoredOpenFlareToken(user.token); + } + return user; + }); } export interface OAuthAuthorizeResult { @@ -79,12 +97,22 @@ export function exchangeOAuthCode( const searchParams = new URLSearchParams({ code, state }); return apiRequest( `/oauth/${encodeURIComponent(String(source))}/callback?${searchParams.toString()}`, - ); + ).then((result) => { + if (result.user?.token) { + setStoredOpenFlareToken(result.user.token); + } + return result; + }); } export function linkExistingOAuthAccount(payload: LinkExistingOAuthPayload) { return apiRequest('/oauth/link-existing', { method: 'POST', body: JSON.stringify(payload), + }).then((result) => { + if (result.user?.token) { + setStoredOpenFlareToken(result.user.token); + } + return result; }); } diff --git a/openflare_server/web/features/settings/api/settings.ts b/openflare_server/web/features/settings/api/settings.ts index d6a0492f..3474b3d7 100644 --- a/openflare_server/web/features/settings/api/settings.ts +++ b/openflare_server/web/features/settings/api/settings.ts @@ -1,4 +1,5 @@ import { apiRequest } from '@/lib/api/client'; +import { setStoredOpenFlareToken } from '@/lib/api/auth-token'; import type { BootstrapTokenPayload, @@ -109,7 +110,10 @@ export function updateSelf(payload: UpdateSelfPayload) { } export function generateAccessToken() { - return apiRequest('/user/token'); + return apiRequest('/user/token').then((token) => { + setStoredOpenFlareToken(token); + return token; + }); } export function bindWeChat(code: string) { diff --git a/openflare_server/web/lib/api/auth-token.ts b/openflare_server/web/lib/api/auth-token.ts new file mode 100644 index 00000000..47ad33ba --- /dev/null +++ b/openflare_server/web/lib/api/auth-token.ts @@ -0,0 +1,22 @@ +const openFlareTokenStorageKey = 'openflare_token'; + +export function getStoredOpenFlareToken() { + if (typeof window === 'undefined') { + return ''; + } + return window.localStorage.getItem(openFlareTokenStorageKey) || ''; +} + +export function setStoredOpenFlareToken(token: string) { + if (typeof window === 'undefined') { + return; + } + window.localStorage.setItem(openFlareTokenStorageKey, token); +} + +export function clearStoredOpenFlareToken() { + if (typeof window === 'undefined') { + return; + } + window.localStorage.removeItem(openFlareTokenStorageKey); +} diff --git a/openflare_server/web/lib/api/client.ts b/openflare_server/web/lib/api/client.ts index a1a964c2..2848f025 100644 --- a/openflare_server/web/lib/api/client.ts +++ b/openflare_server/web/lib/api/client.ts @@ -1,4 +1,5 @@ import { publicEnv } from '@/lib/env/public-env'; +import { getStoredOpenFlareToken } from '@/lib/api/auth-token'; import type { ApiEnvelope } from '@/types/api'; export class ApiError extends Error { @@ -23,6 +24,12 @@ export async function apiRequest(path: string, init?: RequestInit) { if (!(init?.body instanceof FormData) && !headers.has('Content-Type')) { headers.set('Content-Type', 'application/json'); } + if (!headers.has('OPENFLARE_TOKEN')) { + const token = getStoredOpenFlareToken(); + if (token) { + headers.set('OPENFLARE_TOKEN', token); + } + } const response = await fetch(getApiUrl(path), { credentials: 'include', diff --git a/openflare_server/web/types/auth.ts b/openflare_server/web/types/auth.ts index 076b64c7..47a4a997 100644 --- a/openflare_server/web/types/auth.ts +++ b/openflare_server/web/types/auth.ts @@ -4,6 +4,7 @@ export interface AuthUser { display_name: string; role: number; status: number; + token?: string; email?: string; github_id?: string; wechat_id?: string;