From 8122c1755cae4d4027c9f6656fa44aedcc498bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Wed, 1 Oct 2025 11:25:50 +0100 Subject: [PATCH 1/2] Allow empty string as valid URL in DCR workflow --- src/shared/auth.test.ts | 5 ++++- src/shared/auth.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/shared/auth.test.ts b/src/shared/auth.test.ts index 41f9dc1a9..2da1c5298 100644 --- a/src/shared/auth.test.ts +++ b/src/shared/auth.test.ts @@ -18,12 +18,15 @@ describe('SafeUrlSchema', () => { it('rejects invalid URLs', () => { expect(() => SafeUrlSchema.parse('not-a-url')).toThrow(); - expect(() => SafeUrlSchema.parse('')).toThrow(); }); it('works with safeParse', () => { expect(() => SafeUrlSchema.safeParse('not-a-url')).not.toThrow(); }); + + it('works with empty string', () => { + expect(() => SafeUrlSchema.parse('')).not.toThrow(); + }); }); describe('OAuthMetadataSchema', () => { diff --git a/src/shared/auth.ts b/src/shared/auth.ts index 0e079646b..431a8a138 100644 --- a/src/shared/auth.ts +++ b/src/shared/auth.ts @@ -23,7 +23,8 @@ export const SafeUrlSchema = z return u.protocol !== 'javascript:' && u.protocol !== 'data:' && u.protocol !== 'vbscript:'; }, { message: 'URL cannot use javascript:, data:, or vbscript: scheme' } - ); + ) + .or(z.literal('')); /** * RFC 9728 OAuth Protected Resource Metadata From f2ffcbd84cd5928ebdf89c1af28b789efde64554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Mon, 6 Oct 2025 14:41:38 +0200 Subject: [PATCH 2/2] Add a new OptionalSafeUrlSchema just for retrocompatibility on existing clients --- src/shared/auth.test.ts | 15 ++++++++++++--- src/shared/auth.ts | 12 ++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/shared/auth.test.ts b/src/shared/auth.test.ts index 2da1c5298..71877f341 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', () => { @@ -18,14 +24,17 @@ describe('SafeUrlSchema', () => { it('rejects invalid URLs', () => { expect(() => SafeUrlSchema.parse('not-a-url')).toThrow(); + expect(() => SafeUrlSchema.parse('')).toThrow(); }); it('works with safeParse', () => { expect(() => SafeUrlSchema.safeParse('not-a-url')).not.toThrow(); }); +}); - it('works with empty string', () => { - expect(() => SafeUrlSchema.parse('')).not.toThrow(); +describe('OptionalSafeUrlSchema', () => { + it('accepts empty string and transforms it to undefined', () => { + expect(OptionalSafeUrlSchema.parse('')).toBe(undefined); }); }); diff --git a/src/shared/auth.ts b/src/shared/auth.ts index 431a8a138..c5ddbda16 100644 --- a/src/shared/auth.ts +++ b/src/shared/auth.ts @@ -23,8 +23,7 @@ export const SafeUrlSchema = z return u.protocol !== 'javascript:' && u.protocol !== 'data:' && u.protocol !== 'vbscript:'; }, { message: 'URL cannot use javascript:, data:, or vbscript: scheme' } - ) - .or(z.literal('')); + ); /** * RFC 9728 OAuth Protected Resource Metadata @@ -152,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 */ @@ -163,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(),