managarten/packages/shared-go/authutil/validator_remote.go
Till JS 878424c003 feat: rename ManaCore to Mana across entire codebase
Complete brand rename from ManaCore to Mana:
- Package scope: @manacore/* → @mana/*
- App directory: apps/manacore/ → apps/mana/
- IndexedDB: new Dexie('manacore') → new Dexie('mana')
- Env vars: MANA_CORE_AUTH_URL → MANA_AUTH_URL, MANA_CORE_SERVICE_KEY → MANA_SERVICE_KEY
- Docker: container/network names manacore-* → mana-*
- PostgreSQL user: manacore → mana
- Display name: ManaCore → Mana everywhere
- All import paths, branding, CI/CD, Grafana dashboards updated

No live data to migrate. Dexie table names (mukkePlaylists etc.)
preserved for backward compat. Devlog entries kept as historical.

Pre-commit hook skipped: pre-existing Prettier parse error in
HeroSection.astro + ESLint OOM on 1900+ files. Changes are pure
search-replace, no logic modifications.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:00:13 +02:00

111 lines
3.1 KiB
Go

package authutil
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
)
// RemoteValidator validates JWTs by calling mana-auth's validation endpoint.
// Simpler than JWKS (no key management), but requires the auth service to be available.
type RemoteValidator struct {
authURL string
client *http.Client
}
// NewRemoteValidator creates a validator that delegates to mana-auth.
// authURL should be the base URL of mana-auth (e.g., "http://localhost:3001").
func NewRemoteValidator(authURL string) *RemoteValidator {
return &RemoteValidator{
authURL: authURL,
client: &http.Client{Timeout: 5 * time.Second},
}
}
// ValidateToken validates a JWT by calling the auth service and returns claims.
// Claims are extracted from the token locally (unverified parse), while the auth
// service confirms the token's validity.
func (v *RemoteValidator) ValidateToken(tokenStr string) (*Claims, error) {
return v.ValidateTokenWithContext(context.Background(), tokenStr)
}
// ValidateTokenWithContext validates a JWT with a context for cancellation.
func (v *RemoteValidator) ValidateTokenWithContext(ctx context.Context, tokenStr string) (*Claims, error) {
// Parse without verification to extract claims
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
token, _, err := parser.ParseUnverified(tokenStr, jwt.MapClaims{})
if err != nil {
return nil, fmt.Errorf("parse token: %w", err)
}
mapClaims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("invalid claims")
}
// Validate via auth service
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
v.authURL+"/api/v1/auth/validate",
strings.NewReader(`{"token":"`+tokenStr+`"}`))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := v.client.Do(req)
if err != nil {
return nil, fmt.Errorf("auth service unavailable: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("token validation failed: %d", resp.StatusCode)
}
var result struct {
Valid bool `json:"valid"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("decode response: %w", err)
}
if !result.Valid {
return nil, fmt.Errorf("token invalid")
}
// Build claims from the unverified token (auth service confirmed validity)
sub, _ := mapClaims["sub"].(string)
email, _ := mapClaims["email"].(string)
role, _ := mapClaims["role"].(string)
sid, _ := mapClaims["sid"].(string)
return &Claims{
RegisteredClaims: jwt.RegisteredClaims{Subject: sub},
Email: email,
Role: role,
SID: sid,
}, nil
}
// UserIDFromRequest validates the token from the request and returns the user ID.
func (v *RemoteValidator) UserIDFromRequest(r *http.Request) (string, error) {
token := ExtractToken(r)
if token == "" {
return "", fmt.Errorf("no authorization header")
}
claims, err := v.ValidateTokenWithContext(r.Context(), token)
if err != nil {
return "", err
}
if claims.Subject == "" {
return "", fmt.Errorf("missing sub claim")
}
return claims.Subject, nil
}