- windsurf_gateway_service: 添加上游延迟/TTFT/错误上下文记录 - endpoint: DeriveUpstreamEndpoint 添加 PlatformWindsurf 分支 - ops_error_logger: guessPlatformFromPath 添加 /windsurf/ 识别
54 lines
1.6 KiB
Go
54 lines
1.6 KiB
Go
// JWT decoding helpers.
|
|
// Portions derived from windsurf-tools (MIT 2025 shaoyu521). See ./LICENSE.
|
|
package windsurf
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// readRandom abstracts crypto/rand.Read for testability.
|
|
func readRandom(b []byte) (int, error) { return rand.Read(b) }
|
|
|
|
// JWTClaims holds the fields we care about from the Windsurf session JWT.
|
|
type JWTClaims struct {
|
|
SessionID string `json:"session_id,omitempty"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
TeamID string `json:"team_id,omitempty"`
|
|
AuthUID string `json:"auth_uid,omitempty"`
|
|
Exp int64 `json:"exp,omitempty"`
|
|
}
|
|
|
|
// StripDevinPrefix returns the raw JWT (without the "devin-session-token$" prefix).
|
|
func StripDevinPrefix(token string) string {
|
|
if i := strings.Index(token, "$"); i >= 0 && strings.HasPrefix(token, "devin-session-token$") {
|
|
return token[i+1:]
|
|
}
|
|
return token
|
|
}
|
|
|
|
// DecodeJWTClaims parses the payload portion of a JWT (after stripping the
|
|
// optional "devin-session-token$" prefix). It does NOT verify the signature.
|
|
func DecodeJWTClaims(token string) (*JWTClaims, error) {
|
|
jwt := StripDevinPrefix(token)
|
|
parts := strings.Split(jwt, ".")
|
|
if len(parts) != 3 {
|
|
return nil, fmt.Errorf("jwt: expected 3 segments, got %d", len(parts))
|
|
}
|
|
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
|
if err != nil {
|
|
payload, err = base64.URLEncoding.DecodeString(parts[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("jwt payload base64: %w", err)
|
|
}
|
|
}
|
|
var claims JWTClaims
|
|
if err := json.Unmarshal(payload, &claims); err != nil {
|
|
return nil, fmt.Errorf("jwt payload json: %w", err)
|
|
}
|
|
return &claims, nil
|
|
}
|