Skip to content

feat: auto region cho API key Kiro + viet hoa giao dien (vi) - #5

Open
dongp06 wants to merge 2 commits into
zsecducna:mainfrom
dongp06:feature/apikey-region-failover-and-vi-i18n
Open

feat: auto region cho API key Kiro + viet hoa giao dien (vi)#5
dongp06 wants to merge 2 commits into
zsecducna:mainfrom
dongp06:feature/apikey-region-failover-and-vi-i18n

Conversation

@dongp06

@dongp06 dongp06 commented Jul 15, 2026

Copy link
Copy Markdown

Tóm tắt / Summary

PR này bổ sung 2 tính năng độc lập:

  1. Tự động dò lại region cho tài khoản API key Kiro khi lỗi xác thực (feat(kiro))
  2. Việt hóa giao diện admin (thêm ngôn ngữ thứ 3: Tiếng Việt) (feat(web))

1. Auto re-detect region cho API key (ksk_)

Vấn đề

Một Kiro API key (ksk_...) bị ràng buộc theo data-plane region nơi nó được cấp. Nếu request gửi tới sai region, upstream trả về:

HTTP 403 from Kiro IDE: {"message":"The bearer token included in the request is invalid.","reason":null}

dù key vẫn hợp lệ ở region khác. Region lưu trong tài khoản có thể sai khi:

  • tài khoản được import mà không qua bước probe region, hoặc
  • key được cấp lại (re-issue) ở region khác.

Trước đây CallKiroAPI gặp 401/403 là trả lỗi ngay, không hề dò lại region cho tài khoản api_key (chỉ dò lúc thêm key mới), nên tài khoản luôn fail — và ở luồng phục vụ thật còn bị ban nhầm.

Giải pháp

Khi tài khoản api_key gặp lỗi xác thực, hệ thống probe key qua các region ứng viên (kiroApiKeyCandidateRegions, bỏ qua region đang lỗi). Region nào chấp nhận key thì lưu lại vào config + cập nhật account in-memory và retry đúng 1 lần ở region đó. Nếu không region nào nhận → giữ nguyên lỗi gốc (key chết thật). Lỗi auth luôn xảy ra trước khi stream bắt đầu nên retry không gây double-emit output.

Thay đổi

  • config/config.go: thêm UpdateAccountRegion để lưu region mới dò được.
  • proxy/kiro_api.go: thêm reprobeApiKeyRegion — dò lại region + persist.
  • proxy/kiro.go: tách vòng lặp endpoint thành callKiroEndpointsOnce; CallKiroAPI bọc nó bằng logic region-failover cho api_key.

Kiểm thử

  • Test mới TestReprobeApiKeyRegion*: dò lại + persist, key chết trả false, bỏ qua tài khoản non-api_key.
  • Toàn bộ suite pass: ok kiro-go/{auth,config,pool,proxy}.

2. Việt hóa giao diện (locale vi)

Thêm locale Tiếng Việt đầy đủ bên cạnh Chinese (mặc định) và English, và làm logic chuyển ngôn ngữ language-agnostic thay vì chỉ zh/en cứng.

  • web/locales/vi.json: bản dịch Tiếng Việt hoàn chỉnh (512 keys).
  • en.json / zh.json: thêm lang.vi, các key uptime.d/h/m/smodels.familyOther (trước đây hardcode trong app.js).
  • index.html: thêm nút VI vào cả 2 language switcher.
  • app.js: toggle ngôn ngữ giờ xoay vòng zh -> en -> vi; formatUptime và nhãn model-family "Other" dùng translation key thay vì check zh/en inline.

Cả 3 locale đã được xác minh chia sẻ cùng bộ 512 keys.


Test plan

  • go build ./...
  • go test ./... — tất cả package pass
  • vi.json hợp lệ JSON, đủ 512 keys khớp với en.json/zh.json

dongp06 added 2 commits July 15, 2026 17:36
A Kiro API key (ksk_) is bound to the data-plane region it was minted in.
A request sent to the wrong region is rejected as an invalid bearer token
(HTTP 403) even though the key is valid elsewhere. The stored region can be
wrong when an account was imported without a region probe or the key was
re-issued in a different region.

On an auth failure for an api_key account, reprobe the key against the
candidate regions and, if another region accepts it, persist that region and
retry the call once. Auth errors are raised before any streaming callback
fires, so the single retry cannot double-emit output to the client.

- config: add UpdateAccountRegion to persist a re-detected region
- kiro: split callKiroEndpointsOnce out of CallKiroAPI and wrap it with the
  api_key region-failover retry
- kiro_api: add reprobeApiKeyRegion using the same probe as the add-key flow
- tests: cover redetect+persist, dead-key, and non-api_key skip paths
Adds a full Vietnamese locale alongside the existing Chinese (default)
and English translations, and makes the language switching logic
language-agnostic instead of a hardcoded zh/en binary.

- web/locales/vi.json: complete 512-key Vietnamese translation
- en.json / zh.json: add lang.vi plus uptime.d/h/m/s and
  models.familyOther keys (previously hardcoded in app.js)
- index.html: add a VI button to both language switchers
- app.js: language toggle now cycles zh -> en -> vi; formatUptime and
  the model-family "Other" label use translation keys instead of an
  inline zh/en check

All three locales verified to share an identical 512-key set.

@zsecducna zsecducna left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cảm ơn PR — hướng giải quyết đúng và bổ sung tốt cho resolveApiKeyRegion trên main (probe lúc add; PR này probe lúc request). Hai đầu ghép lại là đủ. go build, go vet, và toàn bộ 293 test đều pass.

