Files
homelab-frontend/internal/auth/jwt_test.go
T

138 lines
5.3 KiB
Go
Raw Normal View History

package auth
import (
"strings"
"testing"
"github.com/golang-jwt/jwt/v5"
)
func TestCheckPermissions(t *testing.T) {
validator := NewValidator(
"https://authentik.riotpiao.com/application/o/sqs/",
"sqs",
"https://authentik.riotpiao.com/application/o/sqs/jwks/",
)
// Test 1: Finds sqs:read
claims1 := jwt.MapClaims{
"permissions": []interface{}{"sqs:read", "memory:write"},
}
if !validator.CheckPermissions(claims1, "sqs:read", "sqs:write") {
t.Fatal("expected to find sqs:read permission")
}
// Test 2: Finds wildcard
claims2 := jwt.MapClaims{
"permissions": []interface{}{"*"},
}
if !validator.CheckPermissions(claims2, "sqs:read") {
t.Fatal("expected to find wildcard permission")
}
// Test 3: Rejects when missing
claims3 := jwt.MapClaims{
"permissions": []interface{}{"memory:read"},
}
if validator.CheckPermissions(claims3, "sqs:read") {
t.Fatal("expected to reject missing permission")
}
// Test 4: Handles missing permissions claim
claims4 := jwt.MapClaims{}
if validator.CheckPermissions(claims4, "sqs:read") {
t.Fatal("expected to reject missing permissions claim")
}
t.Log("✅ All permission checks passed")
}
func TestValidateBearerToken(t *testing.T) {
validator := NewValidator(
"https://authentik.riotpiao.com/application/o/sqs/",
"sqs",
"https://authentik.riotpiao.com/application/o/sqs/jwks/",
)
// Test 1: Empty token
_, err := validator.ValidateBearerToken("")
if err == nil {
t.Fatal("expected error for empty token")
}
t.Logf("✅ Correctly rejected empty token: %v", err)
// Test 2: Invalid format
_, err = validator.ValidateBearerToken("not-a-bearer-token")
if err == nil {
t.Fatal("expected error for invalid format")
}
t.Logf("✅ Correctly rejected invalid format: %v", err)
// Test 3: Invalid token payload
_, err = validator.ValidateBearerToken("Bearer invalid.token.format")
if err == nil {
t.Fatal("expected error for invalid token")
}
t.Logf("✅ Correctly rejected invalid token: %v", err)
}
func TestRealAuthentikToken(t *testing.T) {
// Real JWT from Authentik (poimen-memory application)
realToken := "eyJhbGciOiJSUzI1NiIsImtpZCI6Ik50cUdGSmJGbDhkTmxKRHRxVTZIdnltRkN0eDc2Vl9hNnlKbnI4NHhQbzdNRTVxODhKajJZa3NGX3pOclA3QU1PRlBxWTFxVm8wckJIZUFMYmJlVGdBIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2F1dGhlbnRpay5yaW90cGlhby5jb20vYXBwbGljYXRpb24vby9wb2ltZW4tbWVtb3J5LyIsInN1YiI6ImUxMmJhNTE5NTk2NDkzNzhkMTAwMjI2YzllOTI0NTQxOWRkMzYzM2U1ZDRjMTExMjhkNDAyOTNhNmM0ODQzYTMiLCJhdWQiOiJwb2ltZW4tbWVtb3J5IiwiZXhwIjoxNzg3ODYyMzAxLCJpYXQiOjE3ODc4NTg3MDEsImF1dGhfdGltZSI6MTc4Nzg1ODcwMSwiYWNyIjoiZ29hdXRoZW50aWsuaW8vcHJvdmlkZXJzL29hdXRoMi9kZWZhdWx0IiwianRpIjoiekNEOUJaYWxrZ0pGVHQxclk5Z3l5eXZGRk1RMEJZWVhITlBzN0FNdiIsImF6cCI6InBvaW1lbi1tZW1vcnkiLCJ1aWQiOiI3SUY3dWU5WnR4bERKWkhkSmJNc05yTDd3TjF4aUJwdkVMWDdMWklpIiwic2NvcGUiOiIifQ.i6JrDnQuxZm0MInxJENI-oAsUZlaP_VXJMheWOjevQ7O_ZLE1TOPHyRETZBpj5Ne0MrPjJv19kNdmWsadEuVWIcnBVce5A9jghanROQEbl2ATO7GmdyNGLfAUghPimeBUXncfxpfC9zlhbOBRIPQgKjluqCbySwKhwkd95QjAxdWILypkizxFMiHWProxrctIZM-OJ69RGTGxkkLAqkB5O__Ch0-qR4gWTCLOS9V-qe7wAdX4FjU5lQUfx1HnudQ0YECt6g3i5EeonEyYroPk163tj-ad_fhqWxz9B3AeoOy5rp88G1qBlj_sdmtWGoncqI73AuX0PjpSGKnlqZpVVOFhmX_cqnDXPWMl6Rlrq23NishIH6OzTa9aSLZYSN5jRLK1N6hHbslAFnTUdFMAHC8d6dUOfsYToxqxV6dj-FAN64gxBu5ullo47f9jUvGmGVNhVS6LErLdEprO2um0k-mBkq3D_FCoEUcLMCYHDKhw9eFKT_U7NwJz8_zRflJjEwlPwXHnMR_S6bKDsYcK2I2WCs8N1zc0ZMhpqQ7wKJW49B6GnKZQJocOi0rWLu8KgzckGEahTykJ6VN9FMhxu7hLcYNimItNYY6pD7kZdluxHeKW29dA47B1BaVkw2E0XZ7bnzSfLgVXO5810sxpYRm4tSDewzwYsaJ4Q_Wkdw"
t.Run("Decode unverified real Authentik token", func(t *testing.T) {
claims, err := DecodeToken(realToken)
if err != nil {
t.Fatalf("failed to decode token: %v", err)
}
// Verify token structure
if iss, ok := claims["iss"].(string); !ok || !strings.Contains(iss, "authentik.riotpiao.com") {
t.Fatal("token not from Authentik")
}
if aud, ok := claims["aud"].(string); !ok || aud != "poimen-memory" {
t.Fatalf("expected audience 'poimen-memory', got %v", aud)
}
t.Log("✅ Real Authentik token structure valid")
t.Logf(" Issuer: %v", claims["iss"])
t.Logf(" Audience: %v", claims["aud"])
t.Logf(" Subject: %v", claims["sub"])
})
t.Run("Validate with correct issuer/audience", func(t *testing.T) {
validator := NewValidator(
"https://authentik.riotpiao.com/application/o/poimen-memory/",
"poimen-memory",
"https://authentik.riotpiao.com/application/o/poimen-memory/jwks/",
)
claims, err := validator.ValidateBearerToken("Bearer " + realToken)
if err != nil {
// Might fail if JWKS fetch fails (no network), but structure is valid
t.Logf("Token validation error (expected if no JWKS fetch): %v", err)
} else {
t.Log("✅ Real Authentik JWT validated successfully")
if aud, ok := claims["aud"].(string); ok {
t.Logf(" Audience verified: %s", aud)
}
}
})
t.Run("Reject token with wrong audience", func(t *testing.T) {
validator := NewValidator(
"https://authentik.riotpiao.com/application/o/sqs/",
"sqs", // Wrong audience
"https://authentik.riotpiao.com/application/o/sqs/jwks/",
)
claims, err := validator.ValidateBearerToken("Bearer " + realToken)
if err == nil && claims != nil {
t.Fatalf("expected rejection due to wrong audience, but validation passed")
}
// Should fail due to audience mismatch
t.Logf("✅ Correctly rejected token with wrong audience: %v", err)
})
}