從 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>
89 lines
2.4 KiB
Go
89 lines
2.4 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"log/slog"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
|
||
"visiona-backend/internal/auth"
|
||
"visiona-backend/internal/device"
|
||
"visiona-backend/internal/model"
|
||
)
|
||
|
||
// seedDemoData 在啟動時塞入示範資料,方便本機開發 / demo 不必跑完整 pairing。
|
||
//
|
||
// 觸發條件:VISIONA_SEED_DEMO_DATA=true
|
||
//
|
||
// 內容:
|
||
// - 一個示範 device(KL520)
|
||
// - 一個示範 model(YOLOv5 Face)
|
||
// - 一個示範 pairing token(log 出來方便手動 copy)
|
||
//
|
||
// 注意:
|
||
// - 失敗只 log warning,不阻擋啟動
|
||
// - 重複呼叫會產生重複資料;本函式只該被呼叫一次(main 已保證)
|
||
// - **不要**在生產環境啟用此 flag
|
||
func seedDemoData(
|
||
devRepo device.Repository,
|
||
mdlRepo model.Repository,
|
||
pairings auth.PairingStore,
|
||
userID string,
|
||
log *slog.Logger,
|
||
) error {
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
defer cancel()
|
||
|
||
now := time.Now().UTC()
|
||
|
||
// 1. Demo device
|
||
dev := &device.Device{
|
||
ID: "demo-device-" + uuid.NewString()[:8],
|
||
OwnerUserID: userID,
|
||
Name: "Demo KL520 (seeded)",
|
||
DeviceType: "kl520",
|
||
SerialNumber: "DEMO-SN-001",
|
||
RemoteStatus: device.RemoteStatusOffline,
|
||
Status: device.USBStatusUnknown,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
if err := devRepo.Save(ctx, dev); err != nil {
|
||
log.Warn("seed: device save failed", "error", err)
|
||
} else {
|
||
log.Info("seed: demo device created", "id", dev.ID, "name", dev.Name)
|
||
}
|
||
|
||
// 2. Demo model
|
||
mdl := &model.Model{
|
||
ID: "demo-model-" + uuid.NewString()[:8],
|
||
OwnerUserID: userID,
|
||
Name: "YOLOv5 Face (seeded)",
|
||
TargetChip: "kl520",
|
||
FileSize: 1024 * 1024, // 1 MB
|
||
Source: model.SourceUploaded,
|
||
StorageKey: "models/" + userID + "/demo.nef",
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
UploadedAt: &now,
|
||
}
|
||
if err := mdlRepo.Save(ctx, mdl); err != nil {
|
||
log.Warn("seed: model save failed", "error", err)
|
||
} else {
|
||
log.Info("seed: demo model created", "id", mdl.ID, "name", mdl.Name)
|
||
}
|
||
|
||
// 3. Demo pairing token(log plaintext 方便開發 — 雛形 demo 用,生產禁用)
|
||
pt, _, err := pairings.Create(ctx, userID, 24*time.Hour)
|
||
if err != nil {
|
||
log.Warn("seed: pairing token create failed", "error", err)
|
||
} else {
|
||
log.Info("seed: demo pairing token created (use for local-tool tunnel)",
|
||
"token", pt,
|
||
"ttl", "24h")
|
||
}
|
||
|
||
return nil
|
||
}
|