From 54f643cee8ddedce47fb611c218154033221003e Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Mon, 17 Feb 2025 14:46:08 +0000 Subject: [PATCH] Validate redirect URIs _as_ URIs --- src/server/auth/handlers/authorize.ts | 5 +++-- src/server/auth/validation.ts | 8 ++++++++ src/shared/auth.ts | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/server/auth/validation.ts diff --git a/src/server/auth/handlers/authorize.ts b/src/server/auth/handlers/authorize.ts index 1f29b0bb..db1da2db 100644 --- a/src/server/auth/handlers/authorize.ts +++ b/src/server/auth/handlers/authorize.ts @@ -1,6 +1,7 @@ import { RequestHandler } from "express"; import { z } from "zod"; import { OAuthRegisteredClientsStore } from "../clients.js"; +import { isValidUrl } from "../validation.js"; export type AuthorizationHandlerOptions = { /** @@ -11,7 +12,7 @@ export type AuthorizationHandlerOptions = { const ClientAuthorizationParamsSchema = z.object({ client_id: z.string(), - redirect_uri: z.string().optional(), + redirect_uri: z.string().optional().refine((value) => value === undefined || isValidUrl(value), { message: "redirect_uri must be a valid URL" }), }); const RequestAuthorizationParamsSchema = z.object({ @@ -45,7 +46,7 @@ export function authorizationHandler({ store }: AuthorizationHandlerOptions): Re if (redirect_uri !== undefined) { if (!client.redirect_uris.includes(redirect_uri)) { - res.status(400).end("Bad Request: invalid redirect_uri"); + res.status(400).end("Bad Request: unregistered redirect_uri"); return; } } else if (client.redirect_uris.length === 1) { diff --git a/src/server/auth/validation.ts b/src/server/auth/validation.ts new file mode 100644 index 00000000..30bc5e85 --- /dev/null +++ b/src/server/auth/validation.ts @@ -0,0 +1,8 @@ +export function isValidUrl(s: string): boolean { + try { + new URL(s); + return true; + } catch (error) { + return false; + } +} \ No newline at end of file diff --git a/src/shared/auth.ts b/src/shared/auth.ts index fd4a9daa..e1e65067 100644 --- a/src/shared/auth.ts +++ b/src/shared/auth.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { isValidUrl } from "../server/auth/validation.js"; /** * RFC 8414 OAuth 2.0 Authorization Server Metadata @@ -61,7 +62,7 @@ export const OAuthErrorSchema = z * RFC 7591 OAuth 2.0 Dynamic Client Registration metadata */ export const OAuthClientMetadataSchema = z.object({ - redirect_uris: z.array(z.string()), + redirect_uris: z.array(z.string()).refine((uris) => uris.every(isValidUrl), { message: "redirect_uris must contain valid URLs" }), token_endpoint_auth_method: z.string().optional(), grant_types: z.array(z.string()).optional(), response_types: z.array(z.string()).optional(),