@atproto/oauth-types 0.1.5 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +28 -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 +1 -2
- package/dist/atproto-loopback-client-metadata.js.map +1 -1
- package/dist/constants.d.ts +0 -6
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -17
- package/dist/constants.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 +11 -11
- package/dist/oauth-authorization-request-parameters.d.ts +10 -10
- package/dist/oauth-authorization-request-parameters.d.ts.map +1 -1
- package/dist/oauth-authorization-request-parameters.js +3 -2
- package/dist/oauth-authorization-request-parameters.js.map +1 -1
- package/dist/oauth-authorization-request-query.d.ts +11 -11
- 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 +54 -31
- 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 +32 -31
- package/dist/oauth-client-id-loopback.js.map +1 -1
- package/dist/oauth-client-metadata.d.ts +112 -102
- 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 +2 -1
- package/dist/oauth-issuer-identifier.d.ts.map +1 -1
- package/dist/oauth-issuer-identifier.js +8 -23
- 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-refresh-token-grant-token-request.d.ts +0 -3
- package/dist/oauth-refresh-token-grant-token-request.d.ts.map +1 -1
- package/dist/oauth-refresh-token-grant-token-request.js +0 -2
- package/dist/oauth-refresh-token-grant-token-request.js.map +1 -1
- package/dist/oauth-token-request.d.ts +2 -5
- package/dist/oauth-token-request.d.ts.map +1 -1
- package/dist/oauth-token-response.d.ts +9 -12
- package/dist/oauth-token-response.d.ts.map +1 -1
- package/dist/oauth-token-response.js +4 -2
- package/dist/oauth-token-response.js.map +1 -1
- 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/dist/util.js +5 -6
- package/dist/util.js.map +1 -1
- package/package.json +2 -2
- package/src/atproto-loopback-client-metadata.ts +8 -3
- package/src/constants.ts +0 -16
- 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 +3 -2
- 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 +14 -24
- package/src/oauth-protected-resource-metadata.ts +15 -5
- package/src/oauth-redirect-uri.ts +56 -0
- package/src/oauth-refresh-token-grant-token-request.ts +0 -2
- package/src/oauth-token-response.ts +4 -2
- package/src/uri.ts +171 -0
- package/tsconfig.build.tsbuildinfo +1 -0
@@ -1,11 +1,33 @@
|
|
1
|
-
import {
|
1
|
+
import { TypeOf, ZodIssueCode } from 'zod'
|
2
|
+
import { oauthClientIdSchema } from './oauth-client-id.js'
|
3
|
+
import {
|
4
|
+
OAuthLoopbackRedirectURI,
|
5
|
+
oauthLoopbackRedirectURISchema,
|
6
|
+
OAuthRedirectUri,
|
7
|
+
} from './oauth-redirect-uri.js'
|
2
8
|
import { OAuthScope, oauthScopeSchema } from './oauth-scope.js'
|
3
|
-
import { isLoopbackHost, safeUrl } from './util.js'
|
4
9
|
|
5
|
-
const
|
10
|
+
const PREFIX = 'http://localhost'
|
6
11
|
|
7
|
-
export
|
8
|
-
`${typeof
|
12
|
+
export const oauthClientIdLoopbackSchema = oauthClientIdSchema.superRefine(
|
13
|
+
(value, ctx): value is `${typeof PREFIX}${'' | '/'}${'' | `?${string}`}` => {
|
14
|
+
try {
|
15
|
+
assertOAuthLoopbackClientId(value)
|
16
|
+
return true
|
17
|
+
} catch (error) {
|
18
|
+
ctx.addIssue({
|
19
|
+
code: ZodIssueCode.custom,
|
20
|
+
message:
|
21
|
+
error instanceof TypeError
|
22
|
+
? error.message
|
23
|
+
: 'Invalid loopback client ID',
|
24
|
+
})
|
25
|
+
return false
|
26
|
+
}
|
27
|
+
},
|
28
|
+
)
|
29
|
+
|
30
|
+
export type OAuthClientIdLoopback = TypeOf<typeof oauthClientIdLoopbackSchema>
|
9
31
|
|
10
32
|
export function isOAuthClientIdLoopback(
|
11
33
|
clientId: string,
|
@@ -28,21 +50,18 @@ export function assertOAuthLoopbackClientId(
|
|
28
50
|
// validation functions)
|
29
51
|
export function parseOAuthLoopbackClientId(clientId: string): {
|
30
52
|
scope?: OAuthScope
|
31
|
-
redirect_uris?: [
|
53
|
+
redirect_uris?: [OAuthRedirectUri, ...OAuthRedirectUri[]]
|
32
54
|
} {
|
33
|
-
if (!clientId.startsWith(
|
34
|
-
throw new TypeError(
|
35
|
-
|
36
|
-
)
|
37
|
-
} else if (clientId.includes('#', OAUTH_CLIENT_ID_LOOPBACK_URL.length)) {
|
55
|
+
if (!clientId.startsWith(PREFIX)) {
|
56
|
+
throw new TypeError(`Loopback ClientID must start with "${PREFIX}"`)
|
57
|
+
} else if (clientId.includes('#', PREFIX.length)) {
|
38
58
|
throw new TypeError('Loopback ClientID must not contain a hash component')
|
39
59
|
}
|
40
60
|
|
41
61
|
const queryStringIdx =
|
42
|
-
clientId.length >
|
43
|
-
|
44
|
-
|
45
|
-
: OAUTH_CLIENT_ID_LOOPBACK_URL.length
|
62
|
+
clientId.length > PREFIX.length && clientId[PREFIX.length] === '/'
|
63
|
+
? PREFIX.length + 1
|
64
|
+
: PREFIX.length
|
46
65
|
|
47
66
|
if (clientId.length === queryStringIdx) {
|
48
67
|
return {} // no query string to parse
|
@@ -72,33 +91,14 @@ export function parseOAuthLoopbackClientId(clientId: string): {
|
|
72
91
|
}
|
73
92
|
|
74
93
|
const redirect_uris = searchParams.has('redirect_uri')
|
75
|
-
? (searchParams
|
94
|
+
? (searchParams
|
95
|
+
.getAll('redirect_uri')
|
96
|
+
.map((value) => oauthLoopbackRedirectURISchema.parse(value)) as [
|
97
|
+
OAuthLoopbackRedirectURI,
|
98
|
+
...OAuthLoopbackRedirectURI[],
|
99
|
+
])
|
76
100
|
: undefined
|
77
101
|
|
78
|
-
if (redirect_uris) {
|
79
|
-
for (const uri of redirect_uris) {
|
80
|
-
const url = safeUrl(uri)
|
81
|
-
if (!url) {
|
82
|
-
throw new TypeError(`Invalid redirect_uri in client ID: ${uri}`)
|
83
|
-
}
|
84
|
-
if (url.protocol !== 'http:') {
|
85
|
-
throw new TypeError(
|
86
|
-
`Loopback ClientID must use "http:" redirect_uri's (got ${uri})`,
|
87
|
-
)
|
88
|
-
}
|
89
|
-
if (url.hostname === 'localhost') {
|
90
|
-
throw new TypeError(
|
91
|
-
`Loopback ClientID must not use "localhost" as redirect_uri hostname (got ${uri})`,
|
92
|
-
)
|
93
|
-
}
|
94
|
-
if (!isLoopbackHost(url.hostname)) {
|
95
|
-
throw new TypeError(
|
96
|
-
`Loopback ClientID must use loopback addresses as redirect_uri's (got ${uri})`,
|
97
|
-
)
|
98
|
-
}
|
99
|
-
}
|
100
|
-
}
|
101
|
-
|
102
102
|
return {
|
103
103
|
scope,
|
104
104
|
redirect_uris,
|
@@ -4,13 +4,23 @@ import { z } from 'zod'
|
|
4
4
|
import { oauthClientIdSchema } from './oauth-client-id.js'
|
5
5
|
import { oauthEndpointAuthMethod } from './oauth-endpoint-auth-method.js'
|
6
6
|
import { oauthGrantTypeSchema } from './oauth-grant-type.js'
|
7
|
+
import { oauthRedirectUriSchema } from './oauth-redirect-uri.js'
|
7
8
|
import { oauthResponseTypeSchema } from './oauth-response-type.js'
|
8
9
|
import { oauthScopeSchema } from './oauth-scope.js'
|
10
|
+
import { webUriSchema } from './uri.js'
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
/**
|
13
|
+
* @see {@link https://openid.net/specs/openid-connect-registration-1_0.html}
|
14
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7591}
|
15
|
+
* @note we do not enforce https: scheme in URIs to support development
|
16
|
+
* environments. Make sure to validate the URIs before using it in a production
|
17
|
+
* environment.
|
18
|
+
*/
|
12
19
|
export const oauthClientMetadataSchema = z.object({
|
13
|
-
|
20
|
+
/**
|
21
|
+
* @note redirect_uris require additional validation
|
22
|
+
*/
|
23
|
+
redirect_uris: z.array(oauthRedirectUriSchema).nonempty(),
|
14
24
|
response_types: z
|
15
25
|
.array(oauthResponseTypeSchema)
|
16
26
|
.nonempty()
|
@@ -30,7 +40,7 @@ export const oauthClientMetadataSchema = z.object({
|
|
30
40
|
token_endpoint_auth_signing_alg: z.string().optional(),
|
31
41
|
userinfo_signed_response_alg: z.string().optional(),
|
32
42
|
userinfo_encrypted_response_alg: z.string().optional(),
|
33
|
-
jwks_uri:
|
43
|
+
jwks_uri: webUriSchema.optional(),
|
34
44
|
jwks: jwksPubSchema.optional(),
|
35
45
|
application_type: z.enum(['web', 'native']).default('web').optional(), // default, per spec, is "web"
|
36
46
|
subject_type: z.enum(['public', 'pairwise']).default('public').optional(),
|
@@ -41,10 +51,10 @@ export const oauthClientMetadataSchema = z.object({
|
|
41
51
|
authorization_encrypted_response_alg: z.string().optional(),
|
42
52
|
client_id: oauthClientIdSchema.optional(),
|
43
53
|
client_name: z.string().optional(),
|
44
|
-
client_uri:
|
45
|
-
policy_uri:
|
46
|
-
tos_uri:
|
47
|
-
logo_uri:
|
54
|
+
client_uri: webUriSchema.optional(),
|
55
|
+
policy_uri: webUriSchema.optional(),
|
56
|
+
tos_uri: webUriSchema.optional(),
|
57
|
+
logo_uri: webUriSchema.optional(), // TODO: allow data: uri ?
|
48
58
|
|
49
59
|
/**
|
50
60
|
* Default Maximum Authentication Age. Specifies that the End-User MUST be
|
@@ -1,10 +1,8 @@
|
|
1
1
|
import { z } from 'zod'
|
2
|
-
import {
|
3
|
-
import { safeUrl } from './util.js'
|
2
|
+
import { webUriSchema } from './uri.js'
|
4
3
|
|
5
|
-
export const oauthIssuerIdentifierSchema =
|
6
|
-
|
7
|
-
.superRefine((value, ctx) => {
|
4
|
+
export const oauthIssuerIdentifierSchema = webUriSchema.superRefine(
|
5
|
+
(value, ctx) => {
|
8
6
|
// Validate the issuer (MIX-UP attacks)
|
9
7
|
|
10
8
|
if (value.endsWith('/')) {
|
@@ -12,32 +10,17 @@ export const oauthIssuerIdentifierSchema = z
|
|
12
10
|
code: z.ZodIssueCode.custom,
|
13
11
|
message: 'Issuer URL must not end with a slash',
|
14
12
|
})
|
13
|
+
return false
|
15
14
|
}
|
16
15
|
|
17
|
-
const url =
|
18
|
-
if (!url) {
|
19
|
-
return ctx.addIssue({
|
20
|
-
code: z.ZodIssueCode.custom,
|
21
|
-
message: 'Invalid url',
|
22
|
-
})
|
23
|
-
}
|
24
|
-
|
25
|
-
if (url.protocol !== 'https:') {
|
26
|
-
if (ALLOW_UNSECURE_ORIGINS && url.protocol === 'http:') {
|
27
|
-
// We'll allow HTTP in development mode
|
28
|
-
} else {
|
29
|
-
ctx.addIssue({
|
30
|
-
code: z.ZodIssueCode.custom,
|
31
|
-
message: 'Issuer must be an HTTPS URL',
|
32
|
-
})
|
33
|
-
}
|
34
|
-
}
|
16
|
+
const url = new URL(value)
|
35
17
|
|
36
18
|
if (url.username || url.password) {
|
37
19
|
ctx.addIssue({
|
38
20
|
code: z.ZodIssueCode.custom,
|
39
21
|
message: 'Issuer URL must not contain a username or password',
|
40
22
|
})
|
23
|
+
return false
|
41
24
|
}
|
42
25
|
|
43
26
|
if (url.hash || url.search) {
|
@@ -45,6 +28,7 @@ export const oauthIssuerIdentifierSchema = z
|
|
45
28
|
code: z.ZodIssueCode.custom,
|
46
29
|
message: 'Issuer URL must not contain a query or fragment',
|
47
30
|
})
|
31
|
+
return false
|
48
32
|
}
|
49
33
|
|
50
34
|
const canonicalValue = url.pathname === '/' ? url.origin : url.href
|
@@ -53,5 +37,11 @@ export const oauthIssuerIdentifierSchema = z
|
|
53
37
|
code: z.ZodIssueCode.custom,
|
54
38
|
message: 'Issuer URL must be in the canonical form',
|
55
39
|
})
|
40
|
+
return false
|
56
41
|
}
|
57
|
-
|
42
|
+
|
43
|
+
return true
|
44
|
+
},
|
45
|
+
)
|
46
|
+
|
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>
|
@@ -1,11 +1,9 @@
|
|
1
1
|
import { z } from 'zod'
|
2
|
-
import { oauthClientIdSchema } from './oauth-client-id.js'
|
3
2
|
import { oauthRefreshTokenSchema } from './oauth-refresh-token.js'
|
4
3
|
|
5
4
|
export const oauthRefreshTokenGrantTokenRequestSchema = z.object({
|
6
5
|
grant_type: z.literal('refresh_token'),
|
7
6
|
refresh_token: oauthRefreshTokenSchema,
|
8
|
-
client_id: oauthClientIdSchema,
|
9
7
|
})
|
10
8
|
|
11
9
|
export type OAuthRefreshTokenGrantTokenRequest = z.infer<
|
@@ -9,13 +9,15 @@ import { oauthTokenTypeSchema } from './oauth-token-type.js'
|
|
9
9
|
*/
|
10
10
|
export const oauthTokenResponseSchema = z
|
11
11
|
.object({
|
12
|
+
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
12
13
|
access_token: z.string(),
|
13
14
|
token_type: oauthTokenTypeSchema,
|
14
|
-
issuer: z.string().url().optional(),
|
15
15
|
scope: z.string().optional(),
|
16
|
-
id_token: signedJwtSchema.optional(),
|
17
16
|
refresh_token: z.string().optional(),
|
18
17
|
expires_in: z.number().optional(),
|
18
|
+
// https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
|
19
|
+
id_token: signedJwtSchema.optional(),
|
20
|
+
// https://datatracker.ietf.org/doc/html/rfc9396#name-enriched-authorization-deta
|
19
21
|
authorization_details: oauthAuthorizationDetailsSchema.optional(),
|
20
22
|
})
|
21
23
|
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
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>
|
@@ -0,0 +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-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"}
|