從 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>
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
// 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 message(yamux 自己分 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 已讀盡會自動切到下一個 frame;EOF 自動透明處理。
|
||
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)
|