@atproto/oauth-types 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +26 -0
- package/dist/atproto-loopback-client-metadata.d.ts.map +1 -1
- package/dist/atproto-loopback-client-metadata.js +3 -14
- package/dist/atproto-loopback-client-metadata.js.map +1 -1
- package/dist/index.d.ts +18 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/oauth-access-token.d.ts +4 -0
- package/dist/oauth-access-token.d.ts.map +1 -0
- package/dist/oauth-access-token.js +6 -0
- package/dist/oauth-access-token.js.map +1 -0
- package/dist/oauth-authorization-code-grant-token-request.d.ts +20 -0
- package/dist/oauth-authorization-code-grant-token-request.d.ts.map +1 -0
- package/dist/oauth-authorization-code-grant-token-request.js +17 -0
- package/dist/oauth-authorization-code-grant-token-request.js.map +1 -0
- package/dist/oauth-authorization-request-jar.d.ts +16 -0
- package/dist/oauth-authorization-request-jar.d.ts.map +1 -0
- package/dist/oauth-authorization-request-jar.js +15 -0
- package/dist/oauth-authorization-request-jar.js.map +1 -0
- package/dist/oauth-authorization-request-par.d.ts +122 -0
- package/dist/oauth-authorization-request-par.d.ts.map +1 -0
- package/dist/oauth-authorization-request-par.js +11 -0
- package/dist/oauth-authorization-request-par.js.map +1 -0
- package/dist/{oauth-authentication-request-parameters.d.ts → oauth-authorization-request-parameters.d.ts} +15 -15
- package/dist/oauth-authorization-request-parameters.d.ts.map +1 -0
- package/dist/{oauth-authentication-request-parameters.js → oauth-authorization-request-parameters.js} +15 -16
- package/dist/oauth-authorization-request-parameters.js.map +1 -0
- package/dist/oauth-authorization-request-query.d.ts +128 -0
- package/dist/oauth-authorization-request-query.d.ts.map +1 -0
- package/dist/oauth-authorization-request-query.js +13 -0
- package/dist/oauth-authorization-request-query.js.map +1 -0
- package/dist/oauth-authorization-request-uri.d.ts +10 -0
- package/dist/oauth-authorization-request-uri.d.ts.map +1 -0
- package/dist/oauth-authorization-request-uri.js +9 -0
- package/dist/oauth-authorization-request-uri.js.map +1 -0
- package/dist/oauth-authorization-server-metadata.d.ts +10 -10
- package/dist/oauth-authorization-server-metadata.d.ts.map +1 -1
- package/dist/oauth-authorization-server-metadata.js +5 -1
- package/dist/oauth-authorization-server-metadata.js.map +1 -1
- package/dist/oauth-client-credentials-grant-token-request.d.ts +10 -0
- package/dist/oauth-client-credentials-grant-token-request.d.ts.map +1 -0
- package/dist/oauth-client-credentials-grant-token-request.js +8 -0
- package/dist/oauth-client-credentials-grant-token-request.js.map +1 -0
- package/dist/oauth-client-credentials.d.ts +18 -2
- package/dist/oauth-client-credentials.d.ts.map +1 -1
- package/dist/oauth-client-credentials.js +8 -2
- package/dist/oauth-client-credentials.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 +21 -18
- package/dist/oauth-client-id-discoverable.js.map +1 -1
- package/dist/oauth-client-id-loopback.d.ts +10 -3
- package/dist/oauth-client-id-loopback.d.ts.map +1 -1
- package/dist/oauth-client-id-loopback.js +58 -21
- package/dist/oauth-client-id-loopback.js.map +1 -1
- package/dist/oauth-client-metadata.d.ts +1 -1
- package/dist/oauth-client-metadata.d.ts.map +1 -1
- package/dist/oauth-client-metadata.js +2 -1
- package/dist/oauth-client-metadata.js.map +1 -1
- package/dist/oauth-code-challenge-method.d.ts +3 -0
- package/dist/oauth-code-challenge-method.d.ts.map +1 -0
- package/dist/oauth-code-challenge-method.js +6 -0
- package/dist/oauth-code-challenge-method.js.map +1 -0
- package/dist/oauth-introspection-response.d.ts +20 -0
- package/dist/oauth-introspection-response.d.ts.map +1 -0
- package/dist/oauth-introspection-response.js +3 -0
- package/dist/oauth-introspection-response.js.map +1 -0
- package/dist/oauth-par-response.d.ts +3 -0
- package/dist/oauth-par-response.d.ts.map +1 -1
- package/dist/oauth-par-response.js +1 -0
- package/dist/oauth-par-response.js.map +1 -1
- package/dist/oauth-password-grant-token-request.d.ts +16 -0
- package/dist/oauth-password-grant-token-request.d.ts.map +1 -0
- package/dist/oauth-password-grant-token-request.js +10 -0
- package/dist/oauth-password-grant-token-request.js.map +1 -0
- package/dist/oauth-refresh-token-grant-token-request.d.ts +16 -0
- package/dist/oauth-refresh-token-grant-token-request.d.ts.map +1 -0
- package/dist/oauth-refresh-token-grant-token-request.js +12 -0
- package/dist/oauth-refresh-token-grant-token-request.js.map +1 -0
- package/dist/oauth-refresh-token.d.ts +4 -0
- package/dist/oauth-refresh-token.d.ts.map +1 -0
- package/dist/oauth-refresh-token.js +6 -0
- package/dist/oauth-refresh-token.js.map +1 -0
- package/dist/oauth-request-uri.d.ts +4 -0
- package/dist/oauth-request-uri.d.ts.map +1 -0
- package/dist/oauth-request-uri.js +6 -0
- package/dist/oauth-request-uri.js.map +1 -0
- package/dist/oauth-scope.d.ts +10 -0
- package/dist/oauth-scope.d.ts.map +1 -0
- package/dist/oauth-scope.js +16 -0
- package/dist/oauth-scope.js.map +1 -0
- package/dist/oauth-token-identification.d.ts +13 -0
- package/dist/oauth-token-identification.d.ts.map +1 -0
- package/dist/oauth-token-identification.js +11 -0
- package/dist/oauth-token-identification.js.map +1 -0
- package/dist/oauth-token-request.d.ts +49 -0
- package/dist/oauth-token-request.d.ts.map +1 -0
- package/dist/oauth-token-request.js +15 -0
- package/dist/oauth-token-request.js.map +1 -0
- package/dist/util.d.ts +2 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +34 -3
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/atproto-loopback-client-metadata.ts +7 -20
- package/src/index.ts +18 -5
- package/src/oauth-access-token.ts +4 -0
- package/src/oauth-authorization-code-grant-token-request.ts +18 -0
- package/src/oauth-authorization-request-jar.ts +16 -0
- package/src/oauth-authorization-request-par.ts +13 -0
- package/src/{oauth-authentication-request-parameters.ts → oauth-authorization-request-parameters.ts} +20 -21
- package/src/oauth-authorization-request-query.ts +15 -0
- package/src/oauth-authorization-request-uri.ts +11 -0
- package/src/oauth-authorization-server-metadata.ts +5 -1
- package/src/oauth-client-credentials-grant-token-request.ts +9 -0
- package/src/oauth-client-credentials.ts +21 -1
- package/src/oauth-client-id-discoverable.ts +29 -26
- package/src/oauth-client-id-loopback.ts +78 -30
- package/src/oauth-client-metadata.ts +2 -1
- package/src/oauth-code-challenge-method.ts +3 -0
- package/src/oauth-introspection-response.ts +23 -0
- package/src/oauth-par-response.ts +1 -0
- package/src/oauth-password-grant-token-request.ts +11 -0
- package/src/oauth-refresh-token-grant-token-request.ts +13 -0
- package/src/oauth-refresh-token.ts +4 -0
- package/src/oauth-request-uri.ts +5 -0
- package/src/oauth-scope.ts +15 -0
- package/src/oauth-token-identification.ts +12 -0
- package/src/oauth-token-request.ts +14 -0
- package/src/util.ts +41 -1
- package/dist/access-token.d.ts +0 -4
- package/dist/access-token.d.ts.map +0 -1
- package/dist/access-token.js +0 -6
- package/dist/access-token.js.map +0 -1
- package/dist/oauth-authentication-request-parameters.d.ts.map +0 -1
- package/dist/oauth-authentication-request-parameters.js.map +0 -1
- package/dist/oauth-client-id-url.d.ts +0 -3
- package/dist/oauth-client-id-url.d.ts.map +0 -1
- package/dist/oauth-client-id-url.js +0 -21
- package/dist/oauth-client-id-url.js.map +0 -1
- package/dist/oauth-client-identification.d.ts +0 -31
- package/dist/oauth-client-identification.d.ts.map +0 -1
- package/dist/oauth-client-identification.js +0 -12
- package/dist/oauth-client-identification.js.map +0 -1
- package/src/access-token.ts +0 -4
- package/src/oauth-client-id-url.ts +0 -25
- package/src/oauth-client-identification.ts +0 -14
package/package.json
CHANGED
@@ -1,34 +1,21 @@
|
|
1
|
-
import {
|
1
|
+
import { parseOAuthLoopbackClientId } from './oauth-client-id-loopback.js'
|
2
2
|
import { OAuthClientMetadataInput } from './oauth-client-metadata.js'
|
3
|
-
import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'
|
4
3
|
|
5
4
|
export function atprotoLoopbackClientMetadata(
|
6
5
|
clientId: string,
|
7
6
|
): OAuthClientMetadataInput {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
const { origin, pathname, searchParams } = parseOAuthClientIdUrl(clientId)
|
13
|
-
|
14
|
-
for (const name of searchParams.keys()) {
|
15
|
-
if (name !== 'redirect_uri') {
|
16
|
-
throw new TypeError(`Invalid query parameter ${name} in client ID`)
|
17
|
-
}
|
18
|
-
}
|
19
|
-
const redirectUris = searchParams.getAll('redirect_uri')
|
7
|
+
const {
|
8
|
+
scope = 'atproto',
|
9
|
+
redirect_uris = [`http://127.0.0.1/`, `http://[::1]/`],
|
10
|
+
} = parseOAuthLoopbackClientId(clientId)
|
20
11
|
|
21
12
|
return {
|
22
13
|
client_id: clientId,
|
14
|
+
scope,
|
15
|
+
redirect_uris,
|
23
16
|
client_name: 'Loopback client',
|
24
17
|
response_types: ['code'],
|
25
18
|
grant_types: ['authorization_code', 'refresh_token'],
|
26
|
-
redirect_uris: (redirectUris.length
|
27
|
-
? redirectUris
|
28
|
-
: (['127.0.0.1', '[::1]'] as const).map(
|
29
|
-
(ip) =>
|
30
|
-
Object.assign(new URL(pathname, origin), { hostname: ip }).href,
|
31
|
-
)) as [string, ...string[]],
|
32
19
|
token_endpoint_auth_method: 'none',
|
33
20
|
application_type: 'native',
|
34
21
|
dpop_bound_access_tokens: true,
|
package/src/index.ts
CHANGED
@@ -1,25 +1,38 @@
|
|
1
1
|
export * from './constants.js'
|
2
2
|
export * from './util.js'
|
3
3
|
|
4
|
-
export * from './access-token.js'
|
5
4
|
export * from './atproto-loopback-client-metadata.js'
|
6
|
-
export * from './oauth-
|
7
|
-
export * from './oauth-
|
8
|
-
export * from './oauth-authentication-request-parameters.js'
|
5
|
+
export * from './oauth-access-token.js'
|
6
|
+
export * from './oauth-authorization-code-grant-token-request.js'
|
9
7
|
export * from './oauth-authorization-details.js'
|
8
|
+
export * from './oauth-authorization-request-jar.js'
|
9
|
+
export * from './oauth-authorization-request-par.js'
|
10
|
+
export * from './oauth-authorization-request-parameters.js'
|
11
|
+
export * from './oauth-authorization-request-query.js'
|
12
|
+
export * from './oauth-authorization-request-uri.js'
|
10
13
|
export * from './oauth-authorization-server-metadata.js'
|
14
|
+
export * from './oauth-client-credentials-grant-token-request.js'
|
11
15
|
export * from './oauth-client-credentials.js'
|
16
|
+
export * from './oauth-client-id-discoverable.js'
|
17
|
+
export * from './oauth-client-id-loopback.js'
|
12
18
|
export * from './oauth-client-id.js'
|
13
|
-
export * from './oauth-client-identification.js'
|
14
19
|
export * from './oauth-client-metadata.js'
|
15
20
|
export * from './oauth-endpoint-auth-method.js'
|
16
21
|
export * from './oauth-endpoint-name.js'
|
17
22
|
export * from './oauth-grant-type.js'
|
23
|
+
export * from './oauth-introspection-response.js'
|
18
24
|
export * from './oauth-issuer-identifier.js'
|
19
25
|
export * from './oauth-par-response.js'
|
26
|
+
export * from './oauth-password-grant-token-request.js'
|
20
27
|
export * from './oauth-protected-resource-metadata.js'
|
28
|
+
export * from './oauth-refresh-token-grant-token-request.js'
|
29
|
+
export * from './oauth-refresh-token.js'
|
30
|
+
export * from './oauth-request-uri.js'
|
21
31
|
export * from './oauth-response-mode.js'
|
22
32
|
export * from './oauth-response-type.js'
|
33
|
+
export * from './oauth-scope.js'
|
34
|
+
export * from './oauth-token-identification.js'
|
35
|
+
export * from './oauth-token-request.js'
|
23
36
|
export * from './oauth-token-response.js'
|
24
37
|
export * from './oauth-token-type.js'
|
25
38
|
export * from './oidc-claims-parameter.js'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
export const oauthAuthorizationCodeGrantTokenRequestSchema = z.object({
|
4
|
+
grant_type: z.literal('authorization_code'),
|
5
|
+
code: z.string().min(1),
|
6
|
+
redirect_uri: z.string().url(),
|
7
|
+
/** @see {@link https://datatracker.ietf.org/doc/html/rfc7636#section-4.1} */
|
8
|
+
code_verifier: z
|
9
|
+
.string()
|
10
|
+
.min(43)
|
11
|
+
.max(128)
|
12
|
+
.regex(/^[a-zA-Z0-9-._~]+$/)
|
13
|
+
.optional(),
|
14
|
+
})
|
15
|
+
|
16
|
+
export type OAuthAuthorizationCodeGrantTokenRequest = z.infer<
|
17
|
+
typeof oauthAuthorizationCodeGrantTokenRequestSchema
|
18
|
+
>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { signedJwtSchema, unsignedJwtSchema } from '@atproto/jwk'
|
2
|
+
import { z } from 'zod'
|
3
|
+
|
4
|
+
export const oauthAuthorizationRequestJarSchema = z.object({
|
5
|
+
/**
|
6
|
+
* AuthorizationRequest inside a JWT:
|
7
|
+
* - "iat" is required and **MUST** be less than one minute
|
8
|
+
*
|
9
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc9101}
|
10
|
+
*/
|
11
|
+
request: z.union([signedJwtSchema, unsignedJwtSchema]),
|
12
|
+
})
|
13
|
+
|
14
|
+
export type OAuthAuthorizationRequestJar = z.infer<
|
15
|
+
typeof oauthAuthorizationRequestJarSchema
|
16
|
+
>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
import { oauthAuthorizationRequestJarSchema } from './oauth-authorization-request-jar.js'
|
4
|
+
import { oauthAuthorizationRequestParametersSchema } from './oauth-authorization-request-parameters.js'
|
5
|
+
|
6
|
+
export const oauthAuthorizationRequestParSchema = z.union([
|
7
|
+
oauthAuthorizationRequestParametersSchema,
|
8
|
+
oauthAuthorizationRequestJarSchema,
|
9
|
+
])
|
10
|
+
|
11
|
+
export type OAuthAuthorizationRequestPar = z.infer<
|
12
|
+
typeof oauthAuthorizationRequestParSchema
|
13
|
+
>
|
package/src/{oauth-authentication-request-parameters.ts → oauth-authorization-request-parameters.ts}
RENAMED
@@ -3,7 +3,9 @@ import { z } from 'zod'
|
|
3
3
|
|
4
4
|
import { oauthAuthorizationDetailsSchema } from './oauth-authorization-details.js'
|
5
5
|
import { oauthClientIdSchema } from './oauth-client-id.js'
|
6
|
+
import { oauthCodeChallengeMethodSchema } from './oauth-code-challenge-method.js'
|
6
7
|
import { oauthResponseTypeSchema } from './oauth-response-type.js'
|
8
|
+
import { oauthScopeSchema } from './oauth-scope.js'
|
7
9
|
import { oidcClaimsParameterSchema } from './oidc-claims-parameter.js'
|
8
10
|
import { oidcClaimsPropertiesSchema } from './oidc-claims-properties.js'
|
9
11
|
import { oidcEntityTypeSchema } from './oidc-entity-type.js'
|
@@ -11,35 +13,32 @@ import { oidcEntityTypeSchema } from './oidc-entity-type.js'
|
|
11
13
|
/**
|
12
14
|
* @see {@link https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest | OIDC}
|
13
15
|
*/
|
14
|
-
export const
|
16
|
+
export const oauthAuthorizationRequestParametersSchema = z.object({
|
15
17
|
client_id: oauthClientIdSchema,
|
16
|
-
|
17
18
|
state: z.string().optional(),
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
redirect_uri: z.string().url().optional(),
|
20
|
+
scope: oauthScopeSchema.optional(),
|
21
21
|
response_type: oauthResponseTypeSchema,
|
22
22
|
|
23
|
-
// Default depend on response_type
|
24
|
-
response_mode: z.enum(['query', 'fragment', 'form_post']).optional(),
|
25
|
-
|
26
23
|
// PKCE
|
24
|
+
|
27
25
|
code_challenge: z.string().optional(),
|
28
|
-
code_challenge_method:
|
26
|
+
code_challenge_method: oauthCodeChallengeMethodSchema
|
27
|
+
.default('S256')
|
28
|
+
.optional(),
|
29
29
|
|
30
|
-
|
30
|
+
// DPOP
|
31
31
|
|
32
|
-
// https://datatracker.ietf.org/doc/html/
|
33
|
-
|
34
|
-
// scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
|
35
|
-
// = Basically most ASCII characters except backslash and double quote
|
36
|
-
scope: z
|
37
|
-
.string()
|
38
|
-
.regex(/^[!\x23-\x5B\x5D-\x7E]+( [!\x23-\x5B\x5D-\x7E]+)*$/)
|
39
|
-
.optional(),
|
32
|
+
// https://datatracker.ietf.org/doc/html/rfc9449#section-12.3
|
33
|
+
dpop_jkt: z.string().optional(),
|
40
34
|
|
41
35
|
// OIDC
|
42
36
|
|
37
|
+
// Default depend on response_type
|
38
|
+
response_mode: z.enum(['query', 'fragment', 'form_post']).optional(),
|
39
|
+
|
40
|
+
nonce: z.string().optional(),
|
41
|
+
|
43
42
|
// Specifies the allowable elapsed time in seconds since the last time the
|
44
43
|
// End-User was actively authenticated by the OP. If the elapsed time is
|
45
44
|
// greater than this value, the OP MUST attempt to actively re-authenticate
|
@@ -89,8 +88,8 @@ export const oauthAuthenticationRequestParametersSchema = z.object({
|
|
89
88
|
})
|
90
89
|
|
91
90
|
/**
|
92
|
-
* @see {
|
91
|
+
* @see {oauthAuthorizationRequestParametersSchema}
|
93
92
|
*/
|
94
|
-
export type
|
95
|
-
typeof
|
93
|
+
export type OAuthAuthorizationRequestParameters = z.infer<
|
94
|
+
typeof oauthAuthorizationRequestParametersSchema
|
96
95
|
>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
import { oauthAuthorizationRequestJarSchema } from './oauth-authorization-request-jar.js'
|
4
|
+
import { oauthAuthorizationRequestParametersSchema } from './oauth-authorization-request-parameters.js'
|
5
|
+
import { oauthAuthorizationRequestUriSchema } from './oauth-authorization-request-uri.js'
|
6
|
+
|
7
|
+
export const oauthAuthorizationRequestQuerySchema = z.union([
|
8
|
+
oauthAuthorizationRequestParametersSchema,
|
9
|
+
oauthAuthorizationRequestJarSchema,
|
10
|
+
oauthAuthorizationRequestUriSchema,
|
11
|
+
])
|
12
|
+
|
13
|
+
export type OAuthAuthorizationRequestQuery = z.infer<
|
14
|
+
typeof oauthAuthorizationRequestQuerySchema
|
15
|
+
>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
import { oauthRequestUriSchema } from './oauth-request-uri.js'
|
4
|
+
|
5
|
+
export const oauthAuthorizationRequestUriSchema = z.object({
|
6
|
+
request_uri: oauthRequestUriSchema,
|
7
|
+
})
|
8
|
+
|
9
|
+
export type OAuthAuthorizationRequestUri = z.infer<
|
10
|
+
typeof oauthAuthorizationRequestUriSchema
|
11
|
+
>
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { z } from 'zod'
|
2
2
|
|
3
|
+
import { oauthCodeChallengeMethodSchema } from './oauth-code-challenge-method.js'
|
3
4
|
import { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'
|
4
5
|
|
5
6
|
/**
|
@@ -19,7 +20,10 @@ export const oauthAuthorizationServerMetadataSchema = z.object({
|
|
19
20
|
response_types_supported: z.array(z.string()).optional(),
|
20
21
|
response_modes_supported: z.array(z.string()).optional(),
|
21
22
|
grant_types_supported: z.array(z.string()).optional(),
|
22
|
-
code_challenge_methods_supported: z
|
23
|
+
code_challenge_methods_supported: z
|
24
|
+
.array(oauthCodeChallengeMethodSchema)
|
25
|
+
.min(1)
|
26
|
+
.optional(),
|
23
27
|
ui_locales_supported: z.array(z.string()).optional(),
|
24
28
|
id_token_signing_alg_values_supported: z.array(z.string()).optional(),
|
25
29
|
display_values_supported: z.array(z.string()).optional(),
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
export const oauthClientCredentialsGrantTokenRequestSchema = z.object({
|
4
|
+
grant_type: z.literal('client_credentials'),
|
5
|
+
})
|
6
|
+
|
7
|
+
export type OAuthClientCredentialsGrantTokenRequest = z.infer<
|
8
|
+
typeof oauthClientCredentialsGrantTokenRequestSchema
|
9
|
+
>
|
@@ -14,19 +14,39 @@ export const oauthClientCredentialsJwtBearerSchema = z.object({
|
|
14
14
|
* - The JWT MAY contain a "jti" (JWT ID) claim that provides a unique identifier for the token.
|
15
15
|
* - Note that the authorization server may reject JWTs with an "exp" claim value that is unreasonably far in the future.
|
16
16
|
*
|
17
|
-
* @see {@link https://datatracker.ietf.org/doc/html/
|
17
|
+
* @see {@link https://datatracker.ietf.org/doc/html/rfc7523#section-3}
|
18
18
|
*/
|
19
19
|
client_assertion: signedJwtSchema,
|
20
20
|
})
|
21
21
|
|
22
|
+
export type OAuthClientCredentialsJwtBearer = z.infer<
|
23
|
+
typeof oauthClientCredentialsJwtBearerSchema
|
24
|
+
>
|
25
|
+
|
22
26
|
export const oauthClientCredentialsSecretPostSchema = z.object({
|
23
27
|
client_id: oauthClientIdSchema,
|
24
28
|
client_secret: z.string(),
|
25
29
|
})
|
26
30
|
|
31
|
+
export type OAuthClientCredentialsSecretPost = z.infer<
|
32
|
+
typeof oauthClientCredentialsSecretPostSchema
|
33
|
+
>
|
34
|
+
|
35
|
+
export const oauthClientCredentialsNoneSchema = z.object({
|
36
|
+
client_id: oauthClientIdSchema,
|
37
|
+
})
|
38
|
+
|
39
|
+
export type OAuthClientCredentialsNone = z.infer<
|
40
|
+
typeof oauthClientCredentialsNoneSchema
|
41
|
+
>
|
42
|
+
|
43
|
+
//
|
44
|
+
|
27
45
|
export const oauthClientCredentialsSchema = z.union([
|
28
46
|
oauthClientCredentialsJwtBearerSchema,
|
29
47
|
oauthClientCredentialsSecretPostSchema,
|
48
|
+
// Must be last since it is less specific
|
49
|
+
oauthClientCredentialsNoneSchema,
|
30
50
|
])
|
31
51
|
|
32
52
|
export type OAuthClientCredentials = z.infer<
|
@@ -1,15 +1,14 @@
|
|
1
|
-
import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'
|
2
1
|
import { OAuthClientId } from './oauth-client-id.js'
|
3
|
-
import {
|
2
|
+
import { extractUrlPath, isHostnameIP } from './util.js'
|
4
3
|
|
5
4
|
/**
|
6
5
|
* @see {@link https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html}
|
7
6
|
*/
|
8
7
|
export type OAuthClientIdDiscoverable = OAuthClientId & `https://${string}`
|
9
8
|
|
10
|
-
export function isOAuthClientIdDiscoverable
|
11
|
-
clientId:
|
12
|
-
): clientId is
|
9
|
+
export function isOAuthClientIdDiscoverable(
|
10
|
+
clientId: string,
|
11
|
+
): clientId is OAuthClientIdDiscoverable {
|
13
12
|
try {
|
14
13
|
parseOAuthDiscoverableClientId(clientId)
|
15
14
|
return true
|
@@ -18,48 +17,52 @@ export function isOAuthClientIdDiscoverable<C extends OAuthClientId>(
|
|
18
17
|
}
|
19
18
|
}
|
20
19
|
|
21
|
-
export function
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
export function assertOAuthDiscoverableClientId(
|
21
|
+
value: string,
|
22
|
+
): asserts value is OAuthClientIdDiscoverable {
|
23
|
+
void parseOAuthDiscoverableClientId(value)
|
24
|
+
}
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
}
|
26
|
+
export function parseOAuthDiscoverableClientId(clientId: string): URL {
|
27
|
+
const url = new URL(clientId)
|
29
28
|
|
30
29
|
if (url.protocol !== 'https:') {
|
31
30
|
throw new TypeError('ClientID must use the "https:" protocol')
|
32
31
|
}
|
33
32
|
|
33
|
+
if (url.username || url.password) {
|
34
|
+
throw new TypeError('ClientID must not contain credentials')
|
35
|
+
}
|
36
|
+
|
34
37
|
if (url.hash) {
|
35
38
|
throw new TypeError('ClientID must not contain a fragment')
|
36
39
|
}
|
37
40
|
|
38
|
-
if (url.
|
39
|
-
throw new TypeError('ClientID must not
|
41
|
+
if (url.hostname === 'localhost') {
|
42
|
+
throw new TypeError('ClientID hostname must not be "localhost"')
|
40
43
|
}
|
41
44
|
|
42
45
|
if (url.pathname === '/') {
|
43
46
|
throw new TypeError(
|
44
|
-
'ClientID must contain a path (e.g. "/client-metadata")',
|
47
|
+
'ClientID must contain a path component (e.g. "/client-metadata.json")',
|
45
48
|
)
|
46
49
|
}
|
47
50
|
|
48
|
-
if (url.pathname
|
49
|
-
throw new TypeError('ClientID must not end with a trailing slash')
|
51
|
+
if (url.pathname.endsWith('/')) {
|
52
|
+
throw new TypeError('ClientID path must not end with a trailing slash')
|
50
53
|
}
|
51
54
|
|
52
|
-
if (url.
|
53
|
-
throw new TypeError(
|
54
|
-
`ClientID must not contain any double slashes in its path`,
|
55
|
-
)
|
55
|
+
if (isHostnameIP(url.hostname)) {
|
56
|
+
throw new TypeError('ClientID hostname must not be an IP address')
|
56
57
|
}
|
57
58
|
|
58
|
-
//
|
59
|
-
//
|
60
|
-
|
61
|
-
if (
|
62
|
-
throw new TypeError(
|
59
|
+
// URL constructor normalizes the URL, so we extract the path manually to
|
60
|
+
// avoid normalization, then compare it to the normalized path to ensure
|
61
|
+
// that the URL does not contain path traversal or other unexpected characters
|
62
|
+
if (extractUrlPath(clientId) !== url.pathname) {
|
63
|
+
throw new TypeError(
|
64
|
+
`ClientID must be in canonical form ("${url.href}", got "${clientId}")`,
|
65
|
+
)
|
63
66
|
}
|
64
67
|
|
65
68
|
return url
|
@@ -1,12 +1,15 @@
|
|
1
|
-
import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'
|
2
1
|
import { OAuthClientId } from './oauth-client-id.js'
|
2
|
+
import { OAuthScope, oauthScopeSchema } from './oauth-scope.js'
|
3
|
+
import { isLoopbackHost, safeUrl } from './util.js'
|
4
|
+
|
5
|
+
const OAUTH_CLIENT_ID_LOOPBACK_URL = 'http://localhost'
|
3
6
|
|
4
7
|
export type OAuthClientIdLoopback = OAuthClientId &
|
5
|
-
|
8
|
+
`${typeof OAUTH_CLIENT_ID_LOOPBACK_URL}${'' | '/'}${'' | `?${string}`}`
|
6
9
|
|
7
|
-
export function isOAuthClientIdLoopback
|
8
|
-
clientId:
|
9
|
-
): clientId is
|
10
|
+
export function isOAuthClientIdLoopback(
|
11
|
+
clientId: string,
|
12
|
+
): clientId is OAuthClientIdLoopback {
|
10
13
|
try {
|
11
14
|
parseOAuthLoopbackClientId(clientId)
|
12
15
|
return true
|
@@ -15,44 +18,89 @@ export function isOAuthClientIdLoopback<C extends OAuthClientId>(
|
|
15
18
|
}
|
16
19
|
}
|
17
20
|
|
18
|
-
export function
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
export function assertOAuthLoopbackClientId(
|
22
|
+
clientId: string,
|
23
|
+
): asserts clientId is OAuthClientIdLoopback {
|
24
|
+
void parseOAuthLoopbackClientId(clientId)
|
25
|
+
}
|
22
26
|
|
23
|
-
|
24
|
-
|
27
|
+
// @TODO: should we turn this into a zod schema? (more coherent error with other
|
28
|
+
// validation functions)
|
29
|
+
export function parseOAuthLoopbackClientId(clientId: string): {
|
30
|
+
scope?: OAuthScope
|
31
|
+
redirect_uris?: [string, ...string[]]
|
32
|
+
} {
|
33
|
+
if (!clientId.startsWith(OAUTH_CLIENT_ID_LOOPBACK_URL)) {
|
34
|
+
throw new TypeError(
|
35
|
+
`Loopback ClientID must start with "${OAUTH_CLIENT_ID_LOOPBACK_URL}"`,
|
36
|
+
)
|
37
|
+
} else if (clientId.includes('#', OAUTH_CLIENT_ID_LOOPBACK_URL.length)) {
|
38
|
+
throw new TypeError('Loopback ClientID must not contain a hash component')
|
25
39
|
}
|
26
40
|
|
27
|
-
|
28
|
-
|
41
|
+
const queryStringIdx =
|
42
|
+
clientId.length > OAUTH_CLIENT_ID_LOOPBACK_URL.length &&
|
43
|
+
clientId[OAUTH_CLIENT_ID_LOOPBACK_URL.length] === '/'
|
44
|
+
? OAUTH_CLIENT_ID_LOOPBACK_URL.length + 1
|
45
|
+
: OAUTH_CLIENT_ID_LOOPBACK_URL.length
|
46
|
+
|
47
|
+
if (clientId.length === queryStringIdx) {
|
48
|
+
return {} // no query string to parse
|
29
49
|
}
|
30
50
|
|
31
|
-
if (
|
32
|
-
throw new TypeError('Loopback ClientID must not contain a
|
51
|
+
if (clientId[queryStringIdx] !== '?') {
|
52
|
+
throw new TypeError('Loopback ClientID must not contain a path component')
|
33
53
|
}
|
34
54
|
|
35
|
-
|
36
|
-
|
55
|
+
const searchParams = new URLSearchParams(clientId.slice(queryStringIdx + 1))
|
56
|
+
|
57
|
+
for (const name of searchParams.keys()) {
|
58
|
+
if (name !== 'redirect_uri' && name !== 'scope') {
|
59
|
+
throw new TypeError(`Invalid query parameter "${name}" in client ID`)
|
60
|
+
}
|
37
61
|
}
|
38
62
|
|
39
|
-
|
40
|
-
|
63
|
+
const scope = searchParams.get('scope') ?? undefined
|
64
|
+
if (scope != null) {
|
65
|
+
if (searchParams.getAll('scope').length > 1) {
|
66
|
+
throw new TypeError(
|
67
|
+
'Loopback ClientID must contain at most one scope query parameter',
|
68
|
+
)
|
69
|
+
} else if (!oauthScopeSchema.safeParse(scope).success) {
|
70
|
+
throw new TypeError('Invalid scope query parameter in client ID')
|
71
|
+
}
|
41
72
|
}
|
42
73
|
|
43
|
-
|
74
|
+
const redirect_uris = searchParams.has('redirect_uri')
|
75
|
+
? (searchParams.getAll('redirect_uri') as [string, ...string[]])
|
76
|
+
: undefined
|
44
77
|
|
45
|
-
if (
|
46
|
-
|
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
|
+
}
|
47
100
|
}
|
48
101
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
)
|
102
|
+
return {
|
103
|
+
scope,
|
104
|
+
redirect_uris,
|
53
105
|
}
|
54
|
-
|
55
|
-
// Note: Query string is allowed
|
56
|
-
|
57
|
-
return url
|
58
106
|
}
|
@@ -5,6 +5,7 @@ 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
7
|
import { oauthResponseTypeSchema } from './oauth-response-type.js'
|
8
|
+
import { oauthScopeSchema } from './oauth-scope.js'
|
8
9
|
|
9
10
|
// https://openid.net/specs/openid-connect-registration-1_0.html
|
10
11
|
// https://datatracker.ietf.org/doc/html/rfc7591
|
@@ -22,7 +23,7 @@ export const oauthClientMetadataSchema = z.object({
|
|
22
23
|
// > If omitted, the default behavior is that the client will use only the
|
23
24
|
// > "authorization_code" Grant Type.
|
24
25
|
.default(['authorization_code']),
|
25
|
-
scope:
|
26
|
+
scope: oauthScopeSchema.optional(),
|
26
27
|
token_endpoint_auth_method: oauthEndpointAuthMethod
|
27
28
|
.default('none')
|
28
29
|
.optional(),
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { OAuthAuthorizationDetails } from './oauth-authorization-details.js'
|
2
|
+
import { OAuthTokenType } from './oauth-token-type.js'
|
3
|
+
|
4
|
+
// https://datatracker.ietf.org/doc/html/rfc7662#section-2.2
|
5
|
+
export type OAuthIntrospectionResponse =
|
6
|
+
| { active: false }
|
7
|
+
| {
|
8
|
+
active: true
|
9
|
+
|
10
|
+
scope?: string
|
11
|
+
client_id?: string
|
12
|
+
username?: string
|
13
|
+
token_type?: OAuthTokenType
|
14
|
+
authorization_details?: OAuthAuthorizationDetails
|
15
|
+
|
16
|
+
aud?: string | [string, ...string[]]
|
17
|
+
exp?: number
|
18
|
+
iat?: number
|
19
|
+
iss?: string
|
20
|
+
jti?: string
|
21
|
+
nbf?: number
|
22
|
+
sub?: string
|
23
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
export const oauthPasswordGrantTokenRequestSchema = z.object({
|
4
|
+
grant_type: z.literal('password'),
|
5
|
+
username: z.string(),
|
6
|
+
password: z.string(),
|
7
|
+
})
|
8
|
+
|
9
|
+
export type OAuthPasswordGrantTokenRequest = z.infer<
|
10
|
+
typeof oauthPasswordGrantTokenRequestSchema
|
11
|
+
>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
import { oauthClientIdSchema } from './oauth-client-id.js'
|
3
|
+
import { oauthRefreshTokenSchema } from './oauth-refresh-token.js'
|
4
|
+
|
5
|
+
export const oauthRefreshTokenGrantTokenRequestSchema = z.object({
|
6
|
+
grant_type: z.literal('refresh_token'),
|
7
|
+
refresh_token: oauthRefreshTokenSchema,
|
8
|
+
client_id: oauthClientIdSchema,
|
9
|
+
})
|
10
|
+
|
11
|
+
export type OAuthRefreshTokenGrantTokenRequest = z.infer<
|
12
|
+
typeof oauthRefreshTokenGrantTokenRequestSchema
|
13
|
+
>
|