@atproto/oauth-types 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +12 -0
- package/LICENSE.txt +7 -0
- package/README.md +3 -0
- package/dist/access-token.d.ts +4 -0
- package/dist/access-token.d.ts.map +1 -0
- package/dist/access-token.js +6 -0
- package/dist/access-token.js.map +1 -0
- package/dist/atproto-loopback-client-metadata.d.ts +3 -0
- package/dist/atproto-loopback-client-metadata.d.ts.map +1 -0
- package/dist/atproto-loopback-client-metadata.js +26 -0
- package/dist/atproto-loopback-client-metadata.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth-authentication-request-parameters.d.ts +128 -0
- package/dist/oauth-authentication-request-parameters.d.ts.map +1 -0
- package/dist/oauth-authentication-request-parameters.js +76 -0
- package/dist/oauth-authentication-request-parameters.js.map +1 -0
- package/dist/oauth-authorization-details.d.ts +54 -0
- package/dist/oauth-authorization-details.d.ts.map +1 -0
- package/dist/oauth-authorization-details.js +20 -0
- package/dist/oauth-authorization-details.js.map +1 -0
- package/dist/oauth-authorization-server-metadata.d.ts +428 -0
- package/dist/oauth-authorization-server-metadata.d.ts.map +1 -0
- package/dist/oauth-authorization-server-metadata.js +88 -0
- package/dist/oauth-authorization-server-metadata.js.map +1 -0
- package/dist/oauth-client-credentials.d.ts +66 -0
- package/dist/oauth-client-credentials.d.ts.map +1 -0
- package/dist/oauth-client-credentials.js +30 -0
- package/dist/oauth-client-credentials.js.map +1 -0
- package/dist/oauth-client-id-discoverable.d.ts +8 -0
- package/dist/oauth-client-id-discoverable.d.ts.map +1 -0
- package/dist/oauth-client-id-discoverable.js +48 -0
- package/dist/oauth-client-id-discoverable.js.map +1 -0
- package/dist/oauth-client-id-loopback.d.ts +5 -0
- package/dist/oauth-client-id-loopback.d.ts.map +1 -0
- package/dist/oauth-client-id-loopback.js +44 -0
- package/dist/oauth-client-id-loopback.js.map +1 -0
- package/dist/oauth-client-id-url.d.ts +3 -0
- package/dist/oauth-client-id-url.d.ts.map +1 -0
- package/dist/oauth-client-id-url.js +21 -0
- package/dist/oauth-client-id-url.js.map +1 -0
- package/dist/oauth-client-id.d.ts +4 -0
- package/dist/oauth-client-id.d.ts.map +1 -0
- package/dist/oauth-client-id.js +6 -0
- package/dist/oauth-client-id.js.map +1 -0
- package/dist/oauth-client-identification.d.ts +31 -0
- package/dist/oauth-client-identification.d.ts.map +1 -0
- package/dist/oauth-client-identification.js +12 -0
- package/dist/oauth-client-identification.js.map +1 -0
- package/dist/oauth-client-metadata.d.ts +1576 -0
- package/dist/oauth-client-metadata.d.ts.map +1 -0
- package/dist/oauth-client-metadata.js +70 -0
- package/dist/oauth-client-metadata.js.map +1 -0
- package/dist/oauth-endpoint-auth-method.d.ts +4 -0
- package/dist/oauth-endpoint-auth-method.d.ts.map +1 -0
- package/dist/oauth-endpoint-auth-method.js +14 -0
- package/dist/oauth-endpoint-auth-method.js.map +1 -0
- package/dist/oauth-endpoint-name.d.ts +2 -0
- package/dist/oauth-endpoint-name.d.ts.map +1 -0
- package/dist/oauth-endpoint-name.js +3 -0
- package/dist/oauth-endpoint-name.js.map +1 -0
- package/dist/oauth-grant-type.d.ts +4 -0
- package/dist/oauth-grant-type.d.ts.map +1 -0
- package/dist/oauth-grant-type.js +14 -0
- package/dist/oauth-grant-type.js.map +1 -0
- package/dist/oauth-issuer-identifier.d.ts +3 -0
- package/dist/oauth-issuer-identifier.d.ts.map +1 -0
- package/dist/oauth-issuer-identifier.js +59 -0
- package/dist/oauth-issuer-identifier.js.map +1 -0
- package/dist/oauth-par-response.d.ts +10 -0
- package/dist/oauth-par-response.d.ts.map +1 -0
- package/dist/oauth-par-response.js +8 -0
- package/dist/oauth-par-response.js.map +1 -0
- package/dist/oauth-protected-resource-metadata.d.ts +90 -0
- package/dist/oauth-protected-resource-metadata.d.ts.map +1 -0
- package/dist/oauth-protected-resource-metadata.js +75 -0
- package/dist/oauth-protected-resource-metadata.js.map +1 -0
- package/dist/oauth-response-mode.d.ts +4 -0
- package/dist/oauth-response-mode.d.ts.map +1 -0
- package/dist/oauth-response-mode.js +10 -0
- package/dist/oauth-response-mode.js.map +1 -0
- package/dist/oauth-response-type.d.ts +4 -0
- package/dist/oauth-response-type.d.ts.map +1 -0
- package/dist/oauth-response-type.js +17 -0
- package/dist/oauth-response-type.js.map +1 -0
- package/dist/oauth-token-response.d.ts +103 -0
- package/dist/oauth-token-response.d.ts.map +1 -0
- package/dist/oauth-token-response.js +26 -0
- package/dist/oauth-token-response.js.map +1 -0
- package/dist/oauth-token-type.d.ts +4 -0
- package/dist/oauth-token-type.d.ts.map +1 -0
- package/dist/oauth-token-type.js +16 -0
- package/dist/oauth-token-type.js.map +1 -0
- package/dist/oidc-claims-parameter.d.ts +4 -0
- package/dist/oidc-claims-parameter.d.ts.map +1 -0
- package/dist/oidc-claims-parameter.js +36 -0
- package/dist/oidc-claims-parameter.js.map +1 -0
- package/dist/oidc-claims-properties.d.ts +16 -0
- package/dist/oidc-claims-properties.d.ts.map +1 -0
- package/dist/oidc-claims-properties.js +11 -0
- package/dist/oidc-claims-properties.js.map +1 -0
- package/dist/oidc-entity-type.d.ts +4 -0
- package/dist/oidc-entity-type.d.ts.map +1 -0
- package/dist/oidc-entity-type.js +6 -0
- package/dist/oidc-entity-type.js.map +1 -0
- package/dist/util.d.ts +5 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +23 -0
- package/dist/util.js.map +1 -0
- package/package.json +37 -0
- package/src/access-token.ts +4 -0
- package/src/atproto-loopback-client-metadata.ts +30 -0
- package/src/constants.ts +9 -0
- package/src/index.ts +27 -0
- package/src/oauth-authentication-request-parameters.ts +104 -0
- package/src/oauth-authorization-details.ts +28 -0
- package/src/oauth-authorization-server-metadata.ts +106 -0
- package/src/oauth-client-credentials.ts +34 -0
- package/src/oauth-client-id-discoverable.ts +66 -0
- package/src/oauth-client-id-loopback.ts +58 -0
- package/src/oauth-client-id-url.ts +25 -0
- package/src/oauth-client-id.ts +4 -0
- package/src/oauth-client-identification.ts +14 -0
- package/src/oauth-client-metadata.ts +75 -0
- package/src/oauth-endpoint-auth-method.ts +13 -0
- package/src/oauth-endpoint-name.ts +5 -0
- package/src/oauth-grant-type.ts +13 -0
- package/src/oauth-issuer-identifier.ts +61 -0
- package/src/oauth-par-response.ts +7 -0
- package/src/oauth-protected-resource-metadata.ts +85 -0
- package/src/oauth-response-mode.ts +9 -0
- package/src/oauth-response-type.ts +17 -0
- package/src/oauth-token-response.ts +29 -0
- package/src/oauth-token-type.ts +15 -0
- package/src/oidc-claims-parameter.ts +40 -0
- package/src/oidc-claims-properties.ts +11 -0
- package/src/oidc-entity-type.ts +5 -0
- package/src/util.ts +20 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +4 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
import { parseOAuthClientIdUrl } from './oauth-client-id-url.js'
|
2
|
+
import { OAuthClientId } from './oauth-client-id.js'
|
3
|
+
|
4
|
+
export type OAuthClientIdLoopback = OAuthClientId &
|
5
|
+
`http://localhost${'' | `${'/' | '?' | '#'}${string}`}`
|
6
|
+
|
7
|
+
export function isOAuthClientIdLoopback<C extends OAuthClientId>(
|
8
|
+
clientId: C,
|
9
|
+
): clientId is C & OAuthClientIdLoopback {
|
10
|
+
try {
|
11
|
+
parseOAuthLoopbackClientId(clientId)
|
12
|
+
return true
|
13
|
+
} catch {
|
14
|
+
return false
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
export function parseOAuthLoopbackClientId(clientId: OAuthClientId): URL {
|
19
|
+
const url = parseOAuthClientIdUrl(clientId)
|
20
|
+
|
21
|
+
// Optimization: cheap checks first
|
22
|
+
|
23
|
+
if (url.protocol !== 'http:') {
|
24
|
+
throw new TypeError('Loopback ClientID must use the "http:" protocol')
|
25
|
+
}
|
26
|
+
|
27
|
+
if (url.hostname !== 'localhost') {
|
28
|
+
throw new TypeError('Loopback ClientID must use the "localhost" hostname')
|
29
|
+
}
|
30
|
+
|
31
|
+
if (url.hash) {
|
32
|
+
throw new TypeError('Loopback ClientID must not contain a fragment')
|
33
|
+
}
|
34
|
+
|
35
|
+
if (url.username || url.password) {
|
36
|
+
throw new TypeError('Loopback ClientID must not contain credentials')
|
37
|
+
}
|
38
|
+
|
39
|
+
if (url.port) {
|
40
|
+
throw new TypeError('Loopback ClientID must not contain a port')
|
41
|
+
}
|
42
|
+
|
43
|
+
// Note: url.pathname === '/' is allowed for loopback URIs
|
44
|
+
|
45
|
+
if (url.pathname !== '/' && url.pathname.endsWith('/')) {
|
46
|
+
throw new TypeError('Loopback ClientID must not end with a trailing slash')
|
47
|
+
}
|
48
|
+
|
49
|
+
if (url.pathname.includes('//')) {
|
50
|
+
throw new TypeError(
|
51
|
+
`Loopback ClientID must not contain any double slashes in its path`,
|
52
|
+
)
|
53
|
+
}
|
54
|
+
|
55
|
+
// Note: Query string is allowed
|
56
|
+
|
57
|
+
return url
|
58
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { OAuthClientId } from './oauth-client-id.js'
|
2
|
+
|
3
|
+
export function parseOAuthClientIdUrl(clientId: OAuthClientId): URL {
|
4
|
+
if (clientId.endsWith('/')) {
|
5
|
+
throw new TypeError('ClientID must not end with a trailing slash')
|
6
|
+
}
|
7
|
+
|
8
|
+
const url = new URL(clientId)
|
9
|
+
|
10
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
11
|
+
throw new TypeError('ClientID must use the "https:" or "http:" protocol')
|
12
|
+
}
|
13
|
+
|
14
|
+
url.searchParams.sort()
|
15
|
+
|
16
|
+
// URL constructor normalizes the URL, so we need to compare the canonical form
|
17
|
+
const canonicalUri = url.pathname === '/' ? url.origin + url.search : url.href
|
18
|
+
if (canonicalUri !== clientId) {
|
19
|
+
throw new TypeError(
|
20
|
+
`ClientID must be in canonical form ("${canonicalUri}", got "${clientId}")`,
|
21
|
+
)
|
22
|
+
}
|
23
|
+
|
24
|
+
return url
|
25
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
import { oauthClientIdSchema } from './oauth-client-id.js'
|
4
|
+
import { oauthClientCredentialsSchema } from './oauth-client-credentials.js'
|
5
|
+
|
6
|
+
export const oauthClientIdentificationSchema = z.union([
|
7
|
+
oauthClientCredentialsSchema,
|
8
|
+
// Must be last since it is less specific
|
9
|
+
z.object({ client_id: oauthClientIdSchema }),
|
10
|
+
])
|
11
|
+
|
12
|
+
export type OAuthClientIdentification = z.infer<
|
13
|
+
typeof oauthClientIdentificationSchema
|
14
|
+
>
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import { jwksPubSchema } from '@atproto/jwk'
|
2
|
+
import { z } from 'zod'
|
3
|
+
|
4
|
+
import { oauthClientIdSchema } from './oauth-client-id.js'
|
5
|
+
import { oauthEndpointAuthMethod } from './oauth-endpoint-auth-method.js'
|
6
|
+
import { oauthGrantTypeSchema } from './oauth-grant-type.js'
|
7
|
+
import { oauthResponseTypeSchema } from './oauth-response-type.js'
|
8
|
+
|
9
|
+
// https://openid.net/specs/openid-connect-registration-1_0.html
|
10
|
+
// https://datatracker.ietf.org/doc/html/rfc7591
|
11
|
+
export const oauthClientMetadataSchema = z.object({
|
12
|
+
redirect_uris: z.array(z.string().url()).nonempty(),
|
13
|
+
response_types: z
|
14
|
+
.array(oauthResponseTypeSchema)
|
15
|
+
.nonempty()
|
16
|
+
// > If omitted, the default is that the client will use only the "code"
|
17
|
+
// > response type.
|
18
|
+
.default(['code']),
|
19
|
+
grant_types: z
|
20
|
+
.array(oauthGrantTypeSchema)
|
21
|
+
.nonempty()
|
22
|
+
// > If omitted, the default behavior is that the client will use only the
|
23
|
+
// > "authorization_code" Grant Type.
|
24
|
+
.default(['authorization_code']),
|
25
|
+
scope: z.string().optional(),
|
26
|
+
token_endpoint_auth_method: oauthEndpointAuthMethod
|
27
|
+
.default('none')
|
28
|
+
.optional(),
|
29
|
+
token_endpoint_auth_signing_alg: z.string().optional(),
|
30
|
+
introspection_endpoint_auth_method: oauthEndpointAuthMethod.optional(),
|
31
|
+
introspection_endpoint_auth_signing_alg: z.string().optional(),
|
32
|
+
revocation_endpoint_auth_method: oauthEndpointAuthMethod.optional(),
|
33
|
+
revocation_endpoint_auth_signing_alg: z.string().optional(),
|
34
|
+
pushed_authorization_request_endpoint_auth_method:
|
35
|
+
oauthEndpointAuthMethod.optional(),
|
36
|
+
pushed_authorization_request_endpoint_auth_signing_alg: z.string().optional(),
|
37
|
+
userinfo_signed_response_alg: z.string().optional(),
|
38
|
+
userinfo_encrypted_response_alg: z.string().optional(),
|
39
|
+
jwks_uri: z.string().url().optional(),
|
40
|
+
jwks: jwksPubSchema.optional(),
|
41
|
+
application_type: z.enum(['web', 'native']).default('web').optional(), // default, per spec, is "web"
|
42
|
+
subject_type: z.enum(['public', 'pairwise']).default('public').optional(),
|
43
|
+
request_object_signing_alg: z.string().optional(),
|
44
|
+
id_token_signed_response_alg: z.string().optional(),
|
45
|
+
authorization_signed_response_alg: z.string().default('RS256').optional(),
|
46
|
+
authorization_encrypted_response_enc: z.enum(['A128CBC-HS256']).optional(),
|
47
|
+
authorization_encrypted_response_alg: z.string().optional(),
|
48
|
+
client_id: oauthClientIdSchema.optional(),
|
49
|
+
client_name: z.string().optional(),
|
50
|
+
client_uri: z.string().url().optional(),
|
51
|
+
policy_uri: z.string().url().optional(),
|
52
|
+
tos_uri: z.string().url().optional(),
|
53
|
+
logo_uri: z.string().url().optional(),
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Default Maximum Authentication Age. Specifies that the End-User MUST be
|
57
|
+
* actively authenticated if the End-User was authenticated longer ago than
|
58
|
+
* the specified number of seconds. The max_age request parameter overrides
|
59
|
+
* this default value. If omitted, no default Maximum Authentication Age is
|
60
|
+
* specified.
|
61
|
+
*/
|
62
|
+
default_max_age: z.number().optional(),
|
63
|
+
require_auth_time: z.boolean().optional(),
|
64
|
+
contacts: z.array(z.string().email()).optional(),
|
65
|
+
tls_client_certificate_bound_access_tokens: z.boolean().optional(),
|
66
|
+
|
67
|
+
// https://datatracker.ietf.org/doc/html/rfc9449#section-5.2
|
68
|
+
dpop_bound_access_tokens: z.boolean().optional(),
|
69
|
+
|
70
|
+
// https://datatracker.ietf.org/doc/html/rfc9396#section-14.5
|
71
|
+
authorization_details_types: z.array(z.string()).optional(),
|
72
|
+
})
|
73
|
+
|
74
|
+
export type OAuthClientMetadata = z.infer<typeof oauthClientMetadataSchema>
|
75
|
+
export type OAuthClientMetadataInput = z.input<typeof oauthClientMetadataSchema>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
export const oauthEndpointAuthMethod = z.enum([
|
4
|
+
'client_secret_basic',
|
5
|
+
'client_secret_jwt',
|
6
|
+
'client_secret_post',
|
7
|
+
'none',
|
8
|
+
'private_key_jwt',
|
9
|
+
'self_signed_tls_client_auth',
|
10
|
+
'tls_client_auth',
|
11
|
+
])
|
12
|
+
|
13
|
+
export type OauthEndpointAuthMethod = z.infer<typeof oauthEndpointAuthMethod>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
export const oauthGrantTypeSchema = z.enum([
|
4
|
+
'authorization_code',
|
5
|
+
'implicit',
|
6
|
+
'refresh_token',
|
7
|
+
'password', // Not part of OAuth 2.1
|
8
|
+
'client_credentials',
|
9
|
+
'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
10
|
+
'urn:ietf:params:oauth:grant-type:saml2-bearer',
|
11
|
+
])
|
12
|
+
|
13
|
+
export type OAuthGrantType = z.infer<typeof oauthGrantTypeSchema>
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
// try/catch to support running in a browser, including when process.env is
|
4
|
+
// shimmed (e.g. by webpack)
|
5
|
+
const ALLOW_INSECURE = (() => {
|
6
|
+
try {
|
7
|
+
const env = process.env.NODE_ENV
|
8
|
+
return env === 'development' || env === 'test'
|
9
|
+
} catch {
|
10
|
+
return false
|
11
|
+
}
|
12
|
+
})()
|
13
|
+
|
14
|
+
export const oauthIssuerIdentifierSchema = z
|
15
|
+
.string()
|
16
|
+
.url()
|
17
|
+
.superRefine((value, ctx) => {
|
18
|
+
// Validate the issuer (MIX-UP attacks)
|
19
|
+
|
20
|
+
if (value.endsWith('/')) {
|
21
|
+
ctx.addIssue({
|
22
|
+
code: z.ZodIssueCode.custom,
|
23
|
+
message: 'Issuer URL must not end with a slash',
|
24
|
+
})
|
25
|
+
}
|
26
|
+
|
27
|
+
const url = new URL(value)
|
28
|
+
|
29
|
+
if (url.protocol !== 'https:') {
|
30
|
+
if (ALLOW_INSECURE && url.protocol === 'http:') {
|
31
|
+
// We'll allow HTTP in development mode
|
32
|
+
} else {
|
33
|
+
ctx.addIssue({
|
34
|
+
code: z.ZodIssueCode.custom,
|
35
|
+
message: 'Issuer must be an HTTPS URL',
|
36
|
+
})
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
if (url.username || url.password) {
|
41
|
+
ctx.addIssue({
|
42
|
+
code: z.ZodIssueCode.custom,
|
43
|
+
message: 'Issuer URL must not contain a username or password',
|
44
|
+
})
|
45
|
+
}
|
46
|
+
|
47
|
+
if (url.hash || url.search) {
|
48
|
+
ctx.addIssue({
|
49
|
+
code: z.ZodIssueCode.custom,
|
50
|
+
message: 'Issuer URL must not contain a query or fragment',
|
51
|
+
})
|
52
|
+
}
|
53
|
+
|
54
|
+
const canonicalValue = url.pathname === '/' ? url.origin : url.href
|
55
|
+
if (value !== canonicalValue) {
|
56
|
+
ctx.addIssue({
|
57
|
+
code: z.ZodIssueCode.custom,
|
58
|
+
message: 'Issuer URL must be in the canonical form',
|
59
|
+
})
|
60
|
+
}
|
61
|
+
})
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
import { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#name-protected-resource-metadata-r}
|
7
|
+
*/
|
8
|
+
export const oauthProtectedResourceMetadataSchema = z.object({
|
9
|
+
/**
|
10
|
+
* REQUIRED. The protected resource's resource identifier, which is a URL that
|
11
|
+
* uses the https scheme and has no query or fragment components. Using these
|
12
|
+
* well-known resources is described in Section 3.
|
13
|
+
*/
|
14
|
+
resource: z.string().url(),
|
15
|
+
|
16
|
+
/**
|
17
|
+
* OPTIONAL. JSON array containing a list of OAuth authorization server issuer
|
18
|
+
* identifiers, as defined in [RFC8414], for authorization servers that can be
|
19
|
+
* used with this protected resource. Protected resources MAY choose not to
|
20
|
+
* advertise some supported authorization servers even when this parameter is
|
21
|
+
* used. In some use cases, the set of authorization servers will not be
|
22
|
+
* enumerable, in which case this metadata parameter would not be used.
|
23
|
+
*/
|
24
|
+
authorization_servers: z.array(oauthIssuerIdentifierSchema).optional(),
|
25
|
+
|
26
|
+
/**
|
27
|
+
* OPTIONAL. URL of the protected resource's JWK Set [JWK] document. This
|
28
|
+
* contains public keys belonging to the protected resource, such as signing
|
29
|
+
* key(s) that the resource server uses to sign resource responses. This URL
|
30
|
+
* MUST use the https scheme. When both signing and encryption keys are made
|
31
|
+
* available, a use (public key use) parameter value is REQUIRED for all keys
|
32
|
+
* in the referenced JWK Set to indicate each key's intended usage.
|
33
|
+
*/
|
34
|
+
jwks_uri: z.string().url().optional(),
|
35
|
+
|
36
|
+
/**
|
37
|
+
* RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope
|
38
|
+
* values that are used in authorization requests to request access to this
|
39
|
+
* protected resource. Protected resources MAY choose not to advertise some
|
40
|
+
* scope values supported even when this parameter is used.
|
41
|
+
*/
|
42
|
+
scopes_supported: z.array(z.string()).optional(),
|
43
|
+
|
44
|
+
/**
|
45
|
+
* OPTIONAL. JSON array containing a list of the supported methods of sending
|
46
|
+
* an OAuth 2.0 Bearer Token [RFC6750] to the protected resource. Defined
|
47
|
+
* values are ["header", "body", "query"], corresponding to Sections 2.1, 2.2,
|
48
|
+
* and 2.3 of RFC 6750.
|
49
|
+
*/
|
50
|
+
bearer_methods_supported: z
|
51
|
+
.array(z.enum(['header', 'body', 'query']))
|
52
|
+
.optional(),
|
53
|
+
|
54
|
+
/**
|
55
|
+
* OPTIONAL. JSON array containing a list of the JWS [JWS] signing algorithms
|
56
|
+
* (alg values) [JWA] supported by the protected resource for signing resource
|
57
|
+
* responses, for instance, as described in [FAPI.MessageSigning]. No default
|
58
|
+
* algorithms are implied if this entry is omitted. The value none MUST NOT be
|
59
|
+
* used.
|
60
|
+
*/
|
61
|
+
resource_signing_alg_values_supported: z.array(z.string()).optional(),
|
62
|
+
|
63
|
+
/**
|
64
|
+
* OPTIONAL. URL of a page containing human-readable information that
|
65
|
+
* developers might want or need to know when using the protected resource
|
66
|
+
*/
|
67
|
+
resource_documentation: z.string().url().optional(),
|
68
|
+
|
69
|
+
/**
|
70
|
+
* OPTIONAL. URL that the protected resource provides to read about the
|
71
|
+
* protected resource's requirements on how the client can use the data
|
72
|
+
* provided by the protected resource
|
73
|
+
*/
|
74
|
+
resource_policy_uri: z.string().url().optional(),
|
75
|
+
|
76
|
+
/**
|
77
|
+
* OPTIONAL. URL that the protected resource provides to read about the
|
78
|
+
* protected resource's terms of service
|
79
|
+
*/
|
80
|
+
resource_tos_uri: z.string().url().optional(),
|
81
|
+
})
|
82
|
+
|
83
|
+
export type OAuthProtectedResourceMetadata = z.infer<
|
84
|
+
typeof oauthProtectedResourceMetadataSchema
|
85
|
+
>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
export const oauthResponseTypeSchema = z.enum([
|
4
|
+
// OAuth
|
5
|
+
'code', // Authorization Code Grant
|
6
|
+
'token', // Implicit Grant
|
7
|
+
|
8
|
+
// OpenID
|
9
|
+
'none',
|
10
|
+
'code id_token token',
|
11
|
+
'code id_token',
|
12
|
+
'code token',
|
13
|
+
'id_token token',
|
14
|
+
'id_token',
|
15
|
+
])
|
16
|
+
|
17
|
+
export type OAuthResponseType = z.infer<typeof oauthResponseTypeSchema>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { signedJwtSchema } from '@atproto/jwk'
|
2
|
+
import { z } from 'zod'
|
3
|
+
|
4
|
+
import { oauthAuthorizationDetailsSchema } from './oauth-authorization-details.js'
|
5
|
+
import { oauthTokenTypeSchema } from './oauth-token-type.js'
|
6
|
+
|
7
|
+
/**
|
8
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1 | RFC 6749 (OAuth2), Section 5.1}
|
9
|
+
*/
|
10
|
+
export const oauthTokenResponseSchema = z
|
11
|
+
.object({
|
12
|
+
access_token: z.string(),
|
13
|
+
token_type: oauthTokenTypeSchema,
|
14
|
+
issuer: z.string().url().optional(),
|
15
|
+
sub: z.string().optional(),
|
16
|
+
scope: z.string().optional(),
|
17
|
+
id_token: signedJwtSchema.optional(),
|
18
|
+
refresh_token: z.string().optional(),
|
19
|
+
expires_in: z.number().optional(),
|
20
|
+
authorization_details: oauthAuthorizationDetailsSchema.optional(),
|
21
|
+
})
|
22
|
+
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
23
|
+
// > The client MUST ignore unrecognized value names in the response.
|
24
|
+
.passthrough()
|
25
|
+
|
26
|
+
/**
|
27
|
+
* @see {@link oauthTokenResponseSchema}
|
28
|
+
*/
|
29
|
+
export type OAuthTokenResponse = z.infer<typeof oauthTokenResponseSchema>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
// Case insensitive input, normalized output
|
4
|
+
export const oauthTokenTypeSchema = z.union([
|
5
|
+
z
|
6
|
+
.string()
|
7
|
+
.regex(/^DPoP$/i)
|
8
|
+
.transform(() => 'DPoP' as const),
|
9
|
+
z
|
10
|
+
.string()
|
11
|
+
.regex(/^Bearer$/i)
|
12
|
+
.transform(() => 'Bearer' as const),
|
13
|
+
])
|
14
|
+
|
15
|
+
export type OAuthTokenType = z.infer<typeof oauthTokenTypeSchema>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
export const oidcClaimsParameterSchema = z.enum([
|
4
|
+
// https://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html#rfc.section.5.2
|
5
|
+
// if client metadata "require_auth_time" is true, this *must* be provided
|
6
|
+
'auth_time',
|
7
|
+
|
8
|
+
// OIDC
|
9
|
+
'nonce',
|
10
|
+
'acr',
|
11
|
+
|
12
|
+
// OpenID: "profile" scope
|
13
|
+
'name',
|
14
|
+
'family_name',
|
15
|
+
'given_name',
|
16
|
+
'middle_name',
|
17
|
+
'nickname',
|
18
|
+
'preferred_username',
|
19
|
+
'gender',
|
20
|
+
'picture',
|
21
|
+
'profile',
|
22
|
+
'website',
|
23
|
+
'birthdate',
|
24
|
+
'zoneinfo',
|
25
|
+
'locale',
|
26
|
+
'updated_at',
|
27
|
+
|
28
|
+
// OpenID: "email" scope
|
29
|
+
'email',
|
30
|
+
'email_verified',
|
31
|
+
|
32
|
+
// OpenID: "phone" scope
|
33
|
+
'phone_number',
|
34
|
+
'phone_number_verified',
|
35
|
+
|
36
|
+
// OpenID: "address" scope
|
37
|
+
'address',
|
38
|
+
])
|
39
|
+
|
40
|
+
export type OidcClaimsParameter = z.infer<typeof oidcClaimsParameterSchema>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
const oidcClaimsValueSchema = z.union([z.string(), z.number(), z.boolean()])
|
4
|
+
|
5
|
+
export const oidcClaimsPropertiesSchema = z.object({
|
6
|
+
essential: z.boolean().optional(),
|
7
|
+
value: oidcClaimsValueSchema.optional(),
|
8
|
+
values: z.array(oidcClaimsValueSchema).optional(),
|
9
|
+
})
|
10
|
+
|
11
|
+
export type OidcClaimsProperties = z.infer<typeof oidcClaimsPropertiesSchema>
|
package/src/util.ts
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
export function isIP(hostname: string) {
|
2
|
+
// IPv4
|
3
|
+
if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/)) return true
|
4
|
+
|
5
|
+
// IPv6
|
6
|
+
if (hostname.startsWith('[') && hostname.endsWith(']')) return true
|
7
|
+
|
8
|
+
return false
|
9
|
+
}
|
10
|
+
|
11
|
+
export type LoopbackHost = 'localhost' | '127.0.0.1' | '[::1]'
|
12
|
+
|
13
|
+
export function isLoopbackHost(host: unknown): host is LoopbackHost {
|
14
|
+
return host === 'localhost' || host === '127.0.0.1' || host === '[::1]'
|
15
|
+
}
|
16
|
+
|
17
|
+
export function isLoopbackUrl(input: URL | string): boolean {
|
18
|
+
const url = typeof input === 'string' ? new URL(input) : input
|
19
|
+
return isLoopbackHost(url.hostname)
|
20
|
+
}
|
package/tsconfig.json
ADDED