package main
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/MicahParks/keyfunc/v3"
"github.com/golang-jwt/jwt/v5"
)
const jwksURL = "https://api.grantiva.io/api/v1/attestation/public-key"
type GrantivaClaims struct {
DeviceID string `json:"device_id"`
RiskScore *int `json:"risk_score"` // nil on Free tier
RiskCategory string `json:"risk_category"`
DeviceIntegrity string `json:"device_integrity"`
JailbreakDetected bool `json:"jailbreak_detected"`
AttestationCount int `json:"attestation_count"`
CustomClaims map[string]any `json:"custom_claims"`
jwt.RegisteredClaims
}
// Initialize once at startup; keyfunc handles key rotation automatically
var jwks keyfunc.Keyfunc
func init() {
var err error
jwks, err = keyfunc.NewDefault([]string{jwksURL})
if err != nil {
panic(fmt.Sprintf("failed to fetch JWKS: %v", err))
}
}
func VerifyGrantivaToken(tokenStr string) (*GrantivaClaims, error) {
claims := &GrantivaClaims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, jwks.Keyfunc,
jwt.WithIssuedAt(),
jwt.WithIssuer("grantiva"),
jwt.WithValidMethods([]string{"RS256"}),
)
if err != nil || !token.Valid {
return nil, fmt.Errorf("invalid token: %w", err)
}
// Block high-risk devices
if claims.RiskCategory == "blocked" {
return nil, errors.New("device blocked due to high risk score")
}
return claims, nil
}
// HTTP middleware example
func RequireAttestedDevice(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
token := strings.TrimPrefix(auth, "Bearer ")
if token == "" {
http.Error(w, "missing token", http.StatusUnauthorized)
return
}
claims, err := VerifyGrantivaToken(token)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "device_claims", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}