從 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>
87 lines
2.7 KiB
Go
87 lines
2.7 KiB
Go
package api
|
||
|
||
import (
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// 錯誤碼常數 — 對齊 api-spec.md §11。
|
||
const (
|
||
ErrCodeUnauthorized = "UNAUTHORIZED"
|
||
ErrCodeForbidden = "FORBIDDEN"
|
||
ErrCodeNotFound = "NOT_FOUND"
|
||
ErrCodeValidationFailed = "VALIDATION_FAILED"
|
||
ErrCodeTunnelDisconnect = "TUNNEL_DISCONNECTED"
|
||
ErrCodeTunnelError = "TUNNEL_ERROR"
|
||
ErrCodeNotImplemented = "NOT_IMPLEMENTED"
|
||
ErrCodeRateLimited = "RATE_LIMITED"
|
||
ErrCodeInternalError = "INTERNAL_ERROR"
|
||
// ErrCodePayloadTooLarge 對齊 HTTP 413(例:模型上傳超過 MaxUploadSizeMB)。
|
||
ErrCodePayloadTooLarge = "PAYLOAD_TOO_LARGE"
|
||
// ErrCodeInvalidSignature 用於 /storage/* 驗簽失敗 / URL 過期。
|
||
ErrCodeInvalidSignature = "INVALID_SIGNATURE"
|
||
)
|
||
|
||
// ErrorBody 是 API 錯誤回應的 envelope 結構。
|
||
//
|
||
// 對齊 api-spec.md:
|
||
//
|
||
// { "success": false, "error": { "code": "...", "message": "...", "request_id": "..." } }
|
||
//
|
||
// 為什麼用 envelope 而非裸 error:方便前端統一處理 + 與成功回應形狀一致。
|
||
type ErrorBody struct {
|
||
Success bool `json:"success"`
|
||
Error *ErrorDetail `json:"error"`
|
||
}
|
||
|
||
// ErrorDetail 是錯誤的具體資訊。
|
||
type ErrorDetail struct {
|
||
Code string `json:"code"`
|
||
Message string `json:"message"`
|
||
Details []FieldError `json:"details,omitempty"` // 例如 validation 細節
|
||
RequestID string `json:"request_id,omitempty"`
|
||
Extra map[string]any `json:"extra,omitempty"` // 給 specific error 帶結構化資料
|
||
}
|
||
|
||
// FieldError 描述單一欄位的驗證錯誤。
|
||
type FieldError struct {
|
||
Field string `json:"field"`
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
// SuccessBody 是成功回應的 envelope。
|
||
//
|
||
// 對齊 api-spec.md:`{ "success": true, "data": ... }`。
|
||
type SuccessBody struct {
|
||
Success bool `json:"success"`
|
||
Data any `json:"data,omitempty"`
|
||
}
|
||
|
||
// WriteError 統一寫錯誤回應(會自動帶上 request_id)。
|
||
//
|
||
// 注意:呼叫後 caller 仍需自行 c.Abort()(如果是在 middleware 中要終止 chain);
|
||
// 在 handler 中只需 return 即可。
|
||
func WriteError(c *gin.Context, status int, code, message string, details []FieldError) {
|
||
c.JSON(status, ErrorBody{
|
||
Success: false,
|
||
Error: &ErrorDetail{
|
||
Code: code,
|
||
Message: message,
|
||
Details: details,
|
||
RequestID: RequestIDFrom(c),
|
||
},
|
||
})
|
||
}
|
||
|
||
// WriteSuccess 統一寫成功回應。
|
||
func WriteSuccess(c *gin.Context, status int, data any) {
|
||
c.JSON(status, SuccessBody{
|
||
Success: true,
|
||
Data: data,
|
||
})
|
||
}
|
||
|
||
// WriteNotImplemented 回應 501,給 B5 還沒實作的 handler 用。
|
||
func WriteNotImplemented(c *gin.Context, hint string) {
|
||
WriteError(c, 501, ErrCodeNotImplemented, hint, nil)
|
||
}
|