@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.
- package/CHANGELOG.md +10 -0
- package/dist/atproto-loopback-client-metadata.d.ts +4 -1
- package/dist/atproto-loopback-client-metadata.d.ts.map +1 -1
- package/dist/atproto-loopback-client-metadata.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/oauth-authorization-code-grant-token-request.d.ts +2 -2
- package/dist/oauth-authorization-code-grant-token-request.d.ts.map +1 -1
- package/dist/oauth-authorization-code-grant-token-request.js +2 -1
- package/dist/oauth-authorization-code-grant-token-request.js.map +1 -1
- package/dist/oauth-authorization-details.d.ts +42 -4
- package/dist/oauth-authorization-details.d.ts.map +1 -1
- package/dist/oauth-authorization-details.js +21 -1
- package/dist/oauth-authorization-details.js.map +1 -1
- package/dist/oauth-authorization-request-jar.d.ts +1 -1
- package/dist/oauth-authorization-request-par.d.ts +8 -8
- package/dist/oauth-authorization-request-parameters.d.ts +7 -7
- package/dist/oauth-authorization-request-parameters.d.ts.map +1 -1
- package/dist/oauth-authorization-request-parameters.js +2 -1
- package/dist/oauth-authorization-request-parameters.js.map +1 -1
- package/dist/oauth-authorization-request-query.d.ts +8 -8
- package/dist/oauth-authorization-server-metadata.d.ts +69 -66
- package/dist/oauth-authorization-server-metadata.d.ts.map +1 -1
- package/dist/oauth-authorization-server-metadata.js +14 -10
- package/dist/oauth-authorization-server-metadata.js.map +1 -1
- package/dist/oauth-client-id-discoverable.d.ts +3 -2
- package/dist/oauth-client-id-discoverable.d.ts.map +1 -1
- package/dist/oauth-client-id-discoverable.js +52 -28
- package/dist/oauth-client-id-discoverable.js.map +1 -1
- package/dist/oauth-client-id-loopback.d.ts +5 -5
- package/dist/oauth-client-id-loopback.d.ts.map +1 -1
- package/dist/oauth-client-id-loopback.js +29 -27
- package/dist/oauth-client-id-loopback.js.map +1 -1
- package/dist/oauth-client-metadata.d.ts +22 -12
- package/dist/oauth-client-metadata.d.ts.map +1 -1
- package/dist/oauth-client-metadata.js +18 -8
- package/dist/oauth-client-metadata.js.map +1 -1
- package/dist/oauth-issuer-identifier.d.ts +1 -1
- package/dist/oauth-issuer-identifier.d.ts.map +1 -1
- package/dist/oauth-issuer-identifier.js +3 -19
- package/dist/oauth-issuer-identifier.js.map +1 -1
- package/dist/oauth-protected-resource-metadata.d.ts +15 -12
- package/dist/oauth-protected-resource-metadata.d.ts.map +1 -1
- package/dist/oauth-protected-resource-metadata.js +15 -5
- package/dist/oauth-protected-resource-metadata.js.map +1 -1
- package/dist/oauth-redirect-uri.d.ts +10 -0
- package/dist/oauth-redirect-uri.d.ts.map +1 -0
- package/dist/oauth-redirect-uri.js +35 -0
- package/dist/oauth-redirect-uri.js.map +1 -0
- package/dist/oauth-token-request.d.ts +2 -2
- package/dist/oauth-token-response.d.ts +6 -6
- package/dist/uri.d.ts +20 -0
- package/dist/uri.d.ts.map +1 -0
- package/dist/uri.js +127 -0
- package/dist/uri.js.map +1 -0
- package/package.json +1 -1
- package/src/atproto-loopback-client-metadata.ts +8 -3
- package/src/index.ts +2 -0
- package/src/oauth-authorization-code-grant-token-request.ts +2 -1
- package/src/oauth-authorization-details.ts +21 -1
- package/src/oauth-authorization-request-parameters.ts +2 -1
- package/src/oauth-authorization-server-metadata.ts +14 -10
- package/src/oauth-client-id-discoverable.ts +69 -51
- package/src/oauth-client-id-loopback.ts +40 -40
- package/src/oauth-client-metadata.ts +18 -8
- package/src/oauth-issuer-identifier.ts +6 -21
- package/src/oauth-protected-resource-metadata.ts +15 -5
- package/src/oauth-redirect-uri.ts +56 -0
- package/src/uri.ts +171 -0
- package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,9 +1,8 @@
|
|
1
1
|
import { z } from 'zod'
|
2
|
-
import {
|
2
|
+
import { webUriSchema } from './uri.js'
|
3
3
|
|
4
|
-
export const oauthIssuerIdentifierSchema =
|
5
|
-
|
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 =
|
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:
|
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:
|
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:
|
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:
|
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:
|
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"}
|