Nhưng có 2 vấn đề chặn merge. Repo chưa có CI (mergeStateStatus: UNSTABLE chỉ nghĩa là không có check nào), nên mình đã verify tay.


🔴 1. Data race trên account.Region (đã reproduce bằng -race)

proxy/kiro_api.go:183account.Region = region ghi không có lock.

Pointer là shared, không phải copy: pool.GetNextForModelWithApiKey trả &p.accounts[i]. Session affinity chủ động ghim 1 customer key vào 1 account, nên request đồng thời trên cùng account là trường hợp thường gặp, không phải corner case.

Reader chạy song song: kiroRegionForProfile (proxy/kiro_api.go:49) — mọi request đều gọi để build endpoint URL. Go string là giá trị multi-word → torn read là undefined behavior.

Test reproduce (bỏ vào proxy/, chạy go test ./proxy/ -run TestRaceRepro -race):

func TestRaceRepro(t *testing.T) {
	mustInitConfig(t)
	orig := probeKiroApiKey
	t.Cleanup(func() { probeKiroApiKey = orig })
	probeKiroApiKey = func(key, region string) (*config.AccountInfo, error) {
		return &config.AccountInfo{Email: "x@example.com", UserId: "u1"}, nil
	}
	acct := &config.Account{
		ID: "race-1", AuthMethod: "api_key",
		KiroApiKey: "ksk_race", AccessToken: "ksk_race", Region: "us-east-1",
	}
	var wg sync.WaitGroup
	wg.Add(2)
	go func() { defer wg.Done(); reprobeApiKeyRegion(acct) }()
	go func() { defer wg.Done(); _ = kiroRegionForProfile(acct, "") }()
	wg.Wait()
}

Kết quả: WARNING: DATA RACE — write tại kiro_api.go:183 vs read tại kiro_api.go:49.

Ngoài ra không có singleflight: N request cùng 403 trên 1 account → N lượt probe, N lần ghi không đồng bộ, N lần Save().

🔴 2. Key multi-region bị nhân đôi slot → quota bị đếm 2 lần

handleAdminAddKiroApiKey coi multi-region là 1 slot cho mỗi region key phục vụ, dedup theo (UserId, region) qua findAccountForKiroIdentity. reprobeApiKeyRegion giả định "chỉ có đúng 1 region đúng" và dời slot, không dedup.

Kịch bản: key phục vụ cả 2 region → có slot A(us-east-1) và B(eu-central-1). B gặp bất kỳ lỗi nào IsAuthFailure phân loại là auth → reprobe bỏ qua eu-central-1, probe us-east-1 (luôn đứng đầu candidate list), thành công → flip B.Region = us-east-1.

Kết quả: 2 account trùng UserId + region → routing weight gấp đôi, quota bị đếm 2 lần trong /admin/pool. Đây đúng là invariant mà dedup lúc add sinh ra để bảo vệ, và không có gì reconcile lại.


🟡 Các điểm nên sửa luôn trong pass này (không chặn merge)

3. Latency trong serving path. Mỗi probe dùng REST client timeout 30s, không cache/backoff/circuit-breaker. Ban xảy ra sau khi probe xong (account_failover.go), và handler loop thử tới maxAccountRetryAttempts=3 account → 1 request của khách có thể tốn tới 3 lượt sweep (~90s mặc định; với KIRO_PROFILE_REGIONS nhiều region thì còn lâu hơn).

4. Claim "cannot double-emit" đúng nhưng không được enforce. Với path HTTP status thì đúng: 401/403/402 return trước parseAndStream. Nhưng cổng retry là pool.IsAuthFailure(err) — một string classifier áp lên mọi error, kể cả error trả về sau khi callback đã bắn (mid-stream read error, errEventStreamFrameTooLarge). Hiện tại an toàn chỉ vì các chuỗi đó tình cờ không chứa token 401/403. Không có gì cấu trúc (vd. flag emitted) giữ invariant này — nếu sau này có error path nào bubble chuỗi trông giống auth sau khi stream, khách sẽ nhận output nhân đôi và bị tính dư output token.

5. ProfileArn làm feature thành no-op. kiroRegionForProfile ưu tiên payload.ProfileArnaccount.ProfileArnaccount.Region. Account api_keyProfileArn khác rỗng (config sửa tay / import path tương lai) → reprobe flip & persist Region, nhưng retry vẫn regionalize theo ARN → vẫn 403 → account bị ban, và config còn lại Region mâu thuẫn với ARN thực dùng. reprobeApiKeyRegion không check/clear ProfileArn.

6. Skip check phân biệt hoa thường. kiro_api.go:173 dùng normalizeRegion(region) == current, trong khi chỗ dedup dùng strings.EqualFold. Account có Region: "US-EAST-1" sẽ probe lại chính region vừa 403 (phí 1 lượt 30s).


✅ Phần locale vi — OK, không có vấn đề

web/locales/vi.json: JSON hợp lệ, đúng 512 key, parity đầy đủ với en.json/zh.json (đã verify bằng script), không có value rỗng. en/zh đều đã nhận 6 key mới. Toggle xử lý đúng khi stored lang lạ (indexOf −1 → wrap về zh). Cả 2 switcher trong index.html đều có nút VI.

Nếu muốn ship sớm, tách phần vi ra PR riêng thì merge được ngay.


Đề xuất: thêm lock/singleflight quanh chỗ ghi region (mục 1) + check dedup trước khi flip region (mục 2), kèm regression test cho cả hai. Mục 3–5 nên gộp luôn: singleflight + guard "chưa emit byte nào" + dedup check giải quyết được phần lớn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants