從 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>
96 lines
3.8 KiB
Go
96 lines
3.8 KiB
Go
// Package storage 定義物件儲存介面與 LocalFS 實作。
|
||
//
|
||
// 對齊 storage.md §1 與 PRD interface-contracts.md §8.4:
|
||
// - 雛形使用 LocalFSStore(檔案系統)+ 假 presigned URL(HMAC 簽名)
|
||
// - Phase 1 新增 S3Store(同 interface),業務邏輯不用動
|
||
//
|
||
// Key 命名規範見 storage.md §2(例:`models/{user_id}/{model_id}.nef`)。
|
||
package storage
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"io"
|
||
"time"
|
||
)
|
||
|
||
// ==========================================================================
|
||
// Errors
|
||
// ==========================================================================
|
||
|
||
var (
|
||
// ErrNotFound 表示指定 key 的 object 不存在。
|
||
ErrNotFound = errors.New("storage: object not found")
|
||
|
||
// ErrAlreadyExists 表示 object 已存在且 Put 被要求不覆蓋(Phase 1)。
|
||
ErrAlreadyExists = errors.New("storage: object already exists")
|
||
|
||
// ErrInvalidKey 表示 key 含非法字元(例:包含 "..", 嘗試 path traversal)。
|
||
ErrInvalidKey = errors.New("storage: invalid key")
|
||
|
||
// ErrInvalidSignature 表示 presigned URL 簽名錯誤或過期。
|
||
ErrInvalidSignature = errors.New("storage: invalid or expired signature")
|
||
)
|
||
|
||
// ==========================================================================
|
||
// Types
|
||
// ==========================================================================
|
||
|
||
// Object 是儲存物件的描述(metadata;不含實際內容)。
|
||
//
|
||
// 對齊 storage.md §1 的 ObjectInfo。
|
||
type Object struct {
|
||
Key string `json:"key"`
|
||
Size int64 `json:"size"`
|
||
ContentType string `json:"contentType"`
|
||
LastModified time.Time `json:"lastModified"`
|
||
ETag string `json:"etag,omitempty"`
|
||
Metadata map[string]string `json:"metadata,omitempty"`
|
||
}
|
||
|
||
// ==========================================================================
|
||
// Store interface
|
||
// ==========================================================================
|
||
|
||
// Store 是物件儲存的抽象。
|
||
//
|
||
// 實作必須:
|
||
// - 並發安全(多 goroutine 同時 Put / Get 不 panic)
|
||
// - Key 驗證(防止 path traversal)
|
||
// - 語意對齊:Exists 回 (false, nil) 表不存在,其他錯誤走 err
|
||
type Store interface {
|
||
// Put 上傳一個 object;若已存在則覆蓋。
|
||
// size 為預期大小(bytes);實作可用於早期檢查或配額控制。
|
||
// meta 可為 nil(無額外 metadata)。
|
||
Put(ctx context.Context, key string, r io.Reader, size int64, meta map[string]string) error
|
||
|
||
// Get 下載一個 object;caller 必須 Close() reader。
|
||
// 不存在回 ErrNotFound。
|
||
Get(ctx context.Context, key string) (io.ReadCloser, *Object, error)
|
||
|
||
// Stat 取得 object 的 metadata(不下載內容)。
|
||
// 不存在回 ErrNotFound。
|
||
Stat(ctx context.Context, key string) (*Object, error)
|
||
|
||
// Exists 判斷 key 是否存在(Minor-4 / PRD §8.4 要求)。
|
||
// 語意:true = 存在可用;false = 不存在(非 error)。
|
||
// 其他錯誤(權限 / IO)回 (false, err)。
|
||
Exists(ctx context.Context, key string) (bool, error)
|
||
|
||
// Delete 刪除 object;不存在為 no-op,不回 error。
|
||
Delete(ctx context.Context, key string) error
|
||
|
||
// List 列出指定 prefix 下的所有 object。
|
||
List(ctx context.Context, prefix string) ([]*Object, error)
|
||
|
||
// PresignedGetURL 產生一個限時下載 URL。
|
||
// 對 LocalFS:回傳 baseURL + /key?expires=&signature=(由 api-server 驗證)
|
||
// 對 S3:回傳原生 AWS presigned URL
|
||
PresignedGetURL(ctx context.Context, key string, ttl time.Duration) (string, error)
|
||
|
||
// PresignedPutURL 產生一個限時上傳 URL。
|
||
// 對 LocalFS:回傳 baseURL + /key?expires=&signature=&mode=put
|
||
// 對 S3:回傳原生 AWS presigned PUT URL
|
||
PresignedPutURL(ctx context.Context, key string, ttl time.Duration) (string, error)
|
||
}
|