@atproto/oauth-provider 0.2.0 → 0.2.2

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 (170) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/account/account-store.d.ts +2 -2
  3. package/dist/assets/app/bundle-manifest.json +3 -3
  4. package/dist/assets/app/main.css +1 -1
  5. package/dist/assets/app/main.js +3 -3
  6. package/dist/assets/app/main.js.map +1 -1
  7. package/dist/assets/assets-middleware.d.ts.map +1 -1
  8. package/dist/assets/assets-middleware.js +4 -2
  9. package/dist/assets/assets-middleware.js.map +1 -1
  10. package/dist/client/client-manager.d.ts.map +1 -1
  11. package/dist/client/client-manager.js +127 -118
  12. package/dist/client/client-manager.js.map +1 -1
  13. package/dist/client/client-utils.d.ts +1 -2
  14. package/dist/client/client-utils.d.ts.map +1 -1
  15. package/dist/client/client-utils.js +3 -12
  16. package/dist/client/client-utils.js.map +1 -1
  17. package/dist/client/client.d.ts +8 -3
  18. package/dist/client/client.d.ts.map +1 -1
  19. package/dist/client/client.js +70 -1
  20. package/dist/client/client.js.map +1 -1
  21. package/dist/constants.d.ts +0 -1
  22. package/dist/constants.d.ts.map +1 -1
  23. package/dist/constants.js +1 -2
  24. package/dist/constants.js.map +1 -1
  25. package/dist/errors/access-denied-error.d.ts +4 -4
  26. package/dist/errors/access-denied-error.d.ts.map +1 -1
  27. package/dist/errors/access-denied-error.js +2 -2
  28. package/dist/errors/access-denied-error.js.map +1 -1
  29. package/dist/errors/account-selection-required-error.d.ts +2 -2
  30. package/dist/errors/account-selection-required-error.d.ts.map +1 -1
  31. package/dist/errors/account-selection-required-error.js.map +1 -1
  32. package/dist/errors/consent-required-error.d.ts +2 -2
  33. package/dist/errors/consent-required-error.d.ts.map +1 -1
  34. package/dist/errors/consent-required-error.js.map +1 -1
  35. package/dist/errors/invalid-authorization-details-error.d.ts +2 -2
  36. package/dist/errors/invalid-authorization-details-error.d.ts.map +1 -1
  37. package/dist/errors/invalid-authorization-details-error.js.map +1 -1
  38. package/dist/errors/invalid-client-id-error.d.ts +1 -1
  39. package/dist/errors/invalid-client-id-error.d.ts.map +1 -1
  40. package/dist/errors/invalid-client-id-error.js +12 -6
  41. package/dist/errors/invalid-client-id-error.js.map +1 -1
  42. package/dist/errors/invalid-client-metadata-error.d.ts +1 -1
  43. package/dist/errors/invalid-client-metadata-error.d.ts.map +1 -1
  44. package/dist/errors/invalid-client-metadata-error.js +11 -3
  45. package/dist/errors/invalid-client-metadata-error.js.map +1 -1
  46. package/dist/errors/invalid-parameters-error.d.ts +2 -2
  47. package/dist/errors/invalid-parameters-error.d.ts.map +1 -1
  48. package/dist/errors/invalid-parameters-error.js.map +1 -1
  49. package/dist/errors/invalid-scope-error.d.ts +9 -0
  50. package/dist/errors/invalid-scope-error.d.ts.map +1 -0
  51. package/dist/errors/invalid-scope-error.js +14 -0
  52. package/dist/errors/invalid-scope-error.js.map +1 -0
  53. package/dist/errors/login-required-error.d.ts +2 -2
  54. package/dist/errors/login-required-error.d.ts.map +1 -1
  55. package/dist/errors/login-required-error.js.map +1 -1
  56. package/dist/lib/html/html.d.ts +1 -1
  57. package/dist/lib/html/html.d.ts.map +1 -1
  58. package/dist/lib/html/html.js +14 -11
  59. package/dist/lib/html/html.js.map +1 -1
  60. package/dist/lib/http/parser.d.ts +9 -2
  61. package/dist/lib/http/parser.d.ts.map +1 -1
  62. package/dist/lib/http/parser.js +15 -7
  63. package/dist/lib/http/parser.js.map +1 -1
  64. package/dist/lib/http/request.d.ts +0 -23
  65. package/dist/lib/http/request.d.ts.map +1 -1
  66. package/dist/lib/http/request.js +1 -11
  67. package/dist/lib/http/request.js.map +1 -1
  68. package/dist/lib/http/stream.d.ts +28 -6
  69. package/dist/lib/http/stream.d.ts.map +1 -1
  70. package/dist/lib/http/stream.js +21 -32
  71. package/dist/lib/http/stream.js.map +1 -1
  72. package/dist/lib/util/authorization-header.d.ts.map +1 -1
  73. package/dist/lib/util/authorization-header.js +1 -1
  74. package/dist/lib/util/authorization-header.js.map +1 -1
  75. package/dist/lib/util/hostname.d.ts +3 -2
  76. package/dist/lib/util/hostname.d.ts.map +1 -1
  77. package/dist/lib/util/hostname.js +12 -8
  78. package/dist/lib/util/hostname.js.map +1 -1
  79. package/dist/metadata/build-metadata.d.ts.map +1 -1
  80. package/dist/metadata/build-metadata.js +2 -1
  81. package/dist/metadata/build-metadata.js.map +1 -1
  82. package/dist/oauth-errors.d.ts +1 -0
  83. package/dist/oauth-errors.d.ts.map +1 -1
  84. package/dist/oauth-errors.js +3 -1
  85. package/dist/oauth-errors.js.map +1 -1
  86. package/dist/oauth-hooks.d.ts +3 -3
  87. package/dist/oauth-hooks.d.ts.map +1 -1
  88. package/dist/oauth-provider.d.ts +20 -22
  89. package/dist/oauth-provider.d.ts.map +1 -1
  90. package/dist/oauth-provider.js +234 -176
  91. package/dist/oauth-provider.js.map +1 -1
  92. package/dist/oauth-verifier.d.ts +2 -2
  93. package/dist/oauth-verifier.d.ts.map +1 -1
  94. package/dist/oauth-verifier.js.map +1 -1
  95. package/dist/output/build-authorize-data.d.ts +2 -2
  96. package/dist/output/build-authorize-data.d.ts.map +1 -1
  97. package/dist/output/send-authorize-redirect.d.ts +2 -4
  98. package/dist/output/send-authorize-redirect.d.ts.map +1 -1
  99. package/dist/output/send-authorize-redirect.js +5 -2
  100. package/dist/output/send-authorize-redirect.js.map +1 -1
  101. package/dist/request/request-data.d.ts +2 -2
  102. package/dist/request/request-data.d.ts.map +1 -1
  103. package/dist/request/request-info.d.ts +2 -2
  104. package/dist/request/request-info.d.ts.map +1 -1
  105. package/dist/request/request-manager.d.ts +4 -4
  106. package/dist/request/request-manager.d.ts.map +1 -1
  107. package/dist/request/request-manager.js +94 -60
  108. package/dist/request/request-manager.js.map +1 -1
  109. package/dist/signer/signed-token-payload.d.ts +122 -122
  110. package/dist/signer/signer.d.ts +41 -40
  111. package/dist/signer/signer.d.ts.map +1 -1
  112. package/dist/signer/signer.js +13 -15
  113. package/dist/signer/signer.js.map +1 -1
  114. package/dist/token/token-claims.d.ts +121 -121
  115. package/dist/token/token-data.d.ts +3 -3
  116. package/dist/token/token-data.d.ts.map +1 -1
  117. package/dist/token/token-manager.d.ts +4 -5
  118. package/dist/token/token-manager.d.ts.map +1 -1
  119. package/dist/token/token-manager.js +96 -72
  120. package/dist/token/token-manager.js.map +1 -1
  121. package/dist/token/verify-token-claims.d.ts +3 -3
  122. package/dist/token/verify-token-claims.d.ts.map +1 -1
  123. package/dist/token/verify-token-claims.js.map +1 -1
  124. package/package.json +7 -6
  125. package/src/assets/app/components/sign-in-form.tsx +31 -2
  126. package/src/assets/app/components/url-viewer.tsx +3 -3
  127. package/src/assets/assets-middleware.ts +4 -2
  128. package/src/client/client-manager.ts +163 -161
  129. package/src/client/client-utils.ts +7 -12
  130. package/src/client/client.ts +112 -3
  131. package/src/constants.ts +0 -2
  132. package/src/errors/access-denied-error.ts +10 -4
  133. package/src/errors/account-selection-required-error.ts +2 -2
  134. package/src/errors/consent-required-error.ts +2 -2
  135. package/src/errors/invalid-authorization-details-error.ts +2 -2
  136. package/src/errors/invalid-client-id-error.ts +15 -4
  137. package/src/errors/invalid-client-metadata-error.ts +15 -3
  138. package/src/errors/invalid-parameters-error.ts +2 -2
  139. package/src/errors/invalid-scope-error.ts +15 -0
  140. package/src/errors/login-required-error.ts +2 -2
  141. package/src/lib/html/html.ts +14 -12
  142. package/src/lib/http/parser.ts +21 -8
  143. package/src/lib/http/request.ts +1 -23
  144. package/src/lib/http/stream.ts +29 -60
  145. package/src/lib/util/authorization-header.ts +5 -2
  146. package/src/lib/util/hostname.ts +9 -5
  147. package/src/metadata/build-metadata.ts +3 -1
  148. package/src/oauth-errors.ts +1 -0
  149. package/src/oauth-hooks.ts +3 -3
  150. package/src/oauth-provider.ts +368 -269
  151. package/src/oauth-verifier.ts +2 -2
  152. package/src/output/build-authorize-data.ts +2 -2
  153. package/src/output/send-authorize-redirect.ts +7 -6
  154. package/src/request/request-data.ts +2 -2
  155. package/src/request/request-info.ts +2 -2
  156. package/src/request/request-manager.ts +129 -103
  157. package/src/signer/signer.ts +24 -25
  158. package/src/token/token-data.ts +3 -3
  159. package/src/token/token-manager.ts +141 -99
  160. package/src/token/verify-token-claims.ts +3 -3
  161. package/dist/request/types.d.ts +0 -328
  162. package/dist/request/types.d.ts.map +0 -1
  163. package/dist/request/types.js +0 -27
  164. package/dist/request/types.js.map +0 -1
  165. package/dist/token/types.d.ts +0 -250
  166. package/dist/token/types.d.ts.map +0 -1
  167. package/dist/token/types.js +0 -36
  168. package/dist/token/types.js.map +0 -1
  169. package/src/request/types.ts +0 -48
  170. package/src/token/types.ts +0 -86
