1
1
import { z } from "zod" ;
2
2
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
+
3
26
/**
4
27
* RFC 9728 OAuth Protected Resource Metadata
5
28
*/
6
29
export const OAuthProtectedResourceMetadataSchema = z
7
30
. object ( {
8
31
resource : z . string ( ) . url ( ) ,
9
- authorization_servers : z . array ( z . string ( ) . url ( ) ) . optional ( ) ,
32
+ authorization_servers : z . array ( SafeUrlSchema ) . optional ( ) ,
10
33
jwks_uri : z . string ( ) . url ( ) . optional ( ) ,
11
34
scopes_supported : z . array ( z . string ( ) ) . optional ( ) ,
12
35
bearer_methods_supported : z . array ( z . string ( ) ) . optional ( ) ,
@@ -28,9 +51,9 @@ export const OAuthProtectedResourceMetadataSchema = z
28
51
export const OAuthMetadataSchema = z
29
52
. object ( {
30
53
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 ( ) ,
34
57
scopes_supported : z . array ( z . string ( ) ) . optional ( ) ,
35
58
response_types_supported : z . array ( z . string ( ) ) ,
36
59
response_modes_supported : z . array ( z . string ( ) ) . optional ( ) ,
@@ -39,8 +62,8 @@ export const OAuthMetadataSchema = z
39
62
token_endpoint_auth_signing_alg_values_supported : z
40
63
. array ( z . string ( ) )
41
64
. optional ( ) ,
42
- service_documentation : z . string ( ) . optional ( ) ,
43
- revocation_endpoint : z . string ( ) . optional ( ) ,
65
+ service_documentation : SafeUrlSchema . optional ( ) ,
66
+ revocation_endpoint : SafeUrlSchema . optional ( ) ,
44
67
revocation_endpoint_auth_methods_supported : z . array ( z . string ( ) ) . optional ( ) ,
45
68
revocation_endpoint_auth_signing_alg_values_supported : z
46
69
. array ( z . string ( ) )
@@ -63,11 +86,11 @@ export const OAuthMetadataSchema = z
63
86
export const OpenIdProviderMetadataSchema = z
64
87
. object ( {
65
88
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 ( ) ,
71
94
scopes_supported : z . array ( z . string ( ) ) . optional ( ) ,
72
95
response_types_supported : z . array ( z . string ( ) ) ,
73
96
response_modes_supported : z . array ( z . string ( ) ) . optional ( ) ,
@@ -101,8 +124,8 @@ export const OpenIdProviderMetadataSchema = z
101
124
request_parameter_supported : z . boolean ( ) . optional ( ) ,
102
125
request_uri_parameter_supported : z . boolean ( ) . optional ( ) ,
103
126
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 ( ) ,
106
129
} )
107
130
. passthrough ( ) ;
108
131
@@ -146,18 +169,18 @@ export const OAuthErrorResponseSchema = z
146
169
* RFC 7591 OAuth 2.0 Dynamic Client Registration metadata
147
170
*/
148
171
export 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 ) ,
150
173
token_endpoint_auth_method : z . string ( ) . optional ( ) ,
151
174
grant_types : z . array ( z . string ( ) ) . optional ( ) ,
152
175
response_types : z . array ( z . string ( ) ) . optional ( ) ,
153
176
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 ( ) ,
156
179
scope : z . string ( ) . optional ( ) ,
157
180
contacts : z . array ( z . string ( ) ) . optional ( ) ,
158
- tos_uri : z . string ( ) . optional ( ) ,
181
+ tos_uri : SafeUrlSchema . optional ( ) ,
159
182
policy_uri : z . string ( ) . optional ( ) ,
160
- jwks_uri : z . string ( ) . optional ( ) ,
183
+ jwks_uri : SafeUrlSchema . optional ( ) ,
161
184
jwks : z . any ( ) . optional ( ) ,
162
185
software_id : z . string ( ) . optional ( ) ,
163
186
software_version : z . string ( ) . optional ( ) ,
0 commit comments