xworkspace-console/api/auth.go
2026-06-14 08:43:53 +08:00

99 lines
2.1 KiB
Go

package main
import (
"crypto/subtle"
"net/http"
"os"
"path/filepath"
"strings"
)
type AuthConfig struct {
tokens []string
}
func NewAuthConfig() AuthConfig {
return AuthConfig{tokens: loadAuthTokens()}
}
func (a AuthConfig) Required() bool {
return len(a.tokens) > 0
}
func (a AuthConfig) Authorize(r *http.Request) bool {
if !a.Required() {
return true
}
token := requestToken(r)
if token == "" {
return false
}
for _, expected := range a.tokens {
if constantTimeTokenEqual(token, expected) {
return true
}
}
return false
}
func loadAuthTokens() []string {
candidates := []string{
os.Getenv("AI_WORKSPACE_AUTH_TOKEN"),
os.Getenv("XWORKSPACE_CONSOLE_AUTH_TOKEN"),
os.Getenv("XWORKMATE_BRIDGE_AUTH_TOKEN"),
os.Getenv("BRIDGE_AUTH_TOKEN"),
os.Getenv("BRIDGE_REVIEW_AUTH_TOKEN"),
os.Getenv("INTERNAL_SERVICE_TOKEN"),
}
if home, err := os.UserHomeDir(); err == nil {
candidates = append(candidates,
readTokenFile(filepath.Join(home, ".ai_workspace_auth_token")),
readTokenFile(filepath.Join(home, ".config", "xworkspace", "auth-token")),
)
}
seen := map[string]struct{}{}
tokens := make([]string, 0, len(candidates))
for _, candidate := range candidates {
token := strings.TrimSpace(candidate)
if token == "" {
continue
}
if _, ok := seen[token]; ok {
continue
}
seen[token] = struct{}{}
tokens = append(tokens, token)
}
return tokens
}
func readTokenFile(path string) string {
content, err := os.ReadFile(path)
if err != nil {
return ""
}
return strings.TrimSpace(string(content))
}
func requestToken(r *http.Request) string {
header := strings.TrimSpace(r.Header.Get("Authorization"))
if len(header) > 7 && strings.EqualFold(header[:7], "Bearer ") {
return strings.TrimSpace(header[7:])
}
if header != "" {
return header
}
if token := strings.TrimSpace(r.Header.Get("X-Bridge-Token")); token != "" {
return token
}
return strings.TrimSpace(r.Header.Get("X-XWorkspace-Token"))
}
func constantTimeTokenEqual(actual string, expected string) bool {
if len(actual) != len(expected) {
return false
}
return subtle.ConstantTimeCompare([]byte(actual), []byte(expected)) == 1
}