package/src/constants.ts CHANGED
@@ -18,8 +18,6 @@ export const REQUEST_ID_BYTES_LENGTH = 16 // 128 bits
18
18
  export const CODE_PREFIX = 'cod-'
19
19
  export const CODE_BYTES_LENGTH = 32
20
20
 
21
- export const ALLOW_LOOPBACK_CLIENT_REFRESH_TOKEN = true
22
-
23
21
  const SECOND = 1e3
24
22
  const MINUTE = 60 * SECOND
25
23
  const HOUR = 60 * MINUTE
@@ -1,10 +1,10 @@
1
- import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types'
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
2
  import { buildErrorPayload } from '../output/build-error-payload.js'
3
3
  import { OAuthError } from './oauth-error.js'
4
4
 
5
5
  export class AccessDeniedError extends OAuthError {
6
6
  constructor(
7
- public readonly parameters: OAuthAuthenticationRequestParameters,
7
+ public readonly parameters: OAuthAuthorizationRequestParameters,
8
8
  error_description: string,
9
9
  error = 'access_denied',
10
10
  cause?: unknown,
@@ -13,14 +13,20 @@ export class AccessDeniedError extends OAuthError {
13
13
  }
14
14
 
15
15
  static from(
16
- parameters: OAuthAuthenticationRequestParameters,
16
+ parameters: OAuthAuthorizationRequestParameters,
17
17
  cause?: unknown,
18
+ fallbackError?: string,
18
19
  ) {
19
20
  if (cause && cause instanceof AccessDeniedError) {
20
21
  return cause
21
22
  }
22
23
 
23
24
  const { error, error_description } = buildErrorPayload(cause)
24
- return new AccessDeniedError(parameters, error_description, error, cause)
25
+ return new AccessDeniedError(
26
+ parameters,
27
+ error_description,
28
+ fallbackError ?? error,
29
+ cause,
30
+ )
25
31
  }
26
32
  }
@@ -1,9 +1,9 @@
1
- import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types'
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
2
  import { AccessDeniedError } from './access-denied-error.js'
3
3
 
4
4
  export class AccountSelectionRequiredError extends AccessDeniedError {
5
5
  constructor(
6
- parameters: OAuthAuthenticationRequestParameters,
6
+ parameters: OAuthAuthorizationRequestParameters,
7
7
  error_description = 'Account selection required',
8
8
  cause?: unknown,
9
9
  ) {
@@ -1,9 +1,9 @@
1
- import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types'
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
2
  import { AccessDeniedError } from './access-denied-error.js'
3
3
 
4
4
  export class ConsentRequiredError extends AccessDeniedError {
5
5
  constructor(
6
- parameters: OAuthAuthenticationRequestParameters,
6
+ parameters: OAuthAuthorizationRequestParameters,
7
7
  error_description = 'User consent required',
8
8
  cause?: unknown,
9
9
  ) {
@@ -1,4 +1,4 @@
1
- import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types'
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
2
  import { AccessDeniedError } from './access-denied-error.js'
3
3
 
4
4
  /**
@@ -18,7 +18,7 @@ import { AccessDeniedError } from './access-denied-error.js'
18
18
  */
19
19
  export class InvalidAuthorizationDetailsError extends AccessDeniedError {
20
20
  constructor(
21
- parameters: OAuthAuthenticationRequestParameters,
21
+ parameters: OAuthAuthorizationRequestParameters,
22
22
  error_description: string,
23
23
  cause?: unknown,
24
24
  ) {
@@ -12,9 +12,20 @@ export class InvalidClientIdError extends OAuthError {
12
12
  super('invalid_client_id', error_description, 400, cause)
13
13
  }
14
14
 
15
- static from(err: unknown): InvalidClientIdError {
16
- if (err instanceof InvalidClientIdError) return err
17
- if (err instanceof TypeError) return new InvalidClientIdError(err.message)
18
- return new InvalidClientIdError('Invalid client identifier', err)
15
+ static from(
16
+ cause: unknown,
17
+ fallbackMessage = 'Invalid client identifier',
18
+ ): InvalidClientIdError {
19
+ if (cause instanceof InvalidClientIdError) {
20
+ return cause
21
+ }
22
+ if (cause instanceof TypeError) {
23
+ // This method is meant to be used in the context of parsing & validating
24
+ // a client client metadata. In that context, a TypeError would more
25
+ // likely represent a problem with the data (e.g. invalid URL constructor
26
+ // arg) and not a programming error.
27
+ return new InvalidClientIdError(cause.message, cause)
28
+ }
29
+ return new InvalidClientIdError(fallbackMessage, cause)
19
30
  }
20
31
  }
@@ -12,8 +12,20 @@ export class InvalidClientMetadataError extends OAuthError {
12
12
  super('invalid_client_metadata', error_description, 400, cause)
13
13
  }
14
14
 
15
- static from(cause: unknown): InvalidClientMetadataError {
16
- if (cause instanceof InvalidClientMetadataError) return cause
17
- return new InvalidClientMetadataError('Invalid client configuration', cause)
15
+ static from(
16
+ cause: unknown,
17
+ fallbackMessage = 'Invalid client metadata document',
18
+ ): InvalidClientMetadataError {
19
+ if (cause instanceof InvalidClientMetadataError) {
20
+ return cause
21
+ }
22
+ if (cause instanceof TypeError) {
23
+ // This method is meant to be used in the context of parsing & validating
24
+ // a client client metadata. In that context, a TypeError would more
25
+ // likely represent a problem with the data (e.g. invalid URL constructor
26
+ // arg) and not a programming error.
27
+ return new InvalidClientMetadataError(cause.message, cause)
28
+ }
29
+ return new InvalidClientMetadataError(fallbackMessage, cause)
18
30
  }
19
31
  }
@@ -1,9 +1,9 @@
1
- import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types'
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
2
  import { AccessDeniedError } from './access-denied-error.js'
3
3
 
4
4
  export class InvalidParametersError extends AccessDeniedError {
5
5
  constructor(
6
- parameters: OAuthAuthenticationRequestParameters,
6
+ parameters: OAuthAuthorizationRequestParameters,
7
7
  error_description: string,
8
8
  cause?: unknown,
9
9
  ) {
@@ -0,0 +1,15 @@
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
+ import { AccessDeniedError } from './access-denied-error.js'
3
+
4
+ /**
5
+ * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.2.1}
6
+ */
7
+ export class InvalidScopeError extends AccessDeniedError {
8
+ constructor(
9
+ parameters: OAuthAuthorizationRequestParameters,
10
+ error_description: string,
11
+ cause?: unknown,
12
+ ) {
13
+ super(parameters, error_description, 'invalid_scope', cause)
14
+ }
15
+ }
@@ -1,9 +1,9 @@
1
- import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types'
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
2
  import { AccessDeniedError } from './access-denied-error.js'
3
3
 
4
4
  export class LoginRequiredError extends AccessDeniedError {
5
5
  constructor(
6
- parameters: OAuthAuthenticationRequestParameters,
6
+ parameters: OAuthAuthorizationRequestParameters,
7
7
  error_description = 'Login is required',
8
8
  cause?: unknown,
9
9
  ) {
@@ -6,7 +6,7 @@ const symbol = Symbol('Html.dangerouslyCreate')
6
6
  * This class represents trusted HTML that can be safely embedded in a web page,
7
7
  * or used as fragments to build a larger HTML document.
8
8
  */
9
- export class Html {
9
+ export class Html implements Iterable<string> {
10
10
  #fragments: Iterable<Html | string>
11
11
 
12
12
  private constructor(fragments: Iterable<Html | string>, guard: symbol) {
@@ -22,22 +22,19 @@ export class Html {
22
22
  }
23
23
 
24
24
  toString(): string {
25
- // Lazily compute & join the fragments when they are used, to avoid
26
- // unnecessary intermediate strings when concatenating multiple Html as
27
- // fragments.
25
+ let result = ''
26
+ for (const fragment of this) result += fragment
27
+
28
+ // Cache result for future calls
28
29
  if (
29
30
  !Array.isArray(this.#fragments) ||
30
31
  this.#fragments.length > 1 ||
31
32
  !this.#fragments.every(isString)
32
33
  ) {
33
- // Will call `toString` recursively, as well as generating iterator
34
- // results.
35
- const fragment = Array.from(this.#fragments, String).join('')
36
- this.#fragments = [fragment] // Cache result for future calls
37
- return fragment
34
+ this.#fragments = result ? [result] : []
38
35
  }
39
36
 
40
- return this.#fragments.join('')
37
+ return result
41
38
  }
42
39
 
43
40
  [Symbol.toPrimitive](hint): string {
@@ -51,8 +48,13 @@ export class Html {
51
48
  }
52
49
 
53
50
  *[Symbol.iterator](): IterableIterator<string> {
54
- // Using toString() here to use the optimized path for string concatenation
55
- yield this.toString()
51
+ for (const fragment of this.#fragments) {
52
+ if (typeof fragment === 'string') {
53
+ yield fragment
54
+ } else {
55
+ yield* fragment
56
+ }
57
+ }
56
58
  }
57
59
 
58
60
  static dangerouslyCreate(fragments: Iterable<Html | string>): Html {
@@ -5,16 +5,27 @@ import createHttpError from 'http-errors'
5
5
  export type JsonScalar = string | number | boolean | null
6
6
  export type Json = JsonScalar | Json[] | { [_ in string]?: Json }
7
7
 
8
- export const parseContentType = (type: string): ContentType => {
8
+ /**
9
+ * Parse a content-type string into its components.
10
+ *
11
+ * @throws {TypeError} If the content-type is invalid.
12
+ */
13
+ export function parseContentType(type: unknown): ContentType {
14
+ if (typeof type !== 'string') {
15
+ throw createHttpError(
16
+ 415,
17
+ `Invalid content-type: ${type == null ? String(type) : typeof type}`,
18
+ )
19
+ }
20
+
9
21
  try {
10
22
  return hapiContentType(type)
11
23
  } catch (err) {
12
24
  // De-boomify the error
13
- if (err?.['isBoom']) {
14
- throw createHttpError(err['output']['statusCode'], err['message'])
15
- }
16
-
17
- throw err
25
+ throw createHttpError(
26
+ 415,
27
+ err instanceof Error ? err.message : 'Invalid content-type',
28
+ )
18
29
  }
19
30
  }
20
31
 
@@ -61,13 +72,15 @@ export const parsers = [
61
72
  test: (mime): mime is 'application/x-www-form-urlencoded' => {
62
73
  return mime === 'application/x-www-form-urlencoded'
63
74
  },
64
- parse: (buffer, { charset }): Partial<Record<string, string>> => {
75
+ parse: (buffer, { charset }): { [_ in string]?: string } => {
65
76
  if (charset != null && !/^utf-?8$/i.test(charset)) {
66
77
  throw createHttpError(415, 'Unsupported charset')
67
78
  }
68
79
  try {
69
80
  if (!buffer.length) return {}
70
- return Object.fromEntries(new URLSearchParams(buffer.toString()))
81
+ const params = new URLSearchParams(buffer.toString())
82
+ if (params.has('__proto__')) throw new TypeError('Invalid key')
83
+ return Object.fromEntries(params)
71
84
  } catch (err) {
72
85
  throw createHttpError(400, 'Invalid URL-encoded data', { cause: err })
73
86
  }
@@ -1,32 +1,10 @@
1
1
  import { parse as parseCookie, serialize as serializeCookie } from 'cookie'
2
2
  import { randomBytes } from 'crypto'
3
3
  import createHttpError from 'http-errors'
4
- import { z } from 'zod'
5
4
 
6
- import { KnownNames } from './parser.js'
7
5
  import { appendHeader } from './response.js'
8
- import { decodeStream, parseStream } from './stream.js'
9
6
  import { IncomingMessage, ServerResponse } from './types.js'
10
- import { UrlReference, urlMatch } from './url.js'
11
-
12
- export function parseRequestPayload<
13
- A extends readonly KnownNames[] = readonly KnownNames[],
14
- >(req: IncomingMessage, allow?: A) {
15
- return parseStream(
16
- decodeStream(req, req.headers['content-encoding']),
17
- req.headers['content-type'],
18
- allow,
19
- )
20
- }
21
-
22
- export async function validateRequestPayload<S extends z.ZodTypeAny>(
23
- req: IncomingMessage,
24
- schema: S,
25
- allow: readonly KnownNames[] = ['json', 'urlencoded'],
26
- ): Promise<z.infer<S>> {
27
- const payload = await parseRequestPayload(req, allow)
28
- return schema.parseAsync(payload, { path: ['body'] })
29
- }
7
+ import { urlMatch, UrlReference } from './url.js'
30
8
 
31
9
  export function validateHeaderValue(
32
10
  req: IncomingMessage,
@@ -1,81 +1,50 @@
1
- import { PassThrough, Readable } from 'node:stream'
2
- import { createGunzip, createInflate } from 'node:zlib'
3
-
1
+ import { decodeStream, streamToNodeBuffer } from '@atproto/common'
4
2
  import createHttpError from 'http-errors'
3
+ import { IncomingMessage } from 'node:http'
4
+ import { Readable } from 'node:stream'
5
5
 
6
6
  import {
7
7
  KnownNames,
8
8
  KnownParser,
9
- KnownTypes,
10
9
  parseContentType,
11
- ParserForType,
12
10
  ParserResult,
13
11
  parsers,
14
12
  } from './parser.js'
15
13
 
16
- export async function readStream(req: Readable): Promise<Buffer> {
17
- const chunks: Buffer[] = []
18
- let totalLength = 0
19
- for await (const chunk of req) {
20
- chunks.push(chunk)
21
- totalLength += chunk.length
22
- }
23
- return Buffer.concat(chunks, totalLength)
24
- }
25
-
26
- export function decodeStream(
27
- req: Readable,
28
- encoding: string = 'identity',
29
- ): Readable {
30
- switch (encoding) {
31
- case 'deflate':
32
- return req.compose(createInflate())
33
- case 'gzip':
34
- return req.compose(createGunzip())
35
- case 'identity':
36
- return req.compose(new PassThrough())
37
- default:
38
- throw createHttpError(415, 'Unsupported content-encoding')
14
+ export function decodeHttpRequest(req: IncomingMessage): Readable {
15
+ try {
16
+ return decodeStream(req, req.headers['content-encoding'])
17
+ } catch (err) {
18
+ throw createHttpError(415, err, { expose: err instanceof TypeError })
39
19
  }
40
20
  }
41
21
 
42
- export async function parseStream<
43
- T extends KnownTypes,
44
- A extends readonly KnownNames[] = readonly KnownNames[],
45
- >(
46
- req: Readable,
47
- contentType: T,
48
- allow?: A,
49
- ): Promise<
50
- ParserResult<ParserForType<Extract<KnownParser, { name: A[number] }>, T>>
51
- >
52
- export async function parseStream<
53
- A extends readonly KnownNames[] = readonly KnownNames[],
54
- >(
55
- req: Readable,
56
- contentType: unknown,
57
- allow?: A,
58
- ): Promise<ParserResult<Extract<KnownParser, { name: A[number] }>>>
59
- export async function parseStream(
60
- req: Readable,
61
- contentType: unknown = 'application/octet-stream',
62
- allow?: string[],
63
- ): Promise<ParserResult<KnownParser>> {
64
- if (typeof contentType !== 'string') {
65
- throw createHttpError(400, 'Invalid content-type')
66
- }
67
-
68
- const type = parseContentType(contentType)
22
+ /**
23
+ * Generic method that parses a stream of unknown nature (HTTP request/response,
24
+ * socket, file, etc.), but of known mime type, into a parsed object.
25
+ *
26
+ * @throws {TypeError} If the content-type is not valid or supported.
27
+ */
28
+
29
+ export async function parseHttpRequest<A extends readonly KnownNames[]>(
30
+ req: IncomingMessage,
31
+ allow: A,
32
+ ) {
33
+ const type = parseContentType(
34
+ req.headers['content-type'] ?? 'application/octet-stream',
35
+ )
69
36
 
70
37
  const parser = parsers.find(
71
- (parser) =>
72
- allow?.includes(parser.name) !== false && parser.test(type.mime),
38
+ (parser) => allow.includes(parser.name) && parser.test(type.mime),
73
39
  )
74
40
 
75
41
  if (!parser) {
76
- throw createHttpError(400, 'Unsupported content-type')
42
+ throw createHttpError(415, `Unsupported content-type: ${type.mime}`)
77
43
  }
78
44
 
79
- const buffer = await readStream(req)
80
- return parser.parse(buffer, type)
45
+ const stream = decodeHttpRequest(req)
46
+ const buffer = await streamToNodeBuffer(stream)
47
+ return parser.parse(buffer, type) as ParserResult<
48
+ Extract<KnownParser, { name: A[number] }>
49
+ >
81
50
  }
@@ -1,4 +1,7 @@
1
- import { accessTokenSchema, oauthTokenTypeSchema } from '@atproto/oauth-types'
1
+ import {
2
+ oauthAccessTokenSchema,
3
+ oauthTokenTypeSchema,
4
+ } from '@atproto/oauth-types'
2
5
  import { z } from 'zod'
3
6
 
4
7
  import { InvalidRequestError } from '../../errors/invalid-request-error.js'
@@ -6,7 +9,7 @@ import { WWWAuthenticateError } from '../../errors/www-authenticate-error.js'
6
9
 
7
10
  export const authorizationHeaderSchema = z.tuple([
8
11
  oauthTokenTypeSchema,
9
- accessTokenSchema,
12
+ oauthAccessTokenSchema,
10
13
  ])
11
14
 
12
15
  export const parseAuthorizationHeader = (header?: string) => {
@@ -1,15 +1,19 @@
1
1
  import { parse, ParsedDomain } from 'psl'
2
2
 
3
+ export function isInternetUrl(url: URL): boolean {
4
+ return parseUrlPublicSuffix(url) !== null
5
+ }
6
+
3
7
  export function isInternetHost(host: string): boolean {
4
- return parseDomain(host) !== null
8
+ return parseDomainPublicSuffix(host) !== null
5
9
  }
6
10
 
7
- export function parseUrlDomain(input: string | URL): ParsedDomain | null {
8
- const url = new URL(input)
9
- return parseDomain(url.hostname)
11
+ export function parseUrlPublicSuffix(input: string | URL): ParsedDomain | null {
12
+ const { hostname } = new URL(input)
13
+ return parseDomainPublicSuffix(hostname)
10
14
  }
11
15
 
12
- export function parseDomain(domain: string) {
16
+ export function parseDomainPublicSuffix(domain: string): ParsedDomain | null {
13
17
  const parsed = parse(domain)
14
18
  if ('listed' in parsed && parsed.listed && parsed.domain) {
15
19
  return parsed
@@ -60,7 +60,9 @@ export function buildMetadata(
60
60
  code_challenge_methods_supported: [
61
61
  // https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#pkce-code-challenge-method
62
62
  'S256',
63
- 'plain',
63
+
64
+ // atproto does not allow "plain"
65
+ // 'plain',
64
66
  ],
65
67
  ui_locales_supported: [
66
68
  //
@@ -14,6 +14,7 @@ export { InvalidGrantError } from './errors/invalid-grant-error.js'
14
14
  export { InvalidParametersError } from './errors/invalid-parameters-error.js'
15
15
  export { InvalidRedirectUriError } from './errors/invalid-redirect-uri-error.js'
16
16
  export { InvalidRequestError } from './errors/invalid-request-error.js'
17
+ export { InvalidScopeError } from './errors/invalid-scope-error.js'
17
18
  export { InvalidTokenError } from './errors/invalid-token-error.js'
18
19
  export { LoginRequiredError } from './errors/login-required-error.js'
19
20
  export { SecondAuthenticationFactorRequiredError } from './errors/second-authentication-factor-required-error.js'
@@ -1,7 +1,7 @@
1
1
  import { Jwks } from '@atproto/jwk'
2
2
  import {
3
- OAuthAuthenticationRequestParameters,
4
3
  OAuthAuthorizationDetails,
4
+ OAuthAuthorizationRequestParameters,
5
5
  OAuthClientMetadata,
6
6
  OAuthTokenResponse,
7
7
  } from '@atproto/oauth-types'
@@ -23,8 +23,8 @@ export type {
23
23
  ClientInfo,
24
24
  InvalidAuthorizationDetailsError,
25
25
  Jwks,
26
- OAuthAuthenticationRequestParameters,
27
26
  OAuthAuthorizationDetails,
27
+ OAuthAuthorizationRequestParameters,
28
28
  OAuthClientMetadata,
29
29
  OAuthTokenResponse,
30
30
  }
@@ -50,7 +50,7 @@ export type OAuthHooks = {
50
50
  */
51
51
  onAuthorizationDetails?: (data: {
52
52
  client: Client
53
- parameters: OAuthAuthenticationRequestParameters
53
+ parameters: OAuthAuthorizationRequestParameters
54
54
  account: Account
55
55
  }) => Awaitable<undefined | OAuthAuthorizationDetails>
56
56
  }