Details (original message): Add dual-tier storage (embedded SQLite + optional PostgreSQL), tenant owner scoping, API key auth, versioned dual-dialect migrations, integrity hardening, and batched event persistence
68 lines
1.9 KiB
Go
68 lines
1.9 KiB
Go
package server
|
||
|
||
import (
|
||
"context"
|
||
"net/http"
|
||
"strings"
|
||
|
||
"agent-system-backend/internal/store"
|
||
)
|
||
|
||
// ownerCtxKey 携带租户身份(认证中间件注入;未启用认证 = store.OwnerDefault)。
|
||
type ownerCtxKey struct{}
|
||
|
||
// Authenticator 是 API key 认证(阶段 1:user 即租户)。
|
||
// Enabled=false(嵌入式档默认,回环本地单用户)时全部请求回落默认租户。
|
||
// SSE 的 EventSource 无法带 Authorization 头,允许 ?key= 查询参数兜底。
|
||
type Authenticator struct {
|
||
st *store.Store
|
||
Enabled bool
|
||
}
|
||
|
||
func NewAuthenticator(st *store.Store, enabled bool) *Authenticator {
|
||
return &Authenticator{st: st, Enabled: enabled}
|
||
}
|
||
|
||
func (a *Authenticator) Middleware(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if a == nil || !a.Enabled {
|
||
next.ServeHTTP(w, r.WithContext(withOwner(r, store.OwnerDefault)))
|
||
return
|
||
}
|
||
key := bearerKey(r)
|
||
if key == "" {
|
||
key = r.URL.Query().Get("key")
|
||
}
|
||
if key == "" {
|
||
http.Error(w, "unauthorized: missing API key (Authorization: Bearer <key>)", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
owner, ok := a.st.AuthOwner(key)
|
||
if !ok {
|
||
http.Error(w, "unauthorized: invalid or revoked API key", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
next.ServeHTTP(w, r.WithContext(withOwner(r, owner)))
|
||
})
|
||
}
|
||
|
||
func withOwner(r *http.Request, owner string) context.Context {
|
||
return context.WithValue(r.Context(), ownerCtxKey{}, owner)
|
||
}
|
||
|
||
func bearerKey(r *http.Request) string {
|
||
h := r.Header.Get("Authorization")
|
||
if strings.HasPrefix(h, "Bearer ") {
|
||
return strings.TrimSpace(strings.TrimPrefix(h, "Bearer "))
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// owner 从请求上下文取租户身份(未注入回落默认)。
|
||
func (s *Server) owner(r *http.Request) string {
|
||
if v, ok := r.Context().Value(ownerCtxKey{}).(string); ok && v != "" {
|
||
return v
|
||
}
|
||
return store.OwnerDefault
|
||
}
|