fix(server): backport two prod hot-patches that kept getting wiped

1. /api/heicode-auth/* proxy: CriticalRateLimit (20/20min) → GlobalAPIRateLimit
   (180/180s). The Heicode external-identity proxy is hit on every page
   render for /me + /refresh plus the login burst — CriticalRateLimit is
   sized for sensitive ops (password reset, 2FA) and trips at ~5 quick
   page loads, returning 429 to a normal user. APIM upstream rate-limits
   itself, so a second tight layer here adds no security and just
   manufactures 429s.

2. JIT-create user group: seed "default" instead of me.Data.ChannelID.
   Companion to 578a68f which only patched the every-login overwrite
   path. New users (yj2824269760@gmail.com et al, JIT-created after
   578a68f) still landed in a UUID group → empty /v1/models response →
   desktop client showed the static 3-Claude fallback list.

Both fixes were applied on the production VM directly today (sed +
python patch) — committing them so the next docker rebuild on VM keeps
them instead of reverting to the buggy file via git checkout.

DB hot-fix already applied: 6 affected users moved to group=default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 17:14:35 +08:00
co-authored by Claude Opus 4.7
parent dc33522a84
commit 37de6575df
2 changed files with 13 additions and 5 deletions
+7 -4
View File
@@ -242,10 +242,13 @@ func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
if display == "" { if display == "" {
display = strings.Split(email, "@")[0] display = strings.Split(email, "@")[0]
} }
group := strings.TrimSpace(me.Data.ChannelID) // Local model-access bucket — never seed with the Agnet channelId
if group == "" { // (no abilities row matches a random UUID, so the new user would land
group = "default" // with zero models on first /v1/models call). Admins control group
} // from the NewAPI dashboard after JIT-create. Fix companion to
// 578a68f which only patched the every-login overwrite path.
group := "default"
_ = me.Data.ChannelID
nu := model.User{ nu := model.User{
Username: jitUsernameFromEmail(email), Username: jitUsernameFromEmail(email),
Password: common.GetRandomString(32), Password: common.GetRandomString(32),
+6 -1
View File
@@ -58,7 +58,12 @@ func SetApiRouter(router *gin.Engine) {
// Frontend calls /api/heicode-auth/<path>; controller forwards to // Frontend calls /api/heicode-auth/<path>; controller forwards to
// ${HEICODE_AUTH_BASE_URL}/<path>. Avoids browser CORS preflight failures // ${HEICODE_AUTH_BASE_URL}/<path>. Avoids browser CORS preflight failures
// when APIM has not added code.xinghanlab.com to its allow-list. // when APIM has not added code.xinghanlab.com to its allow-list.
apiRouter.Any("/heicode-auth/*proxyPath", middleware.CriticalRateLimit(), controller.HeicodeAuthProxy) // Use GlobalAPIRateLimit (180 req / 180s) not CriticalRateLimit (20 req
// / 20 min). The proxy is hit on every page render for /me /refresh
// plus the login burst — CriticalRateLimit is for sensitive ops
// (login, password reset) and trips at ~5 quick page loads, returning
// 429 to a normal user. Upstream APIM rate-limits itself anyway.
apiRouter.Any("/heicode-auth/*proxyPath", middleware.GlobalAPIRateLimit(), controller.HeicodeAuthProxy)
userRoute := apiRouter.Group("/user") userRoute := apiRouter.Group("/user")
{ {