diff --git a/internal/resources/common/url.go b/internal/resources/common/url.go index 14fc438..9e7f16d 100644 --- a/internal/resources/common/url.go +++ b/internal/resources/common/url.go @@ -7,7 +7,7 @@ import ( ) // NormalizeExternalURL returns an absolute URL with a stable, slash-free path -// representation. It is used for GoTrue's issuer and for derived public URLs. +// representation for derived public URLs. func NormalizeExternalURL(raw string) string { u, err := url.Parse(strings.TrimSpace(raw)) if err != nil || u.Scheme == "" || u.Host == "" { @@ -23,12 +23,18 @@ func NormalizeExternalURL(raw string) string { return strings.TrimRight(u.String(), "/") } -// AuthJWKSURL derives the public Auth JWKS endpoint from the configured Auth +// AuthIssuerURL derives the canonical Supabase Auth issuer from the configured // external URL. If the URL already includes /auth/v1, it is preserved. -func AuthJWKSURL(externalURL string) string { +func AuthIssuerURL(externalURL string) string { base := NormalizeExternalURL(externalURL) if strings.HasSuffix(base, "/auth/v1") { - return base + "/.well-known/jwks.json" + return base } - return fmt.Sprintf("%s/auth/v1/.well-known/jwks.json", base) + return fmt.Sprintf("%s/auth/v1", base) +} + +// AuthJWKSURL derives the public Auth JWKS endpoint from the configured Auth +// external URL. If the URL already includes /auth/v1, it is preserved. +func AuthJWKSURL(externalURL string) string { + return AuthIssuerURL(externalURL) + "/.well-known/jwks.json" } diff --git a/internal/resources/deployments/auth.go b/internal/resources/deployments/auth.go index 8a63bea..4a10b1b 100644 --- a/internal/resources/deployments/auth.go +++ b/internal/resources/deployments/auth.go @@ -146,7 +146,7 @@ func buildAuthEnv(project *supabasev1alpha1.SupabaseProject, secretNames *supaba corev1.EnvVar{Name: "GOTRUE_JWT_ADMIN_ROLES", Value: "service_role"}, corev1.EnvVar{Name: "GOTRUE_JWT_AUD", Value: "authenticated"}, corev1.EnvVar{Name: "GOTRUE_JWT_EXP", Value: fmt.Sprintf("%d", common.GetAccessTokenExpiration(project))}, - corev1.EnvVar{Name: "GOTRUE_JWT_ISSUER", Value: common.NormalizeExternalURL(spec.ExternalURL)}, + corev1.EnvVar{Name: "GOTRUE_JWT_ISSUER", Value: common.AuthIssuerURL(spec.ExternalURL)}, corev1.EnvVar{Name: "GOTRUE_JWT_VALID_METHODS", Value: "ES256"}, corev1.EnvVar{ Name: "GOTRUE_JWT_KEYS", diff --git a/internal/resources/deployments/auth_test.go b/internal/resources/deployments/auth_test.go index d59483e..f4dab17 100644 --- a/internal/resources/deployments/auth_test.go +++ b/internal/resources/deployments/auth_test.go @@ -48,6 +48,23 @@ func TestAuthDeploymentRendersAnonymousSignInsSetting(t *testing.T) { } } +func TestAuthDeploymentRendersCanonicalJWTIssuer(t *testing.T) { + project := newTestProject(testNamespace) + project.Spec.Auth.ExternalURL = "https://auth.example.com" + + deployment := BuildAuthDeployment(project, newTestSecretNames()) + for _, variable := range deployment.Spec.Template.Spec.Containers[0].Env { + if variable.Name != "GOTRUE_JWT_ISSUER" { + continue + } + if variable.Value != "https://auth.example.com/auth/v1" { + t.Fatalf("GOTRUE_JWT_ISSUER = %q, want %q", variable.Value, "https://auth.example.com/auth/v1") + } + return + } + t.Fatal("GOTRUE_JWT_ISSUER was not rendered") +} + func TestAuthEmailHookUsesGeneratedSigningSecret(t *testing.T) { project := newTestProject(testNamespace) project.Spec.Auth.EmailHook = &supabasev1alpha1.EmailHookSpec{