11import { z } from "zod" ;
22
3+ /**
4+ * Reusable URL validation that disallows javascript: scheme
5+ */
6+ export const SafeUrlSchema = z . string ( ) . url ( )
7+ . superRefine ( ( val , ctx ) => {
8+ if ( ! URL . canParse ( val ) ) {
9+ ctx . addIssue ( {
10+ code : z . ZodIssueCode . custom ,
11+ message : "URL must be parseable" ,
12+ fatal : true ,
13+ } ) ;
14+
15+ return z . NEVER ;
16+ }
17+ } ) . refine (
18+ ( url ) => {
19+ const u = new URL ( url ) ;
20+ return u . protocol !== 'javascript:' && u . protocol !== 'data:' && u . protocol !== 'vbscript:' ;
21+ } ,
22+ { message : "URL cannot use javascript:, data:, or vbscript: scheme" }
23+ ) ;
24+
25+
326/**
427 * RFC 9728 OAuth Protected Resource Metadata
528 */
629export const OAuthProtectedResourceMetadataSchema = z
730 . object ( {
831 resource : z . string ( ) . url ( ) ,
9- authorization_servers : z . array ( z . string ( ) . url ( ) ) . optional ( ) ,
32+ authorization_servers : z . array ( SafeUrlSchema ) . optional ( ) ,
1033 jwks_uri : z . string ( ) . url ( ) . optional ( ) ,
1134 scopes_supported : z . array ( z . string ( ) ) . optional ( ) ,
1235 bearer_methods_supported : z . array ( z . string ( ) ) . optional ( ) ,
@@ -28,9 +51,9 @@ export const OAuthProtectedResourceMetadataSchema = z
2851export const OAuthMetadataSchema = z
2952 . object ( {
3053 issuer : z . string ( ) ,
31- authorization_endpoint : z . string ( ) ,
32- token_endpoint : z . string ( ) ,
33- registration_endpoint : z . string ( ) . optional ( ) ,
54+ authorization_endpoint : SafeUrlSchema ,
55+ token_endpoint : SafeUrlSchema ,
56+ registration_endpoint : SafeUrlSchema . optional ( ) ,
3457 scopes_supported : z . array ( z . string ( ) ) . optional ( ) ,
3558 response_types_supported : z . array ( z . string ( ) ) ,
3659 response_modes_supported : z . array ( z . string ( ) ) . optional ( ) ,
@@ -39,8 +62,8 @@ export const OAuthMetadataSchema = z
3962 token_endpoint_auth_signing_alg_values_supported : z
4063 . array ( z . string ( ) )
4164 . optional ( ) ,
42- service_documentation : z . string ( ) . optional ( ) ,
43- revocation_endpoint : z . string ( ) . optional ( ) ,
65+ service_documentation : SafeUrlSchema . optional ( ) ,
66+ revocation_endpoint : SafeUrlSchema . optional ( ) ,
4467 revocation_endpoint_auth_methods_supported : z . array ( z . string ( ) ) . optional ( ) ,
4568 revocation_endpoint_auth_signing_alg_values_supported : z
4669 . array ( z . string ( ) )
@@ -63,11 +86,11 @@ export const OAuthMetadataSchema = z
6386export const OpenIdProviderMetadataSchema = z
6487 . object ( {
6588 issuer : z . string ( ) ,
66- authorization_endpoint : z . string ( ) ,
67- token_endpoint : z . string ( ) ,
68- userinfo_endpoint : z . string ( ) . optional ( ) ,
69- jwks_uri : z . string ( ) ,
70- registration_endpoint : z . string ( ) . optional ( ) ,
89+ authorization_endpoint : SafeUrlSchema ,
90+ token_endpoint : SafeUrlSchema ,
91+ userinfo_endpoint : SafeUrlSchema . optional ( ) ,
92+ jwks_uri : SafeUrlSchema ,
93+ registration_endpoint : SafeUrlSchema . optional ( ) ,
7194 scopes_supported : z . array ( z . string ( ) ) . optional ( ) ,
7295 response_types_supported : z . array ( z . string ( ) ) ,
7396 response_modes_supported : z . array ( z . string ( ) ) . optional ( ) ,
@@ -101,8 +124,8 @@ export const OpenIdProviderMetadataSchema = z
101124 request_parameter_supported : z . boolean ( ) . optional ( ) ,
102125 request_uri_parameter_supported : z . boolean ( ) . optional ( ) ,
103126 require_request_uri_registration : z . boolean ( ) . optional ( ) ,
104- op_policy_uri : z . string ( ) . optional ( ) ,
105- op_tos_uri : z . string ( ) . optional ( ) ,
127+ op_policy_uri : SafeUrlSchema . optional ( ) ,
128+ op_tos_uri : SafeUrlSchema . optional ( ) ,
106129 } )
107130 . passthrough ( ) ;
108131
@@ -146,18 +169,18 @@ export const OAuthErrorResponseSchema = z
146169 * RFC 7591 OAuth 2.0 Dynamic Client Registration metadata
147170 */
148171export const OAuthClientMetadataSchema = z . object ( {
149- redirect_uris : z . array ( z . string ( ) ) . refine ( ( uris ) => uris . every ( ( uri ) => URL . canParse ( uri ) ) , { message : "redirect_uris must contain valid URLs" } ) ,
172+ redirect_uris : z . array ( SafeUrlSchema ) ,
150173 token_endpoint_auth_method : z . string ( ) . optional ( ) ,
151174 grant_types : z . array ( z . string ( ) ) . optional ( ) ,
152175 response_types : z . array ( z . string ( ) ) . optional ( ) ,
153176 client_name : z . string ( ) . optional ( ) ,
154- client_uri : z . string ( ) . optional ( ) ,
155- logo_uri : z . string ( ) . optional ( ) ,
177+ client_uri : SafeUrlSchema . optional ( ) ,
178+ logo_uri : SafeUrlSchema . optional ( ) ,
156179 scope : z . string ( ) . optional ( ) ,
157180 contacts : z . array ( z . string ( ) ) . optional ( ) ,
158- tos_uri : z . string ( ) . optional ( ) ,
181+ tos_uri : SafeUrlSchema . optional ( ) ,
159182 policy_uri : z . string ( ) . optional ( ) ,
160- jwks_uri : z . string ( ) . optional ( ) ,
183+ jwks_uri : SafeUrlSchema . optional ( ) ,
161184 jwks : z . any ( ) . optional ( ) ,
162185 software_id : z . string ( ) . optional ( ) ,
163186 software_version : z . string ( ) . optional ( ) ,
0 commit comments