從 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>
67 lines
2.2 KiB
Go
67 lines
2.2 KiB
Go
package auth
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"regexp"
|
||
)
|
||
|
||
// Token prefix 常數(security.md §1.3)。
|
||
const (
|
||
// PairingTokenPrefix 是 pairing token 的固定前綴。
|
||
PairingTokenPrefix = "vAc_"
|
||
// SessionTokenPrefix 是 session token 的固定前綴。
|
||
SessionTokenPrefix = "vAs_"
|
||
|
||
// PairingTokenHexLen 是 pairing token 底 hex 字串的字元數(32 chars = 16 bytes)。
|
||
PairingTokenHexLen = 32
|
||
// SessionTokenHexLen 是 session token 底 hex 字串的字元數(64 chars = 32 bytes)。
|
||
SessionTokenHexLen = 64
|
||
)
|
||
|
||
// pairingTokenRegex 驗證 vAc_ + 32 小寫 hex 的完整格式。
|
||
var pairingTokenRegex = regexp.MustCompile(`^vAc_[0-9a-f]{32}$`)
|
||
|
||
// sessionTokenRegex 驗證 vAs_ + 64 小寫 hex 的完整格式。
|
||
var sessionTokenRegex = regexp.MustCompile(`^vAs_[0-9a-f]{64}$`)
|
||
|
||
// GeneratePairingToken 產生一個符合 `vAc_[0-9a-f]{32}` 格式的 pairing token。
|
||
//
|
||
// 來源:crypto/rand.Read 16 bytes → hex 編碼。
|
||
// 失敗時回傳 err(通常僅在系統熵耗盡時發生)。
|
||
func GeneratePairingToken() (string, error) {
|
||
b := make([]byte, PairingTokenHexLen/2) // 16 bytes = 32 hex chars
|
||
if _, err := rand.Read(b); err != nil {
|
||
return "", err
|
||
}
|
||
return PairingTokenPrefix + hex.EncodeToString(b), nil
|
||
}
|
||
|
||
// GenerateSessionToken 產生一個符合 `vAs_[0-9a-f]{64}` 格式的 session token。
|
||
//
|
||
// 來源:crypto/rand.Read 32 bytes → hex 編碼。
|
||
func GenerateSessionToken() (string, error) {
|
||
b := make([]byte, SessionTokenHexLen/2) // 32 bytes = 64 hex chars
|
||
if _, err := rand.Read(b); err != nil {
|
||
return "", err
|
||
}
|
||
return SessionTokenPrefix + hex.EncodeToString(b), nil
|
||
}
|
||
|
||
// IsValidPairingToken 驗證 token 字串是否符合 pairing token 格式。
|
||
func IsValidPairingToken(token string) bool {
|
||
return pairingTokenRegex.MatchString(token)
|
||
}
|
||
|
||
// IsValidSessionToken 驗證 token 字串是否符合 session token 格式。
|
||
func IsValidSessionToken(token string) bool {
|
||
return sessionTokenRegex.MatchString(token)
|
||
}
|
||
|
||
// HashToken 計算 token 的 sha256 hex 字串,供 DB 儲存用(永遠不存明文)。
|
||
func HashToken(token string) string {
|
||
h := sha256.Sum256([]byte(token))
|
||
return hex.EncodeToString(h[:])
|
||
}
|