@atproto/oauth-types 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/atproto-loopback-client-metadata.d.ts +4 -1
  3. package/dist/atproto-loopback-client-metadata.d.ts.map +1 -1
  4. package/dist/atproto-loopback-client-metadata.js.map +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/oauth-authorization-code-grant-token-request.d.ts +2 -2
  10. package/dist/oauth-authorization-code-grant-token-request.d.ts.map +1 -1
  11. package/dist/oauth-authorization-code-grant-token-request.js +2 -1
  12. package/dist/oauth-authorization-code-grant-token-request.js.map +1 -1
  13. package/dist/oauth-authorization-details.d.ts +42 -4
  14. package/dist/oauth-authorization-details.d.ts.map +1 -1
  15. package/dist/oauth-authorization-details.js +21 -1
  16. package/dist/oauth-authorization-details.js.map +1 -1
  17. package/dist/oauth-authorization-request-jar.d.ts +1 -1
  18. package/dist/oauth-authorization-request-par.d.ts +8 -8
  19. package/dist/oauth-authorization-request-parameters.d.ts +7 -7
  20. package/dist/oauth-authorization-request-parameters.d.ts.map +1 -1
  21. package/dist/oauth-authorization-request-parameters.js +2 -1
  22. package/dist/oauth-authorization-request-parameters.js.map +1 -1
  23. package/dist/oauth-authorization-request-query.d.ts +8 -8
  24. package/dist/oauth-authorization-server-metadata.d.ts +69 -66
  25. package/dist/oauth-authorization-server-metadata.d.ts.map +1 -1
  26. package/dist/oauth-authorization-server-metadata.js +14 -10
  27. package/dist/oauth-authorization-server-metadata.js.map +1 -1
  28. package/dist/oauth-client-id-discoverable.d.ts +3 -2
  29. package/dist/oauth-client-id-discoverable.d.ts.map +1 -1
  30. package/dist/oauth-client-id-discoverable.js +52 -28
  31. package/dist/oauth-client-id-discoverable.js.map +1 -1
  32. package/dist/oauth-client-id-loopback.d.ts +5 -5
  33. package/dist/oauth-client-id-loopback.d.ts.map +1 -1
  34. package/dist/oauth-client-id-loopback.js +29 -27
  35. package/dist/oauth-client-id-loopback.js.map +1 -1
  36. package/dist/oauth-client-metadata.d.ts +22 -12
  37. package/dist/oauth-client-metadata.d.ts.map +1 -1
  38. package/dist/oauth-client-metadata.js +18 -8
  39. package/dist/oauth-client-metadata.js.map +1 -1
  40. package/dist/oauth-issuer-identifier.d.ts +1 -1
  41. package/dist/oauth-issuer-identifier.d.ts.map +1 -1
  42. package/dist/oauth-issuer-identifier.js +3 -19
  43. package/dist/oauth-issuer-identifier.js.map +1 -1
  44. package/dist/oauth-protected-resource-metadata.d.ts +15 -12
  45. package/dist/oauth-protected-resource-metadata.d.ts.map +1 -1
  46. package/dist/oauth-protected-resource-metadata.js +15 -5
  47. package/dist/oauth-protected-resource-metadata.js.map +1 -1
  48. package/dist/oauth-redirect-uri.d.ts +10 -0
  49. package/dist/oauth-redirect-uri.d.ts.map +1 -0
  50. package/dist/oauth-redirect-uri.js +35 -0
  51. package/dist/oauth-redirect-uri.js.map +1 -0
  52. package/dist/oauth-token-request.d.ts +2 -2
  53. package/dist/oauth-token-response.d.ts +6 -6
  54. package/dist/uri.d.ts +20 -0
  55. package/dist/uri.d.ts.map +1 -0
  56. package/dist/uri.js +127 -0
  57. package/dist/uri.js.map +1 -0
  58. package/package.json +1 -1
  59. package/src/atproto-loopback-client-metadata.ts +8 -3
  60. package/src/index.ts +2 -0
  61. package/src/oauth-authorization-code-grant-token-request.ts +2 -1
  62. package/src/oauth-authorization-details.ts +21 -1
  63. package/src/oauth-authorization-request-parameters.ts +2 -1
  64. package/src/oauth-authorization-server-metadata.ts +14 -10
  65. package/src/oauth-client-id-discoverable.ts +69 -51
  66. package/src/oauth-client-id-loopback.ts +40 -40
  67. package/src/oauth-client-metadata.ts +18 -8
  68. package/src/oauth-issuer-identifier.ts +6 -21
  69. package/src/oauth-protected-resource-metadata.ts +15 -5
  70. package/src/oauth-redirect-uri.ts +56 -0
  71. package/src/uri.ts +171 -0
  72. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,9 +1,8 @@
