@atproto/oauth-types 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }