@atproto/oauth-types 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +3 -0
  4. package/dist/access-token.d.ts +4 -0
  5. package/dist/access-token.d.ts.map +1 -0
  6. package/dist/access-token.js +6 -0
  7. package/dist/access-token.js.map +1 -0
  8. package/dist/atproto-loopback-client-metadata.d.ts +3 -0
  9. package/dist/atproto-loopback-client-metadata.d.ts.map +1 -0
  10. package/dist/atproto-loopback-client-metadata.js +26 -0
  11. package/dist/atproto-loopback-client-metadata.js.map +1 -0
  12. package/dist/constants.d.ts +3 -0
  13. package/dist/constants.d.ts.map +1 -0
  14. package/dist/constants.js +11 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/index.d.ts +27 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +43 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/oauth-authentication-request-parameters.d.ts +128 -0
  21. package/dist/oauth-authentication-request-parameters.d.ts.map +1 -0
  22. package/dist/oauth-authentication-request-parameters.js +76 -0
  23. package/dist/oauth-authentication-request-parameters.js.map +1 -0
  24. package/dist/oauth-authorization-details.d.ts +54 -0
  25. package/dist/oauth-authorization-details.d.ts.map +1 -0
  26. package/dist/oauth-authorization-details.js +20 -0
  27. package/dist/oauth-authorization-details.js.map +1 -0
  28. package/dist/oauth-authorization-server-metadata.d.ts +428 -0
  29. package/dist/oauth-authorization-server-metadata.d.ts.map +1 -0
  30. package/dist/oauth-authorization-server-metadata.js +88 -0
  31. package/dist/oauth-authorization-server-metadata.js.map +1 -0
  32. package/dist/oauth-client-credentials.d.ts +66 -0
  33. package/dist/oauth-client-credentials.d.ts.map +1 -0
  34. package/dist/oauth-client-credentials.js +30 -0
  35. package/dist/oauth-client-credentials.js.map +1 -0
  36. package/dist/oauth-client-id-discoverable.d.ts +8 -0
  37. package/dist/oauth-client-id-discoverable.d.ts.map +1 -0
  38. package/dist/oauth-client-id-discoverable.js +48 -0
  39. package/dist/oauth-client-id-discoverable.js.map +1 -0
  40. package/dist/oauth-client-id-loopback.d.ts +5 -0
  41. package/dist/oauth-client-id-loopback.d.ts.map +1 -0
  42. package/dist/oauth-client-id-loopback.js +44 -0
  43. package/dist/oauth-client-id-loopback.js.map +1 -0
  44. package/dist/oauth-client-id-url.d.ts +3 -0
  45. package/dist/oauth-client-id-url.d.ts.map +1 -0
  46. package/dist/oauth-client-id-url.js +21 -0
  47. package/dist/oauth-client-id-url.js.map +1 -0
  48. package/dist/oauth-client-id.d.ts +4 -0
  49. package/dist/oauth-client-id.d.ts.map +1 -0
  50. package/dist/oauth-client-id.js +6 -0
  51. package/dist/oauth-client-id.js.map +1 -0
  52. package/dist/oauth-client-identification.d.ts +31 -0
  53. package/dist/oauth-client-identification.d.ts.map +1 -0
  54. package/dist/oauth-client-identification.js +12 -0
  55. package/dist/oauth-client-identification.js.map +1 -0
  56. package/dist/oauth-client-metadata.d.ts +1576 -0
  57. package/dist/oauth-client-metadata.d.ts.map +1 -0
  58. package/dist/oauth-client-metadata.js +70 -0
  59. package/dist/oauth-client-metadata.js.map +1 -0
  60. package/dist/oauth-endpoint-auth-method.d.ts +4 -0
  61. package/dist/oauth-endpoint-auth-method.d.ts.map +1 -0
  62. package/dist/oauth-endpoint-auth-method.js +14 -0
  63. package/dist/oauth-endpoint-auth-method.js.map +1 -0
  64. package/dist/oauth-endpoint-name.d.ts +2 -0
  65. package/dist/oauth-endpoint-name.d.ts.map +1 -0
  66. package/dist/oauth-endpoint-name.js +3 -0
  67. package/dist/oauth-endpoint-name.js.map +1 -0
  68. package/dist/oauth-grant-type.d.ts +4 -0
  69. package/dist/oauth-grant-type.d.ts.map +1 -0
  70. package/dist/oauth-grant-type.js +14 -0
  71. package/dist/oauth-grant-type.js.map +1 -0
  72. package/dist/oauth-issuer-identifier.d.ts +3 -0
  73. package/dist/oauth-issuer-identifier.d.ts.map +1 -0
  74. package/dist/oauth-issuer-identifier.js +59 -0
  75. package/dist/oauth-issuer-identifier.js.map +1 -0
  76. package/dist/oauth-par-response.d.ts +10 -0
  77. package/dist/oauth-par-response.d.ts.map +1 -0
  78. package/dist/oauth-par-response.js +8 -0
  79. package/dist/oauth-par-response.js.map +1 -0
  80. package/dist/oauth-protected-resource-metadata.d.ts +90 -0
  81. package/dist/oauth-protected-resource-metadata.d.ts.map +1 -0
  82. package/dist/oauth-protected-resource-metadata.js +75 -0
  83. package/dist/oauth-protected-resource-metadata.js.map +1 -0
  84. package/dist/oauth-response-mode.d.ts +4 -0
  85. package/dist/oauth-response-mode.d.ts.map +1 -0
  86. package/dist/oauth-response-mode.js +10 -0
  87. package/dist/oauth-response-mode.js.map +1 -0
  88. package/dist/oauth-response-type.d.ts +4 -0
  89. package/dist/oauth-response-type.d.ts.map +1 -0
  90. package/dist/oauth-response-type.js +17 -0
  91. package/dist/oauth-response-type.js.map +1 -0
  92. package/dist/oauth-token-response.d.ts +103 -0
  93. package/dist/oauth-token-response.d.ts.map +1 -0
  94. package/dist/oauth-token-response.js +26 -0
  95. package/dist/oauth-token-response.js.map +1 -0
  96. package/dist/oauth-token-type.d.ts +4 -0
  97. package/dist/oauth-token-type.d.ts.map +1 -0
  98. package/dist/oauth-token-type.js +16 -0
  99. package/dist/oauth-token-type.js.map +1 -0
  100. package/dist/oidc-claims-parameter.d.ts +4 -0
  101. package/dist/oidc-claims-parameter.d.ts.map +1 -0
  102. package/dist/oidc-claims-parameter.js +36 -0
  103. package/dist/oidc-claims-parameter.js.map +1 -0
  104. package/dist/oidc-claims-properties.d.ts +16 -0
  105. package/dist/oidc-claims-properties.d.ts.map +1 -0
  106. package/dist/oidc-claims-properties.js +11 -0
  107. package/dist/oidc-claims-properties.js.map +1 -0
  108. package/dist/oidc-entity-type.d.ts +4 -0
  109. package/dist/oidc-entity-type.d.ts.map +1 -0
  110. package/dist/oidc-entity-type.js +6 -0
  111. package/dist/oidc-entity-type.js.map +1 -0
  112. package/dist/util.d.ts +5 -0
  113. package/dist/util.d.ts.map +1 -0
  114. package/dist/util.js +23 -0
  115. package/dist/util.js.map +1 -0
  116. package/package.json +37 -0
  117. package/src/access-token.ts +4 -0
  118. package/src/atproto-loopback-client-metadata.ts +30 -0
  119. package/src/constants.ts +9 -0
  120. package/src/index.ts +27 -0
  121. package/src/oauth-authentication-request-parameters.ts +104 -0
  122. package/src/oauth-authorization-details.ts +28 -0
  123. package/src/oauth-authorization-server-metadata.ts +106 -0
  124. package/src/oauth-client-credentials.ts +34 -0
  125. package/src/oauth-client-id-discoverable.ts +66 -0
  126. package/src/oauth-client-id-loopback.ts +58 -0
  127. package/src/oauth-client-id-url.ts +25 -0
  128. package/src/oauth-client-id.ts +4 -0
  129. package/src/oauth-client-identification.ts +14 -0
  130. package/src/oauth-client-metadata.ts +75 -0
  131. package/src/oauth-endpoint-auth-method.ts +13 -0
  132. package/src/oauth-endpoint-name.ts +5 -0
  133. package/src/oauth-grant-type.ts +13 -0
  134. package/src/oauth-issuer-identifier.ts +61 -0
  135. package/src/oauth-par-response.ts +7 -0
  136. package/src/oauth-protected-resource-metadata.ts +85 -0
  137. package/src/oauth-response-mode.ts +9 -0
  138. package/src/oauth-response-type.ts +17 -0
  139. package/src/oauth-token-response.ts +29 -0
  140. package/src/oauth-token-type.ts +15 -0
  141. package/src/oidc-claims-parameter.ts +40 -0
  142. package/src/oidc-claims-properties.ts +11 -0
  143. package/src/oidc-entity-type.ts +5 -0
  144. package/src/util.ts +20 -0
  145. package/tsconfig.build.json +8 -0
  146. 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,4 @@