1
1
  import { z } from 'zod'
2
- import { safeUrl } from './util.js'
2
+ import { webUriSchema } from './uri.js'
3
3
 
4
- export const oauthIssuerIdentifierSchema = z
5
- .string()
6
- .superRefine((value, ctx): value is `${'http' | 'https'}://${string}` => {
4
+ export const oauthIssuerIdentifierSchema = webUriSchema.superRefine(
5
+ (value, ctx) => {
7
6
  // Validate the issuer (MIX-UP attacks)
8
7
 
9
8
  if (value.endsWith('/')) {
@@ -14,22 +13,7 @@ export const oauthIssuerIdentifierSchema = z
14
13
  return false
15
14
  }
16
15
 
17
- const url = safeUrl(value)
18
- if (!url) {
19
- ctx.addIssue({
20
- code: z.ZodIssueCode.custom,
21
- message: 'Invalid url',
22
- })
23
- return false
24
- }
25
-
26
- if (url.protocol !== 'https:' && url.protocol !== 'http:') {
27
- ctx.addIssue({
28
- code: z.ZodIssueCode.custom,
29
- message: `Invalid issuer URL protocol "${url.protocol}"`,
30
- })
31
- return false
32
- }
16
+ const url = new URL(value)
33
17
 
34
18
  if (url.username || url.password) {
35
19
  ctx.addIssue({
@@ -57,6 +41,7 @@ export const oauthIssuerIdentifierSchema = z
57
41
  }
58
42
 
59
43
  return true
60
- })
44
+ },
45
+ )
61
46
 
62
47
  export type OAuthIssuerIdentifier = z.infer<typeof oauthIssuerIdentifierSchema>
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod'
2
2
 
3
3
  import { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'
4
+ import { webUriSchema } from './uri.js'
4
5
 
5
6
  /**
6
7
  * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#name-protected-resource-metadata-r}
@@ -10,8 +11,17 @@ export const oauthProtectedResourceMetadataSchema = z.object({
10
11
  * REQUIRED. The protected resource's resource identifier, which is a URL that
11
12
  * uses the https scheme and has no query or fragment components. Using these
12
13
  * well-known resources is described in Section 3.
14
+ *
15
+ * @note This schema allows non https URLs for testing & development purposes.
16
+ * Make sure to validate the URL before using it in a production environment.
13
17
  */
14
- resource: z.string().url(),
18
+ resource: webUriSchema
19
+ .refine((url) => !url.includes('?'), {
20
+ message: 'Resource URL must not contain query parameters',
21
+ })
22
+ .refine((url) => !url.includes('#'), {
23
+ message: 'Resource URL must not contain a fragment',
24
+ }),
15
25
 
16
26
  /**
17
27
  * OPTIONAL. JSON array containing a list of OAuth authorization server issuer
@@ -31,7 +41,7 @@ export const oauthProtectedResourceMetadataSchema = z.object({
31
41
  * available, a use (public key use) parameter value is REQUIRED for all keys
32
42
  * in the referenced JWK Set to indicate each key's intended usage.
33
43
  */
34
- jwks_uri: z.string().url().optional(),
44
+ jwks_uri: webUriSchema.optional(),
35
45
 
36
46
  /**
37
47
  * RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope
@@ -64,20 +74,20 @@ export const oauthProtectedResourceMetadataSchema = z.object({
64
74
  * OPTIONAL. URL of a page containing human-readable information that
65
75
  * developers might want or need to know when using the protected resource
66
76
  */
67
- resource_documentation: z.string().url().optional(),
77
+ resource_documentation: webUriSchema.optional(),
68
78
 
69
79
  /**
70
80
  * OPTIONAL. URL that the protected resource provides to read about the
71
81
  * protected resource's requirements on how the client can use the data
72
82
  * provided by the protected resource
73
83
  */
74
- resource_policy_uri: z.string().url().optional(),
84
+ resource_policy_uri: webUriSchema.optional(),
75
85
 
76
86
  /**
77
87
  * OPTIONAL. URL that the protected resource provides to read about the
78
88
  * protected resource's terms of service
79
89
  */
80
- resource_tos_uri: z.string().url().optional(),
90
+ resource_tos_uri: webUriSchema.optional(),
81
91
  })
82
92
 
83
93
  export type OAuthProtectedResourceMetadata = z.infer<
@@ -0,0 +1,56 @@
1
+ import { TypeOf, z, ZodIssueCode } from 'zod'
2
+ import {
3
+ httpsUriSchema,
4
+ LoopbackUri,
5
+ loopbackUriSchema,
6
+ privateUseUriSchema,
7
+ } from './uri.js'
8
+
9
+ export const oauthLoopbackRedirectURISchema = loopbackUriSchema.superRefine(
10
+ (value, ctx): value is Exclude<LoopbackUri, `http://localhost${string}`> => {
11
+ if (value.startsWith('http://localhost')) {
12
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-8.3
13
+ //
14
+ // > While redirect URIs using localhost (i.e.,
15
+ // > "http://localhost:{port}/{path}") function similarly to loopback IP
16
+ // > redirects described in Section 7.3, the use of localhost is NOT
17
+ // > RECOMMENDED. Specifying a redirect URI with the loopback IP literal
18
+ // > rather than localhost avoids inadvertently listening on network
19
+ // > interfaces other than the loopback interface. It is also less
20
+ // > susceptible to client-side firewalls and misconfigured host name
21
+ // > resolution on the user's device.
22
+ ctx.addIssue({
23
+ code: ZodIssueCode.custom,
24
+ message:
25
+ 'Use of "localhost" hostname is not allowed (RFC 8252), use a loopback IP such as "127.0.0.1" instead',
26
+ })
27
+ return false
28
+ }
29
+
30
+ return true
31
+ },
32
+ )
33
+ export type OAuthLoopbackRedirectURI = TypeOf<
34
+ typeof oauthLoopbackRedirectURISchema
35
+ >
36
+
37
+ export const oauthHttpsRedirectURISchema = httpsUriSchema
38
+ export type OAuthHttpsRedirectURI = TypeOf<typeof oauthHttpsRedirectURISchema>
39
+
40
+ export const oauthPrivateUseRedirectURISchema = privateUseUriSchema
41
+ export type OAuthPrivateUseRedirectURI = TypeOf<
42
+ typeof oauthPrivateUseRedirectURISchema
43
+ >
44
+
45
+ export const oauthRedirectUriSchema = z.union(
46
+ [
47
+ oauthLoopbackRedirectURISchema,
48
+ oauthHttpsRedirectURISchema,
49
+ oauthPrivateUseRedirectURISchema,
50
+ ],
51
+ {
52
+ message: `URL must use the "https:" or "http:" protocol, or a private-use URI scheme (RFC 8252)`,
53
+ },
54
+ )
55
+
56
+ export type OAuthRedirectUri = TypeOf<typeof oauthRedirectUriSchema>
package/src/uri.ts ADDED
@@ -0,0 +1,171 @@
1
+ import { TypeOf, z, ZodIssueCode } from 'zod'
2
+ import { isHostnameIP, isLoopbackHost } from './util.js'
3
+
4
+ /**
5
+ * Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).
6
+ *
7
+ * Any value that matches this schema is safe to parse using `new URL()`.
8
+ */
9
+ export const dangerousUriSchema = z
10
+ .string()
11
+ .refine(
12
+ (data): data is `${string}:${string}` =>
13
+ data.includes(':') && URL.canParse(data),
14
+ {
15
+ message: 'Invalid URL',
16
+ },
17
+ )
18
+
19
+ /**
20
+ * Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).
21
+ */
22
+ export type DangerousUrl = TypeOf<typeof dangerousUriSchema>
23
+
24
+ export const loopbackUriSchema = dangerousUriSchema.superRefine(
25
+ (
26
+ value,
27
+ ctx,
28
+ ): value is
29
+ | `http://[::1]${string}`
30
+ | `http://localhost${'' | `${':' | '/' | '?' | '#'}${string}`}`
31
+ | `http://127.0.0.1${'' | `${':' | '/' | '?' | '#'}${string}`}` => {
32
+ // Loopback url must use the "http:" protocol
33
+ if (!value.startsWith('http://')) {
34
+ ctx.addIssue({
35
+ code: ZodIssueCode.custom,
36
+ message: 'URL must use the "http:" protocol',
37
+ })
38
+ return false
39
+ }
40
+
41
+ const url = new URL(value)
42
+
43
+ if (!isLoopbackHost(url.hostname)) {
44
+ ctx.addIssue({
45
+ code: ZodIssueCode.custom,
46
+ message: 'URL must use "localhost", "127.0.0.1" or "[::1]" as hostname',
47
+ })
48
+ return false
49
+ }
50
+
51
+ return true
52
+ },
53
+ )
54
+
55
+ export type LoopbackUri = TypeOf<typeof loopbackUriSchema>
56
+
57
+ export const httpsUriSchema = dangerousUriSchema.superRefine(
58
+ (value, ctx): value is `https://${string}` => {
59
+ if (!value.startsWith('https://')) {
60
+ ctx.addIssue({
61
+ code: ZodIssueCode.custom,
62
+ message: 'URL must use the "https:" protocol',
63
+ })
64
+ return false
65
+ }
66
+
67
+ const url = new URL(value)
68
+
69
+ // Disallow loopback URLs with the `https:` protocol
70
+ if (isLoopbackHost(url.hostname)) {
71
+ ctx.addIssue({
72
+ code: ZodIssueCode.custom,
73
+ message: 'https: URL must not use a loopback host',
74
+ })
75
+ return false
76
+ }
77
+
78
+ if (isHostnameIP(url.hostname)) {
79
+ // Hostname is an IP address
80
+ } else {
81
+ // Hostname is a domain name
82
+ if (!url.hostname.includes('.')) {
83
+ // we don't depend on PSL here, so we only check for a dot
84
+ ctx.addIssue({
85
+ code: ZodIssueCode.custom,
86
+ message: 'Domain name must contain at least two segments',
87
+ })
88
+ return false
89
+ }
90
+
91
+ if (url.hostname.endsWith('.local')) {
92
+ ctx.addIssue({
93
+ code: ZodIssueCode.custom,
94
+ message: 'Domain name must not end with ".local"',
95
+ })
96
+ return false
97
+ }
98
+ }
99
+
100
+ return true
101
+ },
102
+ )
103
+
104
+ export type HttpsUri = TypeOf<typeof httpsUriSchema>
105
+
106
+ export const webUriSchema = z
107
+ .string()
108
+ .superRefine((value, ctx): value is LoopbackUri | HttpsUri => {
109
+ // discriminated union of `loopbackUriSchema` and `httpsUriSchema`
110
+ if (value.startsWith('http://')) {
111
+ const result = loopbackUriSchema.safeParse(value)
112
+ if (!result.success) result.error.issues.forEach(ctx.addIssue, ctx)
113
+ return result.success
114
+ }
115
+
116
+ if (value.startsWith('https://')) {
117
+ const result = httpsUriSchema.safeParse(value)
118
+ if (!result.success) result.error.issues.forEach(ctx.addIssue, ctx)
119
+ return result.success
120
+ }
121
+
122
+ ctx.addIssue({
123
+ code: ZodIssueCode.custom,
124
+ message: 'URL must use the "http:" or "https:" protocol',
125
+ })
126
+ return false
127
+ })
128
+
129
+ export type WebUri = TypeOf<typeof webUriSchema>
130
+
131
+ export const privateUseUriSchema = dangerousUriSchema.superRefine(
132
+ (value, ctx): value is `${string}.${string}:/${string}` => {
133
+ const dotIdx = value.indexOf('.')
134
+ const colonIdx = value.indexOf(':')
135
+
136
+ // Optimization: avoid parsing the URL if the protocol does not contain a "."
137
+ if (dotIdx === -1 || colonIdx === -1 || dotIdx > colonIdx) {
138
+ ctx.addIssue({
139
+ code: ZodIssueCode.custom,
140
+ message:
141
+ 'Private-use URI scheme requires a "." as part of the protocol',
142
+ })
143
+ return false
144
+ }
145
+
146
+ const url = new URL(value)
147
+
148
+ // Should be covered by the check before, but let's be extra sure
149
+ if (!url.protocol.includes('.')) {
150
+ ctx.addIssue({
151
+ code: ZodIssueCode.custom,
152
+ message: 'Invalid private-use URI scheme',
153
+ })
154
+ return false
155
+ }
156
+
157
+ if (url.hostname) {
158
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
159
+ ctx.addIssue({
160
+ code: ZodIssueCode.custom,
161
+ message:
162
+ 'Private-use URI schemes must not include a hostname (only one "/" is allowed after the protocol, as per RFC 8252)',
163
+ })
164
+ return false
165
+ }
166
+
167
+ return true
168
+ },
169
+ )
170
+
171
+ export type PrivateUseUri = TypeOf<typeof privateUseUriSchema>
@@ -1 +1 @@
1
- {"root":["./src/atproto-loopback-client-metadata.ts","./src/constants.ts","./src/index.ts","./src/oauth-access-token.ts","./src/oauth-authorization-code-grant-token-request.ts","./src/oauth-authorization-details.ts","./src/oauth-authorization-request-jar.ts","./src/oauth-authorization-request-par.ts","./src/oauth-authorization-request-parameters.ts","./src/oauth-authorization-request-query.ts","./src/oauth-authorization-request-uri.ts","./src/oauth-authorization-server-metadata.ts","./src/oauth-client-credentials-grant-token-request.ts","./src/oauth-client-credentials.ts","./src/oauth-client-id-discoverable.ts","./src/oauth-client-id-loopback.ts","./src/oauth-client-id.ts","./src/oauth-client-metadata.ts","./src/oauth-code-challenge-method.ts","./src/oauth-endpoint-auth-method.ts","./src/oauth-endpoint-name.ts","./src/oauth-grant-type.ts","./src/oauth-introspection-response.ts","./src/oauth-issuer-identifier.ts","./src/oauth-par-response.ts","./src/oauth-password-grant-token-request.ts","./src/oauth-protected-resource-metadata.ts","./src/oauth-refresh-token-grant-token-request.ts","./src/oauth-refresh-token.ts","./src/oauth-request-uri.ts","./src/oauth-response-mode.ts","./src/oauth-response-type.ts","./src/oauth-scope.ts","./src/oauth-token-identification.ts","./src/oauth-token-request.ts","./src/oauth-token-response.ts","./src/oauth-token-type.ts","./src/oidc-claims-parameter.ts","./src/oidc-claims-properties.ts","./src/oidc-entity-type.ts","./src/util.ts"],"version":"5.6.3"}
1
+ {"root":["./src/atproto-loopback-client-metadata.ts","./src/constants.ts","./src/index.ts","./src/oauth-access-token.ts","./src/oauth-authorization-code-grant-token-request.ts","./src/oauth-authorization-details.ts","./src/oauth-authorization-request-jar.ts","./src/oauth-authorization-request-par.ts","./src/oauth-authorization-request-parameters.ts","./src/oauth-authorization-request-query.ts","./src/oauth-authorization-request-uri.ts","./src/oauth-authorization-server-metadata.ts","./src/oauth-client-credentials-grant-token-request.ts","./src/oauth-client-credentials.ts","./src/oauth-client-id-discoverable.ts","./src/oauth-client-id-loopback.ts","./src/oauth-client-id.ts","./src/oauth-client-metadata.ts","./src/oauth-code-challenge-method.ts","./src/oauth-endpoint-auth-method.ts","./src/oauth-endpoint-name.ts","./src/oauth-grant-type.ts","./src/oauth-introspection-response.ts","./src/oauth-issuer-identifier.ts","./src/oauth-par-response.ts","./src/oauth-password-grant-token-request.ts","./src/oauth-protected-resource-metadata.ts","./src/oauth-redirect-uri.ts","./src/oauth-refresh-token-grant-token-request.ts","./src/oauth-refresh-token.ts","./src/oauth-request-uri.ts","./src/oauth-response-mode.ts","./src/oauth-response-type.ts","./src/oauth-scope.ts","./src/oauth-token-identification.ts","./src/oauth-token-request.ts","./src/oauth-token-response.ts","./src/oauth-token-type.ts","./src/oidc-claims-parameter.ts","./src/oidc-claims-properties.ts","./src/oidc-entity-type.ts","./src/uri.ts","./src/util.ts"],"version":"5.6.3"}