@atproto/oauth-provider 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (171) hide show
  1. package/CHANGELOG.md +45 -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 +5 -4
  125. package/src/assets/app/components/accept-form.tsx +6 -2
  126. package/src/assets/app/components/client-name.tsx +10 -11
  127. package/src/assets/app/components/sign-in-form.tsx +31 -2
  128. package/src/assets/assets-middleware.ts +4 -2
  129. package/src/client/client-manager.ts +163 -161
  130. package/src/client/client-utils.ts +7 -12
  131. package/src/client/client.ts +112 -3
  132. package/src/constants.ts +0 -2
  133. package/src/errors/access-denied-error.ts +10 -4
  134. package/src/errors/account-selection-required-error.ts +2 -2
  135. package/src/errors/consent-required-error.ts +2 -2
  136. package/src/errors/invalid-authorization-details-error.ts +2 -2
  137. package/src/errors/invalid-client-id-error.ts +15 -4
  138. package/src/errors/invalid-client-metadata-error.ts +15 -3
  139. package/src/errors/invalid-parameters-error.ts +2 -2
  140. package/src/errors/invalid-scope-error.ts +15 -0
  141. package/src/errors/login-required-error.ts +2 -2
  142. package/src/lib/html/html.ts +14 -12
  143. package/src/lib/http/parser.ts +21 -8
  144. package/src/lib/http/request.ts +1 -23
  145. package/src/lib/http/stream.ts +29 -60
  146. package/src/lib/util/authorization-header.ts +5 -2
  147. package/src/lib/util/hostname.ts +9 -5
  148. package/src/metadata/build-metadata.ts +3 -1
  149. package/src/oauth-errors.ts +1 -0
  150. package/src/oauth-hooks.ts +3 -3
  151. package/src/oauth-provider.ts +368 -269
  152. package/src/oauth-verifier.ts +2 -2
  153. package/src/output/build-authorize-data.ts +2 -2
  154. package/src/output/send-authorize-redirect.ts +7 -6
  155. package/src/request/request-data.ts +2 -2
  156. package/src/request/request-info.ts +2 -2
  157. package/src/request/request-manager.ts +129 -103
  158. package/src/signer/signer.ts +24 -25
  159. package/src/token/token-data.ts +3 -3
  160. package/src/token/token-manager.ts +141 -99
  161. package/src/token/verify-token-claims.ts +3 -3
  162. package/dist/request/types.d.ts +0 -328
  163. package/dist/request/types.d.ts.map +0 -1
  164. package/dist/request/types.js +0 -27
  165. package/dist/request/types.js.map +0 -1
  166. package/dist/token/types.d.ts +0 -250
  167. package/dist/token/types.d.ts.map +0 -1
  168. package/dist/token/types.js +0 -36
  169. package/dist/token/types.js.map +0 -1
  170. package/src/request/types.ts +0 -48
  171. 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
  }