diff --git a/internal/auth/jwt_test.go b/internal/auth/jwt_test.go index 2a0d7fe..9625800 100644 --- a/internal/auth/jwt_test.go +++ b/internal/auth/jwt_test.go @@ -1,6 +1,7 @@ package auth import ( + "strings" "testing" "github.com/golang-jwt/jwt/v5" @@ -74,3 +75,63 @@ func TestValidateBearerToken(t *testing.T) { } 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) + }) +}