@atproto/oauth-provider 0.9.2 → 0.9.3

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 (121) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/client/client.js +6 -6
  3. package/dist/client/client.js.map +1 -1
  4. package/dist/device/device-manager.js +1 -1
  5. package/dist/device/device-manager.js.map +1 -1
  6. package/dist/dpop/dpop-manager.js +15 -15
  7. package/dist/dpop/dpop-manager.js.map +1 -1
  8. package/dist/errors/access-denied-error.d.ts +4 -7
  9. package/dist/errors/access-denied-error.d.ts.map +1 -1
  10. package/dist/errors/access-denied-error.js +4 -13
  11. package/dist/errors/access-denied-error.js.map +1 -1
  12. package/dist/errors/account-selection-required-error.d.ts +2 -2
  13. package/dist/errors/account-selection-required-error.d.ts.map +1 -1
  14. package/dist/errors/account-selection-required-error.js +2 -2
  15. package/dist/errors/account-selection-required-error.js.map +1 -1
  16. package/dist/errors/authorization-error.d.ts +10 -0
  17. package/dist/errors/authorization-error.d.ts.map +1 -0
  18. package/dist/errors/authorization-error.js +31 -0
  19. package/dist/errors/authorization-error.js.map +1 -0
  20. package/dist/errors/consent-required-error.d.ts +2 -2
  21. package/dist/errors/consent-required-error.d.ts.map +1 -1
  22. package/dist/errors/consent-required-error.js +2 -2
  23. package/dist/errors/consent-required-error.js.map +1 -1
  24. package/dist/errors/error-parser.d.ts.map +1 -1
  25. package/dist/errors/error-parser.js +2 -1
  26. package/dist/errors/error-parser.js.map +1 -1
  27. package/dist/errors/invalid-authorization-details-error.d.ts +2 -2
  28. package/dist/errors/invalid-authorization-details-error.d.ts.map +1 -1
  29. package/dist/errors/invalid-authorization-details-error.js +2 -2
  30. package/dist/errors/invalid-authorization-details-error.js.map +1 -1
  31. package/dist/errors/invalid-scope-error.d.ts +2 -2
  32. package/dist/errors/invalid-scope-error.d.ts.map +1 -1
  33. package/dist/errors/invalid-scope-error.js +2 -2
  34. package/dist/errors/invalid-scope-error.js.map +1 -1
  35. package/dist/errors/login-required-error.d.ts +2 -3
  36. package/dist/errors/login-required-error.d.ts.map +1 -1
  37. package/dist/errors/login-required-error.js +2 -7
  38. package/dist/errors/login-required-error.js.map +1 -1
  39. package/dist/lib/http/response.d.ts +4 -4
  40. package/dist/lib/http/response.d.ts.map +1 -1
  41. package/dist/lib/http/response.js +8 -7
  42. package/dist/lib/http/response.js.map +1 -1
  43. package/dist/lib/http/stream.d.ts +1 -0
  44. package/dist/lib/http/stream.d.ts.map +1 -1
  45. package/dist/lib/http/stream.js +6 -0
  46. package/dist/lib/http/stream.js.map +1 -1
  47. package/dist/lib/util/error.d.ts +2 -0
  48. package/dist/lib/util/error.d.ts.map +1 -0
  49. package/dist/lib/util/error.js +11 -0
  50. package/dist/lib/util/error.js.map +1 -0
  51. package/dist/lib/util/zod-error.d.ts +3 -1
  52. package/dist/lib/util/zod-error.d.ts.map +1 -1
  53. package/dist/lib/util/zod-error.js +20 -10
  54. package/dist/lib/util/zod-error.js.map +1 -1
  55. package/dist/oauth-errors.d.ts +1 -1
  56. package/dist/oauth-errors.d.ts.map +1 -1
  57. package/dist/oauth-errors.js +1 -1
  58. package/dist/oauth-errors.js.map +1 -1
  59. package/dist/oauth-hooks.d.ts +3 -2
  60. package/dist/oauth-hooks.d.ts.map +1 -1
  61. package/dist/oauth-hooks.js +4 -3
  62. package/dist/oauth-hooks.js.map +1 -1
  63. package/dist/oauth-provider.d.ts.map +1 -1
  64. package/dist/oauth-provider.js +18 -21
  65. package/dist/oauth-provider.js.map +1 -1
  66. package/dist/request/request-manager.d.ts.map +1 -1
  67. package/dist/request/request-manager.js +12 -12
  68. package/dist/request/request-manager.js.map +1 -1
  69. package/dist/router/create-api-middleware.d.ts.map +1 -1
  70. package/dist/router/create-api-middleware.js +60 -45
  71. package/dist/router/create-api-middleware.js.map +1 -1
  72. package/dist/router/create-authorization-page-middleware.d.ts.map +1 -1
  73. package/dist/router/create-authorization-page-middleware.js +19 -17
  74. package/dist/router/create-authorization-page-middleware.js.map +1 -1
  75. package/dist/router/create-oauth-middleware.d.ts.map +1 -1
  76. package/dist/router/create-oauth-middleware.js +21 -18
  77. package/dist/router/create-oauth-middleware.js.map +1 -1
  78. package/dist/router/send-redirect.js +2 -2
  79. package/dist/router/send-redirect.js.map +1 -1
  80. package/dist/token/token-manager.js +1 -1
  81. package/dist/types/authorization-response-error.d.ts +5 -0
  82. package/dist/types/authorization-response-error.d.ts.map +1 -0
  83. package/dist/types/authorization-response-error.js +21 -0
  84. package/dist/types/authorization-response-error.js.map +1 -0
  85. package/dist/types/par-response-error.d.ts +5 -0
  86. package/dist/types/par-response-error.d.ts.map +1 -0
  87. package/dist/types/par-response-error.js +22 -0
  88. package/dist/types/par-response-error.js.map +1 -0
  89. package/package.json +5 -5
  90. package/src/client/client.ts +6 -6
  91. package/src/device/device-manager.ts +1 -1
  92. package/src/dpop/dpop-manager.ts +16 -16
  93. package/src/errors/access-denied-error.ts +6 -33
  94. package/src/errors/account-selection-required-error.ts +2 -2
  95. package/src/errors/authorization-error.ts +45 -0
  96. package/src/errors/consent-required-error.ts +2 -2
  97. package/src/errors/error-parser.ts +2 -1
  98. package/src/errors/invalid-authorization-details-error.ts +2 -2
  99. package/src/errors/invalid-scope-error.ts +2 -2
  100. package/src/errors/login-required-error.ts +2 -12
  101. package/src/lib/http/response.ts +14 -13
  102. package/src/lib/http/stream.ts +6 -0
  103. package/src/lib/util/error.ts +7 -0
  104. package/src/lib/util/zod-error.ts +23 -11
  105. package/src/oauth-errors.ts +1 -1
  106. package/src/oauth-hooks.ts +3 -2
  107. package/src/oauth-provider.ts +18 -28
  108. package/src/request/request-manager.ts +12 -18
  109. package/src/router/create-api-middleware.ts +84 -62
  110. package/src/router/create-authorization-page-middleware.ts +19 -21
  111. package/src/router/create-oauth-middleware.ts +28 -27
  112. package/src/router/send-redirect.ts +2 -2
  113. package/src/token/token-manager.ts +1 -1
  114. package/src/types/authorization-response-error.ts +27 -0
  115. package/src/types/par-response-error.ts +25 -0
  116. package/tsconfig.build.tsbuildinfo +1 -1
  117. package/dist/errors/invalid-parameters-error.d.ts +0 -6
  118. package/dist/errors/invalid-parameters-error.d.ts.map +0 -1
  119. package/dist/errors/invalid-parameters-error.js +0 -11
  120. package/dist/errors/invalid-parameters-error.js.map +0 -1
  121. package/src/errors/invalid-parameters-error.ts +0 -12
