Files
sub2api/backend/internal/web/embed_on.go
IanShaw cf8a64528c fix: 修复 Gemini API 认证和 /responses 端点路由问题 (#45)
* fix(middleware): 修复 Gemini API Key 认证中间件用户上下文类型错误

修复了 ApiKeyAuthWithSubscriptionGoogle 中间件中设置用户上下文时的类型错误。

**问题:**
- 中间件直接设置 `apiKey.User` 对象到上下文
- 导致 handler 中获取 `AuthSubject` 时类型断言失败
- 所有 Gemini v1beta 端点返回 500 "User context not found"

**修复:**
- 改为设置 `AuthSubject` 结构体,与 `api_key_auth.go` 保持一致
- 添加 `ContextKeyUserRole` 设置以完整支持角色检查

**影响范围:**
- Gemini v1beta API 端点 (generateContent, streamGenerateContent)
- 使用 Google API Key 认证的所有请求

**测试:**
- 验证 Gemini CLI 调用成功返回 200
- 确认用户上下文正确传递到 handler

* fix(web): 修复 /responses 端点被前端中间件拦截的问题

- 将 /responses 路径添加到 API 白名单,防止其被当作前端路由处理
- 修复 /responses 端点返回 HTML 而非 API 响应的 BUG
- 解决 codex CLI stream 在远程服务器上断开连接的问题

根本原因:
在 6c469b4 提交中添加了 /responses 路由,但未同步更新前端嵌入中间件
的 API 白名单,导致该路由被拦截并返回 index.html 而非 API 响应。
2025-12-27 10:50:15 +08:00

78 lines
1.5 KiB
Go

//go:build embed
package web
import (
"embed"
"io"
"io/fs"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
//go:embed all:dist
var frontendFS embed.FS
func ServeEmbeddedFrontend() gin.HandlerFunc {
distFS, err := fs.Sub(frontendFS, "dist")
if err != nil {
panic("failed to get dist subdirectory: " + err.Error())
}
fileServer := http.FileServer(http.FS(distFS))
return func(c *gin.Context) {
path := c.Request.URL.Path
if strings.HasPrefix(path, "/api/") ||
strings.HasPrefix(path, "/v1/") ||
strings.HasPrefix(path, "/v1beta/") ||
strings.HasPrefix(path, "/setup/") ||
path == "/health" ||
path == "/responses" {
c.Next()
return
}
cleanPath := strings.TrimPrefix(path, "/")
if cleanPath == "" {
cleanPath = "index.html"
}
if file, err := distFS.Open(cleanPath); err == nil {
_ = file.Close()
fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort()
return
}
serveIndexHTML(c, distFS)
}
}
func serveIndexHTML(c *gin.Context, fsys fs.FS) {
file, err := fsys.Open("index.html")
if err != nil {
c.String(http.StatusNotFound, "Frontend not found")
c.Abort()
return
}
defer func() { _ = file.Close() }()
content, err := io.ReadAll(file)
if err != nil {
c.String(http.StatusInternalServerError, "Failed to read index.html")
c.Abort()
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", content)
c.Abort()
}
func HasEmbeddedFrontend() bool {
_, err := frontendFS.ReadFile("dist/index.html")
return err == nil
}