jim800121chen 22f0837ba8 feat(visionA-backend): Phase 0 → 0.7 雲端後端(雙 binary + OIDC BFF + stage 部署)
從 edge-ai-platform POC 轉為正式產品的雲端後端,含以下整合階段:

- Phase 0:雛形骨架 — `cmd/api-server` (REST :3721) + `cmd/remote-proxy`
  (tunnel :3800 / internal :3801) 雙 binary 共用 internal/,沿用 POC 的
  WebSocket+yamux tunnel 協定但解耦 relay 與 API
- Phase 0.6:OIDC BFF 接 Innovedus Member Center
  - internal/oidc package(coreos/go-oidc + PKCE S256 + state + nonce)
  - internal/usersession package(HMAC-SHA256 cookie + RotateSessionID
    防 session fixation, OWASP ASVS V3.2.1)
  - 4 個 OIDC handler(/api/auth/login|callback|me|logout)+ AuthMiddleware
  - 完全拔除 StaticAuthProvider,OIDC 是唯一認證路徑
  - 9 個 ADR(含 ADR-010 BFF / ADR-011 取代 static auth /
    ADR-012 pending session shared cookie / ADR-013 PKCE-only public client)
- Phase 0.7:A1 改造 + security audit 修復
  - OIDC ClientSecret 變選填,支援 stage MC 的 public PKCE-only client
    (AuthStyleInParams 強制 token endpoint 不送 client_secret)
  - 預留 ServiceClient* 欄位給未來 client_credentials grant
  - 移除 13+ 處 resolveUserID(uc, StaticUserID) fallback 改 strict mode
    (Audit C1:multi-tenant 隔離破口)
  - Pairing exchange MarkUsed 失敗 abort + revoke session token(Audit M3)
  - 新增 all_endpoints_require_auth_test 整合測試(51 endpoint × 401)

驗證:go test -race -count=3 ./... 17 packages 全綠 / go vet 0 warning

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:21:20 +08:00

78 lines
2.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package oidc
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
)
// 隨機值長度常數(位元組數)。
//
// RFC 7636 §4.1 規定 code_verifier 是 43-128 個字元([A-Z a-z 0-9 - . _ ~])。
// 32 bytes 經 base64url無 padding編碼後 = ceil(32 * 4 / 3) = 43 字元,
// 剛好等於最小邊界 43落在合規範圍內且提供 256 bits 熵。
//
// state 與 nonce 沒有 RFC 規範長度,採同樣 32 bytes256 bits足以抵抗暴力猜測。
const (
// pkceVerifierBytes = 32 bytes → base64url 後 43 字元,符合 RFC 7636 範圍下界。
pkceVerifierBytes = 32
// stateNonceBytes = 32 bytes → 256 bits 隨機性。
stateNonceBytes = 32
)
// GenerateCodeVerifier 產生 RFC 7636 PKCE code_verifier。
//
// 回傳值是 base64url無 padding編碼的 32 byte 隨機值,等於 43 個字元,
// 落在 RFC 7636 §4.1 規定的 43-128 字元範圍內(剛好踩在下界)。
//
// 字元集為 [A-Z a-z 0-9 - _]base64url 規範),符合 RFC 7636 §4.1 的
// unreserved characters 子集(少了 `.` 和 `~`,但仍合規)。
func GenerateCodeVerifier() (string, error) {
return randomBase64URL(pkceVerifierBytes)
}
// CodeChallenge 由 code_verifier 算出 RFC 7636 §4.2 規定的 code_challenge
//
// BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
//
// challenge_method 固定 S256不支援 plain因 OAuth 2.1 已標 plain 為 deprecated
//
// caller 應將回傳值放在 authorization request 的 code_challenge 參數,
// 並把 GenerateCodeVerifier() 的原值verifier安全地存在 server-side pending session
// 後續 ExchangeCode 時帶 verifier 給 token endpoint 完成 proof。
func CodeChallenge(verifier string) string {
sum := sha256.Sum256([]byte(verifier))
return base64.RawURLEncoding.EncodeToString(sum[:])
}
// GenerateState 產生 OAuth 2.0 state 值,用於 CSRF 防護。
//
// caller 應將其存在 server-side pending session並在 callback 收到後比對。
func GenerateState() (string, error) {
return randomBase64URL(stateNonceBytes)
}
// GenerateNonce 產生 OIDC nonce 值,用於 id_token replay 防護。
//
// caller 應將其存在 server-side pending session並在 VerifyIDToken 時比對 claims.Nonce。
func GenerateNonce() (string, error) {
return randomBase64URL(stateNonceBytes)
}
// randomBase64URL 是內部 helper產生 n bytes 隨機值並做 base64url無 padding編碼。
//
// 使用 crypto/rand 為密碼學安全亂數源;任何讀取錯誤都向上傳遞給 caller 處理
// (通常代表系統 entropy 出問題,應該讓請求 fail 而非回退到不安全的預設值)。
func randomBase64URL(n int) (string, error) {
if n <= 0 {
return "", fmt.Errorf("oidc: random length must be positive, got %d", n)
}
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("oidc: read random bytes: %w", err)
}
return base64.RawURLEncoding.EncodeToString(b), nil
}