@@ -0,0 +1,45 @@
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
+ import {
3
+ AuthorizationResponseError,
4
+ isAuthorizationResponseError,
5
+ } from '../types/authorization-response-error.js'
6
+ import { buildErrorPayload } from './error-parser.js'
7
+ import { OAuthError } from './oauth-error.js'
8
+
9
+ export type { AuthorizationResponseError, OAuthAuthorizationRequestParameters }
10
+
11
+ export class AuthorizationError extends OAuthError {
12
+ constructor(
13
+ public readonly parameters: OAuthAuthorizationRequestParameters,
14
+ error_description: string,
15
+ error: AuthorizationResponseError = 'invalid_request',
16
+ cause?: unknown,
17
+ ) {
18
+ super(error, error_description, 400, cause)
19
+ }
20
+
21
+ static from(
22
+ parameters: OAuthAuthorizationRequestParameters,
23
+ cause: unknown,
24
+ ): AuthorizationError {
25
+ if (cause instanceof AuthorizationError) return cause
26
+ const payload = buildErrorPayload(cause)
27
+ return new AuthorizationError(
28
+ parameters,
29
+ payload.error_description,
30
+ isAuthorizationResponseError(payload.error)
31
+ ? payload.error // Propagate "error" derived from the cause
32
+ : rootCause(cause) instanceof OAuthError
33
+ ? 'invalid_request'
34
+ : 'server_error',
35
+ cause,
36
+ )
37
+ }
38
+ }
39
+
40
+ function rootCause(err: unknown): unknown {
41
+ while (err instanceof Error && err.cause != null) {
42
+ err = err.cause
43
+ }
44
+ return err
45
+ }
@@ -1,7 +1,7 @@
1
1
  import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
