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

112 lines
2.8 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 wsconn 把一個 gorilla/websocket.Conn 包裝成 net.Conn
// 讓 hashicorp/yamux 這類 stream multiplexer 可以直接跑在 WebSocket 之上。
//
// 從 POC `edge-ai-platform/server/pkg/wsconn` 直接複製,未做邏輯變動;
// 對齊 tunnel.md §4.1 與 ADR-002「沿用 POC 的 tunnel 協定」。
//
// 協定重點:
// - 所有 frame 皆為 WebSocket Binary frame無壓縮yamux 已是位元流)。
// - 讀取時以 NextReader() 取得當前 frame 的 reader剩餘 bytes 跨次呼叫延續。
// - 寫入時整個 []byte 打包為一個 Binary messageyamux 自己分 frame
package wsconn
import (
"io"
"net"
"sync"
"time"
"github.com/gorilla/websocket"
)
// Conn 是 *websocket.Conn 到 net.Conn 的 adapter。
//
// rmu / wmu 分開,允許同時讀與寫(符合 net.Conn 典型期望)。
// 但同一方向不允許並發呼叫yamux 已保證此約束)。
type Conn struct {
ws *websocket.Conn
reader io.Reader // 當前 WebSocket frame 的 reader跨次呼叫延續
rmu sync.Mutex
wmu sync.Mutex
}
// New 將 *websocket.Conn 包裝成符合 net.Conn 介面的 *Conn。
func New(ws *websocket.Conn) *Conn {
return &Conn{ws: ws}
}
// Read 實作 io.Reader。
//
// 若當前 frame reader 已讀盡會自動切到下一個 frameEOF 自動透明處理。
func (c *Conn) Read(p []byte) (int, error) {
c.rmu.Lock()
defer c.rmu.Unlock()
for {
if c.reader != nil {
n, err := c.reader.Read(p)
if err == io.EOF {
c.reader = nil
if n > 0 {
return n, nil
}
continue
}
return n, err
}
_, reader, err := c.ws.NextReader()
if err != nil {
return 0, err
}
c.reader = reader
}
}
// Write 實作 io.Writer整個 p 以單一 BinaryMessage 寫出。
func (c *Conn) Write(p []byte) (int, error) {
c.wmu.Lock()
defer c.wmu.Unlock()
if err := c.ws.WriteMessage(websocket.BinaryMessage, p); err != nil {
return 0, err
}
return len(p), nil
}
// Close 關閉底層 WebSocket 連線。
func (c *Conn) Close() error {
return c.ws.Close()
}
// LocalAddr 回傳本地 WebSocket endpoint 的位址。
func (c *Conn) LocalAddr() net.Addr {
return c.ws.LocalAddr()
}
// RemoteAddr 回傳對端 WebSocket endpoint 的位址。
func (c *Conn) RemoteAddr() net.Addr {
return c.ws.RemoteAddr()
}
// SetDeadline 同時設定讀寫 deadline。
func (c *Conn) SetDeadline(t time.Time) error {
if err := c.ws.SetReadDeadline(t); err != nil {
return err
}
return c.ws.SetWriteDeadline(t)
}
// SetReadDeadline 設定讀取 deadline。
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.ws.SetReadDeadline(t)
}
// SetWriteDeadline 設定寫入 deadline。
func (c *Conn) SetWriteDeadline(t time.Time) error {
return c.ws.SetWriteDeadline(t)
}
// 編譯時檢查Conn 必須實作 net.Conn。
var _ net.Conn = (*Conn)(nil)