A community based topic aggregation platform built on atproto
at main 2.1 kB view raw
1package communities 2 3import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "strings" 8 "time" 9) 10 11// parseJWTExpiration extracts the expiration time from a JWT access token 12// This function does NOT verify the signature - it only parses the exp claim 13// atproto access tokens use standard JWT format with 'exp' claim (Unix timestamp) 14func parseJWTExpiration(token string) (time.Time, error) { 15 // Remove "Bearer " prefix if present 16 token = strings.TrimPrefix(token, "Bearer ") 17 token = strings.TrimSpace(token) 18 19 // JWT format: header.payload.signature 20 parts := strings.Split(token, ".") 21 if len(parts) != 3 { 22 return time.Time{}, fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts)) 23 } 24 25 // Decode payload (second part) - use RawURLEncoding (no padding) 26 payload, err := base64.RawURLEncoding.DecodeString(parts[1]) 27 if err != nil { 28 return time.Time{}, fmt.Errorf("failed to decode JWT payload: %w", err) 29 } 30 31 // Extract exp claim (Unix timestamp) 32 var claims struct { 33 Exp int64 `json:"exp"` // Expiration time (seconds since Unix epoch) 34 } 35 if err := json.Unmarshal(payload, &claims); err != nil { 36 return time.Time{}, fmt.Errorf("failed to parse JWT claims: %w", err) 37 } 38 39 if claims.Exp == 0 { 40 return time.Time{}, fmt.Errorf("JWT missing 'exp' claim") 41 } 42 43 // Convert Unix timestamp to time.Time 44 return time.Unix(claims.Exp, 0), nil 45} 46 47// NeedsRefresh checks if an access token should be refreshed 48// Returns true if the token expires within the next 5 minutes (or is already expired) 49// Uses a 5-minute buffer to ensure we refresh before actual expiration 50func NeedsRefresh(accessToken string) (bool, error) { 51 if accessToken == "" { 52 return false, fmt.Errorf("access token is empty") 53 } 54 55 expiration, err := parseJWTExpiration(accessToken) 56 if err != nil { 57 return false, fmt.Errorf("failed to parse token expiration: %w", err) 58 } 59 60 // Refresh if token expires within 5 minutes 61 // This prevents service interruptions from expired tokens 62 bufferTime := 5 * time.Minute 63 expiresWithinBuffer := time.Now().Add(bufferTime).After(expiration) 64 65 return expiresWithinBuffer, nil 66}