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) }) }