1
+ import { z } from 'zod'
2
+
3
+ export const oauthClientIdSchema = z.string().min(1)
4
+ export type OAuthClientId = z.infer<typeof oauthClientIdSchema>
@@ -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,5 @@
1
+ export type OAuthEndpointName =
2
+ | 'token'
3
+ | 'revocation'
4
+ | 'introspection'
5
+ | 'pushed_authorization_request'
@@ -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,7 @@
1
+ import { z } from 'zod'
2
+
3
+ export const oauthParResponseSchema = z.object({
4
+ request_uri: z.string(),
5
+ })
6
+
7
+ export type OAuthParResponse = z.infer<typeof oauthParResponseSchema>
@@ -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,9 @@
1
+ import { z } from 'zod'
2
+
3
+ export const oauthResponseModeSchema = z.enum([
4
+ 'query',
5
+ 'fragment',
6
+ 'form_post',
7
+ ])
8
+
9
+ export type OAuthResponseMode = z.infer<typeof oauthResponseModeSchema>
@@ -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>
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod'
2
+
3
+ export const oidcEntityTypeSchema = z.enum(['userinfo', 'id_token'])
4
+
5
+ export type OidcEntityType = z.infer<typeof oidcEntityTypeSchema>
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
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../../tsconfig/isomorphic.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["./src"]
8
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "include": [],
3
+ "references": [{ "path": "./tsconfig.build.json" }]
4
+ }