- import { AccessDeniedError } from './access-denied-error.js'
2
+ import { AuthorizationError } from './authorization-error.js'
3
3
 
4
- export class ConsentRequiredError extends AccessDeniedError {
4
+ export class ConsentRequiredError extends AuthorizationError {
5
5
  constructor(
6
6
  parameters: OAuthAuthorizationRequestParameters,
7
7
  error_description = 'User consent required',
@@ -1,6 +1,7 @@
1
1
  import { errors } from 'jose'
2
2
  import { ZodError } from 'zod'
3
3
  import { JwtVerifyError } from '@atproto/jwk'
4
+ import { formatZodError } from '../lib/util/zod-error.js'
4
5
  import { OAuthError } from './oauth-error.js'
5
6
 
6
7
  const { JOSEError } = errors
@@ -63,7 +64,7 @@ export function buildErrorPayload(error: unknown): ErrorPayload {
63
64
  if (error instanceof ZodError) {
64
65
  return {
65
66
  error: INVALID_REQUEST,
66
- error_description: error.issues[0]?.message || 'Invalid request',
67
+ error_description: formatZodError(error, 'Validation error'),
67
68
  }
68
69
  }
69
70
 
@@ -1,5 +1,5 @@
1
1
  import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
- import { AccessDeniedError } from './access-denied-error.js'
2
+ import { AuthorizationError } from './authorization-error.js'
3
3
 
4
4
  /**
5
5
  * @see
@@ -16,7 +16,7 @@ import { AccessDeniedError } from './access-denied-error.js'
16
16
  * - contains fields with invalid values for the authorization details type, or
17
17
  * - is missing required fields for the authorization details type.
18
18
  */
19
- export class InvalidAuthorizationDetailsError extends AccessDeniedError {
19
+ export class InvalidAuthorizationDetailsError extends AuthorizationError {
20
20
  constructor(
21
21
  parameters: OAuthAuthorizationRequestParameters,
22
22
  error_description: string,
@@ -1,10 +1,10 @@
1
1
  import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
- import { AccessDeniedError } from './access-denied-error.js'
2
+ import { AuthorizationError } from './authorization-error.js'
3
3
 
4
4
  /**
5
5
  * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.2.1}
6
6
  */
7
- export class InvalidScopeError extends AccessDeniedError {
7
+ export class InvalidScopeError extends AuthorizationError {
8
8
  constructor(
9
9
  parameters: OAuthAuthorizationRequestParameters,
10
10
  error_description: string,
@@ -1,7 +1,7 @@
1
1
  import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
- import { AccessDeniedError } from './access-denied-error.js'
2
+ import { AuthorizationError } from './authorization-error.js'
3
3
 
4
- export class LoginRequiredError extends AccessDeniedError {
4
+ export class LoginRequiredError extends AuthorizationError {
5
5
  constructor(
6
6
  parameters: OAuthAuthorizationRequestParameters,
7
7
  error_description = 'Login is required',
@@ -9,14 +9,4 @@ export class LoginRequiredError extends AccessDeniedError {
9
9
  ) {
10
10
  super(parameters, error_description, 'login_required', cause)
11
11
  }
12
-
13
- static from(
14
- parameters: OAuthAuthorizationRequestParameters,
15
- cause?: unknown,
16
- fallbackError?: string,
17
- ): LoginRequiredError {
18
- if (cause instanceof LoginRequiredError) return cause
19
-
20
- return new LoginRequiredError(parameters, fallbackError, cause)
21
- }
22
12
  }
@@ -101,16 +101,16 @@ export function cacheControlMiddleware(maxAge: number): Middleware<void> {
101
101
  }
102
102
  }
103
103
 
104
+ export type JsonResponse<P = unknown> = WriteResponseOptions & {
105
+ json: P
106
+ }
107
+
104
108
  export function jsonHandler<
105
109
  T,
106
110
  Req extends IncomingMessage = IncomingMessage,
107
111
  Res extends ServerResponse = ServerResponse,
108
112
  >(
109
- buildJson: (
110
- this: T,
111
- req: Req,
112
- res: Res,
113
- ) => Awaitable<{ payload: unknown; status?: number }>,
113
+ buildJson: (this: T, req: Req, res: Res) => Awaitable<JsonResponse>,
114
114
  ): Middleware<T, Req, Res> {
115
115
  return function (req, res, next) {
116
116
  // Ensure we can agree on a content encoding & type before starting to
@@ -120,14 +120,11 @@ export function jsonHandler<
120
120
  // promise and return it.
121
121
  void (async () => {
122
122
  try {
123
- const { payload, status = 200 } = await buildJson.call(this, req, res)
124
- writeJson(res, payload, { status })
125
- } catch (cause) {
126
- const error =
127
- cause instanceof Error
128
- ? cause
129
- : new Error('Failed to build JSON response', { cause })
130
- next(error satisfies Error)
123
+ const jsonResponse = await buildJson.call(this, req, res)
124
+ const { json, status = 200, ...options } = jsonResponse
125
+ writeJson(res, json, { ...options, status })
126
+ } catch (err) {
127
+ next(asError(err, 'Failed to build JSON response'))
131
128
  }
132
129
  })()
133
130
  } else {
@@ -135,3 +132,7 @@ export function jsonHandler<
135
132
  }
136
133
  }
137
134
  }
135
+
136
+ function asError(cause: unknown, message: string): Error {
137
+ return cause instanceof Error ? cause : new Error(message, { cause })
138
+ }
@@ -49,3 +49,9 @@ export async function parseHttpRequest<A extends readonly KnownNames[]>(
49
49
  Extract<KnownParser, { name: A[number] }>
50
50
  >
51
51
  }
52
+
53
+ export async function flushStream(stream: AsyncIterable<any>): Promise<void> {
54
+ for await (const _ of stream) {
55
+ // Consume the stream to completion
56
+ }
57
+ }
@@ -0,0 +1,7 @@
1
+ import { ZodError } from 'zod'
2
+ import { formatZodError } from './zod-error.js'
3
+
4
+ export function formatError(err: unknown, prefix: string): string {
5
+ if (err instanceof ZodError) return formatZodError(err, prefix)
6
+ return prefix
7
+ }
@@ -1,14 +1,26 @@
1
- import { ZodError } from 'zod'
2
-
3
- export function extractZodErrorMessage(err: unknown): string | undefined {
4
- if (err instanceof ZodError) {
5
- const issue = err.issues[0]
6
- if (issue?.path.length) {
7
- // "part" will typically be "body" or "query"
8
- const [part, ...path] = issue.path
9
- return `Validation of "${path.join('.')}" ${part} parameter failed: ${issue.message}`
10
- }
1
+ import { ZodError, ZodIssue, ZodIssueCode } from 'zod'
2
+
3
+ export function formatZodError(err: ZodError, prefix?: string): string {
4
+ const message = err.issues.length
5
+ ? err.issues.map(formatZodIssue).join('; ')
6
+ : err.message // Should never happen (issues should never be empty)
7
+ return prefix ? `${prefix}: ${message}` : message
8
+ }
9
+
10
+ export function formatZodIssue(issue: ZodIssue): string {
11
+ if (issue.code === ZodIssueCode.invalid_union) {
12
+ return issue.unionErrors
13
+ .map((err) => err.issues.map(formatZodIssue).join('; '))
14
+ .join(', or ')
15
+ }
16
+
17
+ if (issue.path.length === 1 && typeof issue.path[0] === 'number') {
18
+ return `${issue.message} at index ${issue.path[0]}`
19
+ }
20
+
21
+ if (issue.path.length) {
22
+ return `${issue.message} at ${issue.path.join('.')}`
11
23
  }
12
24
 
13
- return undefined
25
+ return issue.message
14
26
  }
@@ -3,6 +3,7 @@ export { OAuthError } from './errors/oauth-error.js'
3
3
 
4
4
  export * from './errors/access-denied-error.js'
5
5
  export * from './errors/account-selection-required-error.js'
6
+ export * from './errors/authorization-error.js'
6
7
  export * from './errors/consent-required-error.js'
7
8
  export * from './errors/handle-unavailable-error.js'
8
9
  export * from './errors/invalid-authorization-details-error.js'
@@ -13,7 +14,6 @@ export * from './errors/invalid-dpop-key-binding-error.js'
13
14
  export * from './errors/invalid-dpop-proof-error.js'
14
15
  export * from './errors/invalid-grant-error.js'
15
16
  export * from './errors/invalid-invite-code-error.js'
16
- export * from './errors/invalid-parameters-error.js'
17
17
  export * from './errors/invalid-redirect-uri-error.js'
18
18
  export * from './errors/invalid-request-error.js'
19
19
  export * from './errors/invalid-scope-error.js'
@@ -12,7 +12,9 @@ import { ClientAuth } from './client/client-auth.js'
12
12
  import { ClientId } from './client/client-id.js'
13
13
  import { ClientInfo } from './client/client-info.js'
14
14
  import { Client } from './client/client.js'
15
+ import { AccessDeniedError } from './errors/access-denied-error.js'
15
16
  import { InvalidRequestError } from './errors/invalid-request-error.js'
17
+ import { OAuthError } from './errors/oauth-error.js'
16
18
  import {
17
19
  HcaptchaClientTokens,
18
20
  HcaptchaConfig,
@@ -20,7 +22,6 @@ import {
20
22
  } from './lib/hcaptcha.js'
21
23
  import { RequestMetadata } from './lib/http/request.js'
22
24
  import { Awaitable } from './lib/util/type.js'
23
- import { AccessDeniedError, OAuthError } from './oauth-errors.js'
24
25
  import { DeviceId, SignUpData } from './oauth-store.js'
25
26
  import { RequestId } from './request/request-id.js'
26
27
 
@@ -118,7 +119,7 @@ export type OAuthHooks = {
118
119
  /**
119
120
  * This hook is called when a client is authorized.
120
121
  *
121
- * @throws {AccessDeniedError} to deny the authorization request and redirect
122
+ * @throws {AuthorizationError} to deny the authorization request and redirect
122
123
  * the user to the client with an OAuth error (other errors will result in an
123
124
  * internal server error being displayed to the user)
124
125
  *
@@ -1,6 +1,5 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import type { Redis, RedisOptions } from 'ioredis'
3
- import { ZodError } from 'zod'
4
3
  import { Jwks, Keyset } from '@atproto/jwk'
5
4
  import type { Account } from '@atproto/oauth-provider-api'
6
5
  import {
@@ -64,8 +63,8 @@ import {
64
63
  deviceManagerOptionsSchema,
65
64
  } from './device/device-manager.js'
66
65
  import { DeviceStore, asDeviceStore } from './device/device-store.js'
67
- import { AccessDeniedError } from './errors/access-denied-error.js'
68
66
  import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
67
+ import { AuthorizationError } from './errors/authorization-error.js'
69
68
  import { ConsentRequiredError } from './errors/consent-required-error.js'
70
69
  import { InvalidDpopKeyBindingError } from './errors/invalid-dpop-key-binding-error.js'
71
70
  import { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js'
@@ -75,8 +74,8 @@ import { LoginRequiredError } from './errors/login-required-error.js'
75
74
  import { HcaptchaConfig } from './lib/hcaptcha.js'
76
75
  import { RequestMetadata } from './lib/http/request.js'
77
76
  import { dateToRelativeSeconds } from './lib/util/date.js'
77
+ import { formatError } from './lib/util/error.js'
78
78
  import { LocalizedString, MultiLangString } from './lib/util/locale.js'
79
- import { extractZodErrorMessage } from './lib/util/zod-error.js'
80
79
  import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
81
80
  import { OAuthHooks } from './oauth-hooks.js'
82
81
  import {
@@ -104,6 +103,7 @@ import {
104
103
  VerifyTokenClaimsOptions,
105
104
  VerifyTokenClaimsResult,
106
105
  } from './token/verify-token-claims.js'
106
+ import { isPARResponseError } from './types/par-response-error.js'
107
107
 
108
108
  export { AccessTokenMode, Keyset }
109
109
  export type {
@@ -448,11 +448,8 @@ export class OAuthProvider extends OAuthVerifier {
448
448
  const parameters = await oauthAuthorizationRequestParametersSchema
449
449
  .parseAsync(payload)
450
450
  .catch((err) => {
451
- const message =
452
- err instanceof ZodError
453
- ? `Invalid request parameters: ${err.message}`
454
- : `Invalid "request" object`
455
- throw InvalidRequestError.from(err, message)
451
+ const msg = formatError(err, 'Invalid parameters in JAR')
452
+ throw new InvalidRequestError(msg, err)
456
453
  })
457
454
 
458
455
  return parameters
@@ -522,7 +519,7 @@ export class OAuthProvider extends OAuthVerifier {
522
519
  // > Since initial processing of the pushed authorization request does not
523
520
  // > involve resource owner interaction, error codes related to user
524
521
  // > interaction, such as "access_denied", are never returned.
525
- if (err instanceof AccessDeniedError) {
522
+ if (err instanceof AuthorizationError && !isPARResponseError(err.error)) {
526
523
  throw new InvalidRequestError(err.error_description, err)
527
524
  }
528
525
  throw err
@@ -539,10 +536,8 @@ export class OAuthProvider extends OAuthVerifier {
539
536
  const requestUri = await requestUriSchema
540
537
  .parseAsync(query.request_uri, { path: ['query', 'request_uri'] })
541
538
  .catch((err) => {
542
- throw new InvalidRequestError(
543
- extractZodErrorMessage(err) ?? 'Input validation error',
544
- err,
545
- )
539
+ const msg = formatError(err, 'Invalid "request_uri" query parameter')
540
+ throw new InvalidRequestError(msg, err)
546
541
  })
547
542
 
548
543
  return this.requestManager.get(requestUri, deviceId, client.id)
@@ -592,24 +587,24 @@ export class OAuthProvider extends OAuthVerifier {
592
587
  const { issuer } = this
593
588
 
594
589
  // If there is a chance to redirect the user to the client, let's do
595
- // it by wrapping the error in an AccessDeniedError.
596
- const accessDeniedCatcher =
590
+ // it by wrapping the error in an AuthorizationError.
591
+ const throwAuthorizationError =
597
592
  'redirect_uri' in query
598
593
  ? (err: unknown): never => {
599
594
  // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.2.1
600
- throw AccessDeniedError.from(query, err, 'invalid_request')
595
+ throw AuthorizationError.from(query, err)
601
596
  }
602
597
  : null
603
598
 
604
599
  const client = await this.clientManager
605
600
  .getClient(clientCredentials.client_id)
606
- .catch(accessDeniedCatcher)
601
+ .catch(throwAuthorizationError)
607
602
 
608
603
  const { parameters, uri } = await this.processAuthorizationRequest(
609
604
  client,
610
605
  deviceId,
611
606
  query,
612
- ).catch(accessDeniedCatcher)
607
+ ).catch(throwAuthorizationError)
613
608
 
614
609
  try {
615
610
  const sessions = await this.getSessions(client.id, deviceId, parameters)
@@ -694,9 +689,7 @@ export class OAuthProvider extends OAuthVerifier {
694
689
  // (allowing to log this error)
695
690
  }
696
691
 
697
- // Not using accessDeniedCatcher here because "parameters" will most
698
- // likely contain the redirect_uri (using the client default).
699
- throw AccessDeniedError.from(parameters, err, 'server_error')
692
+ throw AuthorizationError.from(parameters, err)
700
693
  }
701
694
  }
702
695
 
@@ -872,12 +865,8 @@ export class OAuthProvider extends OAuthVerifier {
872
865
  const code = await codeSchema
873
866
  .parseAsync(input.code, { path: ['code'] })
874
867
  .catch((err) => {
875
- throw InvalidGrantError.from(
876
- err,
877
- err instanceof ZodError
878
- ? `Invalid code: ${err.message}`
879
- : `Invalid code`,
880
- )
868
+ const msg = formatError(err, 'Invalid code')
869
+ throw new InvalidGrantError(msg, err)
881
870
  })
882
871
 
883
872
  const data = await this.requestManager
@@ -999,7 +988,8 @@ export class OAuthProvider extends OAuthVerifier {
999
988
  const refreshToken = await refreshTokenSchema
1000
989
  .parseAsync(input.refresh_token, { path: ['refresh_token'] })
1001
990
  .catch((err) => {
1002
- throw InvalidGrantError.from(err, `Invalid refresh token`)
991
+ const msg = formatError(err, 'Invalid refresh token')
992
+ throw new InvalidGrantError(msg, err)
1003
993
  })
1004
994
 
1005
995
  const tokenInfo = await this.tokenManager.consumeRefreshToken(refreshToken)
@@ -16,10 +16,10 @@ import {
16
16
  } from '../constants.js'
17
17
  import { DeviceId } from '../device/device-id.js'
18
18
  import { AccessDeniedError } from '../errors/access-denied-error.js'
19
+ import { AuthorizationError } from '../errors/authorization-error.js'
19
20
  import { ConsentRequiredError } from '../errors/consent-required-error.js'
20
21
  import { InvalidAuthorizationDetailsError } from '../errors/invalid-authorization-details-error.js'
21
22
  import { InvalidGrantError } from '../errors/invalid-grant-error.js'
22
- import { InvalidParametersError } from '../errors/invalid-parameters-error.js'
23
23
  import { InvalidRequestError } from '../errors/invalid-request-error.js'
24
24
  import { InvalidScopeError } from '../errors/invalid-scope-error.js'
25
25
  import { RequestMetadata } from '../lib/http/request.js'
@@ -93,10 +93,7 @@ export class RequestManager {
93
93
  'nonce', // note that OIDC "nonce" is redundant with PKCE
94
94
  ] as const) {
95
95
  if (parameters[k] !== undefined) {
96
- throw new InvalidParametersError(
97
- parameters,
98
- `Unsupported "${k}" parameter`,
99
- )
96
+ throw new AuthorizationError(parameters, `Unsupported "${k}" parameter`)
100
97
  }
101
98
  }
102
99
 
@@ -109,7 +106,7 @@ export class RequestManager {
109
106
  parameters.response_type,
110
107
  )
111
108
  ) {
112
- throw new AccessDeniedError(
109
+ throw new AuthorizationError(
113
110
  parameters,
114
111
  `Unsupported response_type "${parameters.response_type}"`,
115
112
  'unsupported_response_type',
@@ -120,7 +117,7 @@ export class RequestManager {
120
117
  parameters.response_type === 'code' &&
121
118
  !this.metadata.grant_types_supported?.includes('authorization_code')
122
119
  ) {
123
- throw new AccessDeniedError(
120
+ throw new AuthorizationError(
124
121
  parameters,
125
122
  `Unsupported grant_type "authorization_code"`,
126
123
  'invalid_request',
@@ -133,7 +130,7 @@ export class RequestManager {
133
130
  // defined in the server metadata. In the future, we might add support
134
131
  // for dynamic scopes.
135
132
  if (!this.metadata.scopes_supported?.includes(scope)) {
136
- throw new InvalidParametersError(
133
+ throw new AuthorizationError(
137
134
  parameters,
138
135
  `Scope "${scope}" is not supported by this server`,
139
136
  )
@@ -169,7 +166,7 @@ export class RequestManager {
169
166
  if (!parameters.redirect_uri) {
170
167
  // Should already be ensured by client.validateRequest(). Adding here for
171
168
  // clarity & extra safety.
172
- throw new InvalidParametersError(parameters, 'Missing "redirect_uri"')
169
+ throw new AuthorizationError(parameters, 'Missing "redirect_uri"')
173
170
  }
174
171
 
175
172
  // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10#section-1.4.1
@@ -195,7 +192,7 @@ export class RequestManager {
195
192
  case 'S256':
196
193
  break
197
194
  default: {
198
- throw new InvalidParametersError(
195
+ throw new AuthorizationError(
199
196
  parameters,
200
197
  `Unsupported code_challenge_method "${parameters.code_challenge_method}"`,
201
198
  )
@@ -204,7 +201,7 @@ export class RequestManager {
204
201
  } else {
205
202
  if (parameters.code_challenge_method) {
206
203
  // https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1
207
- throw new InvalidParametersError(
204
+ throw new AuthorizationError(
208
205
  parameters,
209
206
  'code_challenge is required when code_challenge_method is provided',
210
207
  )
@@ -225,7 +222,7 @@ export class RequestManager {
225
222
  // atproto does not implement the OpenID Connect nonce mechanism, so we
226
223
  // require the use of PKCE for all clients.
227
224
 
228
- throw new InvalidParametersError(parameters, 'Use of PKCE is required')
225
+ throw new AuthorizationError(parameters, 'Use of PKCE is required')
229
226
  }
230
227
 
231
228
  // -----------------
@@ -233,7 +230,7 @@ export class RequestManager {
233
230
  // -----------------
234
231
 
235
232
  if (parameters.response_type !== 'code') {
236
- throw new InvalidParametersError(
233
+ throw new AuthorizationError(
237
234
  parameters,
238
235
  'atproto only supports the "code" response_type',
239
236
  )
@@ -249,7 +246,7 @@ export class RequestManager {
249
246
  }
250
247
 
251
248
  if (parameters.code_challenge_method !== 'S256') {
252
- throw new InvalidParametersError(
249
+ throw new AuthorizationError(
253
250
  parameters,
254
251
  'atproto requires use of "S256" code_challenge_method',
255
252
  )
@@ -280,10 +277,7 @@ export class RequestManager {
280
277
  const hint = parameters.login_hint?.toLowerCase()
281
278
  if (hint) {
282
279
  if (!isAtprotoDid(hint) && !isValidHandle(hint)) {
283
- throw new InvalidParametersError(
284
- parameters,
285
- `Invalid login_hint "${hint}"`,
286
- )
280
+ throw new AuthorizationError(parameters, `Invalid login_hint "${hint}"`)
287
281
  }
288
282
 
289
283
  // @TODO: ensure that the account actually exists on this server (there is