From caa25503cdfc449d116c204e866bccc2617d7037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Mon, 13 Oct 2025 16:43:58 +0200 Subject: [PATCH] Allow empty string as valid URL in DCR workflow (#987) --- src/shared/auth.test.ts | 14 +++++++++++++- src/shared/auth.ts | 9 +++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/shared/auth.test.ts b/src/shared/auth.test.ts index 41f9dc1a..71877f34 100644 --- a/src/shared/auth.test.ts +++ b/src/shared/auth.test.ts @@ -1,5 +1,11 @@ import { describe, it, expect } from '@jest/globals'; -import { SafeUrlSchema, OAuthMetadataSchema, OpenIdProviderMetadataSchema, OAuthClientMetadataSchema } from './auth.js'; +import { + SafeUrlSchema, + OAuthMetadataSchema, + OpenIdProviderMetadataSchema, + OAuthClientMetadataSchema, + OptionalSafeUrlSchema +} from './auth.js'; describe('SafeUrlSchema', () => { it('accepts valid HTTPS URLs', () => { @@ -26,6 +32,12 @@ describe('SafeUrlSchema', () => { }); }); +describe('OptionalSafeUrlSchema', () => { + it('accepts empty string and transforms it to undefined', () => { + expect(OptionalSafeUrlSchema.parse('')).toBe(undefined); + }); +}); + describe('OAuthMetadataSchema', () => { it('validates complete OAuth metadata', () => { const metadata = { diff --git a/src/shared/auth.ts b/src/shared/auth.ts index 0e079646..c5ddbda1 100644 --- a/src/shared/auth.ts +++ b/src/shared/auth.ts @@ -151,6 +151,11 @@ export const OAuthErrorResponseSchema = z.object({ error_uri: z.string().optional() }); +/** + * Optional version of SafeUrlSchema that allows empty string for retrocompatibility on tos_uri and logo_uri + */ +export const OptionalSafeUrlSchema = SafeUrlSchema.optional().or(z.literal('').transform(() => undefined)); + /** * RFC 7591 OAuth 2.0 Dynamic Client Registration metadata */ @@ -162,10 +167,10 @@ export const OAuthClientMetadataSchema = z response_types: z.array(z.string()).optional(), client_name: z.string().optional(), client_uri: SafeUrlSchema.optional(), - logo_uri: SafeUrlSchema.optional(), + logo_uri: OptionalSafeUrlSchema, scope: z.string().optional(), contacts: z.array(z.string()).optional(), - tos_uri: SafeUrlSchema.optional(), + tos_uri: OptionalSafeUrlSchema, policy_uri: z.string().optional(), jwks_uri: SafeUrlSchema.optional(), jwks: z.any().optional(),