CORS unblock — add /api/heicode-auth/*proxyPath backend proxy to HEICODE_AUTH_BASE_URL. Frontend defaults to same-origin path so the browser never hits APIM directly. Sidebar — replace backend jargon (Git sources / Deployments / Events / Wallet / Available models / Profile) with the user-facing labels docs §10 mandates: 总览 / 准备清单 / 任务总览 / 审计 / 模型与余额 / 客户端 / 账号安全. /sk-sources rewritten as 4-card preparation wizard with progress meter; full Git form moves into a 高级补充 sheet. Drops JSON editor, permission manifest, snapshots and resource-grant pills. /deployments simplified to 任务总览: objective + status + last update. Drops risk / budget / scope / secret_ref pills and the RunDetailPanel; manifest details only in audit/advanced views. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
2.1 KiB
Go
69 lines
2.1 KiB
Go
package controller
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// HeicodeAuthProxy transparently forwards browser calls to the upstream Heicode
|
|
// identity service (APIM). The frontend cannot call APIM directly because that
|
|
// host does not include code.xinghanlab.com in its CORS allow-list, so we
|
|
// terminate the request same-origin and re-emit it server-side.
|
|
//
|
|
// Mounted at /api/heicode-auth/*proxyPath. The trailing path (everything after
|
|
// /api/heicode-auth/) is appended verbatim to HEICODE_AUTH_BASE_URL. Request
|
|
// method, query string, body, and the Authorization header are preserved.
|
|
func HeicodeAuthProxy(c *gin.Context) {
|
|
tail := strings.TrimPrefix(c.Param("proxyPath"), "/")
|
|
if tail == "" {
|
|
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "missing upstream path"})
|
|
return
|
|
}
|
|
|
|
baseURL := defaultHeicodeAuthBaseURL()
|
|
target := baseURL + "/" + tail
|
|
if raw := c.Request.URL.RawQuery; raw != "" {
|
|
target += "?" + raw
|
|
}
|
|
|
|
var body io.Reader
|
|
if c.Request.Body != nil {
|
|
body = c.Request.Body
|
|
}
|
|
req, err := http.NewRequestWithContext(c.Request.Context(), c.Request.Method, target, body)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadGateway, gin.H{"success": false, "message": "upstream request build failed"})
|
|
return
|
|
}
|
|
|
|
// Forward only headers that matter for the upstream call. We intentionally
|
|
// drop Cookie, Host, Origin, Referer so APIM does not see browser context.
|
|
if auth := strings.TrimSpace(c.GetHeader("Authorization")); auth != "" {
|
|
req.Header.Set("Authorization", auth)
|
|
}
|
|
if ct := strings.TrimSpace(c.GetHeader("Content-Type")); ct != "" {
|
|
req.Header.Set("Content-Type", ct)
|
|
}
|
|
if accept := strings.TrimSpace(c.GetHeader("Accept")); accept != "" {
|
|
req.Header.Set("Accept", accept)
|
|
}
|
|
|
|
client := &http.Client{Timeout: 20 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadGateway, gin.H{"success": false, "message": "upstream request failed"})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if ct := resp.Header.Get("Content-Type"); ct != "" {
|
|
c.Writer.Header().Set("Content-Type", ct)
|
|
}
|
|
c.Writer.WriteHeader(resp.StatusCode)
|
|
_, _ = io.Copy(c.Writer, resp.Body)
|
|
}
|