@atproto/oauth-types 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -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 +2 -2
- 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"}
|