@atproto/oauth-provider 0.8.1 → 0.9.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 (77) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/client/client-auth.d.ts +48 -3
  3. package/dist/client/client-auth.d.ts.map +1 -1
  4. package/dist/client/client-auth.js +0 -31
  5. package/dist/client/client-auth.js.map +1 -1
  6. package/dist/client/client-manager.d.ts.map +1 -1
  7. package/dist/client/client-manager.js +19 -19
  8. package/dist/client/client-manager.js.map +1 -1
  9. package/dist/client/client.d.ts +14 -17
  10. package/dist/client/client.d.ts.map +1 -1
  11. package/dist/client/client.js +115 -73
  12. package/dist/client/client.js.map +1 -1
  13. package/dist/constants.d.ts +7 -6
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/constants.js +8 -7
  16. package/dist/constants.js.map +1 -1
  17. package/dist/metadata/build-metadata.js +1 -1
  18. package/dist/metadata/build-metadata.js.map +1 -1
  19. package/dist/oauth-provider.d.ts +20 -16
  20. package/dist/oauth-provider.d.ts.map +1 -1
  21. package/dist/oauth-provider.js +268 -122
  22. package/dist/oauth-provider.js.map +1 -1
  23. package/dist/replay/replay-manager.d.ts +1 -1
  24. package/dist/replay/replay-manager.d.ts.map +1 -1
  25. package/dist/replay/replay-manager.js +5 -2
  26. package/dist/replay/replay-manager.js.map +1 -1
  27. package/dist/request/request-data.d.ts +3 -2
  28. package/dist/request/request-data.d.ts.map +1 -1
  29. package/dist/request/request-data.js.map +1 -1
  30. package/dist/request/request-info.d.ts +1 -1
  31. package/dist/request/request-info.d.ts.map +1 -1
  32. package/dist/request/request-manager.d.ts +73 -9
  33. package/dist/request/request-manager.d.ts.map +1 -1
  34. package/dist/request/request-manager.js +20 -61
  35. package/dist/request/request-manager.js.map +1 -1
  36. package/dist/request/request-store.d.ts +6 -2
  37. package/dist/request/request-store.d.ts.map +1 -1
  38. package/dist/request/request-store.js +6 -6
  39. package/dist/request/request-store.js.map +1 -1
  40. package/dist/router/create-api-middleware.js +1 -1
  41. package/dist/router/create-api-middleware.js.map +1 -1
  42. package/dist/router/create-oauth-middleware.d.ts.map +1 -1
  43. package/dist/router/create-oauth-middleware.js +2 -1
  44. package/dist/router/create-oauth-middleware.js.map +1 -1
  45. package/dist/token/token-data.d.ts +2 -2
  46. package/dist/token/token-data.d.ts.map +1 -1
  47. package/dist/token/token-manager.d.ts +10 -10
  48. package/dist/token/token-manager.d.ts.map +1 -1
  49. package/dist/token/token-manager.js +64 -201
  50. package/dist/token/token-manager.js.map +1 -1
  51. package/package.json +7 -7
  52. package/src/client/client-auth.ts +52 -33
  53. package/src/client/client-manager.ts +26 -27
  54. package/src/client/client.ts +153 -89
  55. package/src/constants.ts +9 -7
  56. package/src/metadata/build-metadata.ts +2 -2
  57. package/src/oauth-provider.ts +391 -191
  58. package/src/replay/replay-manager.ts +10 -6
  59. package/src/request/request-data.ts +12 -2
  60. package/src/request/request-info.ts +1 -1
  61. package/src/request/request-manager.ts +25 -85
  62. package/src/request/request-store.ts +11 -8
  63. package/src/router/create-api-middleware.ts +1 -1
  64. package/src/router/create-oauth-middleware.ts +7 -1
  65. package/src/token/token-data.ts +2 -2
  66. package/src/token/token-manager.ts +112 -312
  67. package/tsconfig.build.tsbuildinfo +1 -1
  68. package/dist/request/request-store-memory.d.ts +0 -16
  69. package/dist/request/request-store-memory.d.ts.map +0 -1
  70. package/dist/request/request-store-memory.js +0 -31
  71. package/dist/request/request-store-memory.js.map +0 -1
  72. package/dist/request/request-store-redis.d.ts +0 -24
  73. package/dist/request/request-store-redis.d.ts.map +0 -1
  74. package/dist/request/request-store-redis.js +0 -58
  75. package/dist/request/request-store-redis.js.map +0 -1
  76. package/src/request/request-store-memory.ts +0 -39
  77. package/src/request/request-store-redis.ts +0 -71
@@ -311,13 +311,7 @@ export class ClientManager {
311
311
  )
312
312
  }
313
313
 
314
- const method = metadata[`token_endpoint_auth_method`]
315
- switch (method) {
316
- case undefined:
317
- throw new InvalidClientMetadataError(
318
- 'Missing token_endpoint_auth_method client metadata',
319
- )
320
-
314
+ switch (metadata.token_endpoint_auth_method) {
321
315
  case 'none':
322
316
  if (metadata.token_endpoint_auth_signing_alg) {
323
317
  throw new InvalidClientMetadataError(
@@ -346,7 +340,7 @@ export class ClientManager {
346
340
 
347
341
  default:
348
342
  throw new InvalidClientMetadataError(
349
- `${method} is not a supported "token_endpoint_auth_method". Use "private_key_jwt" or "none".`,
343
+ `Unsupported client authentication method "${metadata.token_endpoint_auth_method}". Make sure "token_endpoint_auth_method" is set to one of: "${Client.AUTH_METHODS_SUPPORTED.join('", "')}"`,
350
344
  )
351
345
  }
352
346
 
@@ -421,6 +415,29 @@ export class ClientManager {
421
415
  )
422
416
  }
423
417
 
418
+ if (
419
+ metadata.application_type === 'native' &&
420
+ metadata.token_endpoint_auth_method !== 'none'
421
+ ) {
422
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
423
+ //
424
+ // > Except when using a mechanism like Dynamic Client Registration
425
+ // > [RFC7591] to provision per-instance secrets, native apps are
426
+ // > classified as public clients, as defined by Section 2.1 of OAuth 2.0
427
+ // > [RFC6749]; they MUST be registered with the authorization server as
428
+ // > such. Authorization servers MUST record the client type in the client
429
+ // > registration details in order to identify and process requests
430
+ // > accordingly.
431
+
432
+ // @NOTE We may want to remove this restriction in the future, for example
433
+ // if https://github.com/bluesky-social/proposals/tree/main/0010-client-assertion-backend
434
+ // gets adopted
435
+
436
+ throw new InvalidClientMetadataError(
437
+ 'Native clients must authenticate using "none" method',
438
+ )
439
+ }
440
+
424
441
  if (
425
442
  metadata.application_type === 'web' &&
426
443
  metadata.grant_types.includes('implicit')
@@ -670,7 +687,7 @@ export class ClientManager {
670
687
  )
671
688
  }
672
689
 
673
- const method = metadata[`token_endpoint_auth_method`]
690
+ const method = metadata.token_endpoint_auth_method
674
691
  if (method !== 'none') {
675
692
  throw new InvalidClientMetadataError(
676
693
  `Loopback clients are not allowed to use "token_endpoint_auth_method" ${method}`,
@@ -738,24 +755,6 @@ export class ClientManager {
738
755
  }
739
756
  }
740
757
 
741
- const method = metadata[`token_endpoint_auth_method`]
742
- switch (method) {
743
- case 'none':
744
- case 'private_key_jwt':
745
- case undefined:
746
- break
747
- case 'client_secret_post':
748
- case 'client_secret_basic':
749
- case 'client_secret_jwt':
750
- throw new InvalidClientMetadataError(
751
- `Client authentication method "${method}" is not allowed for discoverable clients`,
752
- )
753
- default:
754
- throw new InvalidClientMetadataError(
755
- `Unsupported client authentication method "${method}"`,
756
- )
757
- }
758
-
759
758
  for (const redirectUri of metadata.redirect_uris) {
760
759
  const url = parseRedirectUri(redirectUri)
761
760
 
@@ -1,18 +1,21 @@
1
1
  import {
2
+ JWTClaimVerificationOptions,
3
+ type JWTHeaderParameters,
2
4
  type JWTPayload,
3
- type JWTVerifyGetKey,
4
5
  type JWTVerifyOptions,
5
6
  type JWTVerifyResult,
6
7
  type KeyLike,
7
8
  type ResolvedKey,
8
9
  UnsecuredJWT,
9
10
  type UnsecuredResult,
11
+ calculateJwkThumbprint,
10
12
  createLocalJWKSet,
11
13
  createRemoteJWKSet,
12
14
  errors,
15
+ exportJWK,
13
16
  jwtVerify,
14
17
  } from 'jose'
15
- import { Jwks } from '@atproto/jwk'
18
+ import { Jwks, SignedJwt, UnsignedJwt } from '@atproto/jwk'
16
19
  import {
17
20
  CLIENT_ASSERTION_TYPE_JWT_BEARER,
18
21
  OAuthAuthorizationRequestParameters,
@@ -27,8 +30,10 @@ import { InvalidClientMetadataError } from '../errors/invalid-client-metadata-er
27
30
  import { InvalidParametersError } from '../errors/invalid-parameters-error.js'
28
31
  import { InvalidRequestError } from '../errors/invalid-request-error.js'
29
32
  import { InvalidScopeError } from '../errors/invalid-scope-error.js'
33
+ import { asArray } from '../lib/util/cast.js'
30
34
  import { compareRedirectUri } from '../lib/util/redirect-uri.js'
31
- import { ClientAuth, authJwkThumbprint } from './client-auth.js'
35
+ import { Awaitable } from '../lib/util/type.js'
36
+ import { ClientAuth } from './client-auth.js'
32
37
  import { ClientId } from './client-id.js'
33
38
  import { ClientInfo } from './client-info.js'
34
39
 
@@ -40,7 +45,9 @@ export class Client {
40
45
  */
41
46
  static readonly AUTH_METHODS_SUPPORTED = ['none', 'private_key_jwt'] as const
42
47
 
43
- private readonly keyGetter: JWTVerifyGetKey
48
+ private readonly keyGetter: (
49
+ protectedHeader: JWTHeaderParameters,
50
+ ) => Awaitable<KeyLike | Uint8Array>
44
51
 
45
52
  constructor(
46
53
  public readonly id: ClientId,
@@ -55,26 +62,42 @@ export class Client {
55
62
  : createRemoteJWKSet(new URL(metadata.jwks_uri), {})
56
63
  }
57
64
 
58
- public async decodeRequestObject(jar: string) {
65
+ /**
66
+ * @see {@link https://www.rfc-editor.org/rfc/rfc9101.html#name-request-object-2}
67
+ */
68
+ public async decodeRequestObject(
69
+ jar: SignedJwt | UnsignedJwt,
70
+ audience: string,
71
+ ) {
72
+ // https://www.rfc-editor.org/rfc/rfc9101.html#name-request-object-2
73
+ // > If signed, the Authorization Request Object SHOULD contain the Claims
74
+ // > iss (issuer) and aud (audience) as members with their semantics being
75
+ // > the same as defined in the JWT [RFC7519] specification. The value of
76
+ // > aud should be the value of the authorization server (AS) issuer, as
77
+ // > defined in RFC 8414 [RFC8414].
59
78
  try {
60
- switch (this.metadata.request_object_signing_alg) {
61
- case 'none':
62
- return await this.jwtVerifyUnsecured(jar, {
63
- maxTokenAge: JAR_MAX_AGE / 1000,
64
- })
65
- case undefined:
66
- // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2
67
- // > The default, if omitted, is that any algorithm supported by the OP
68
- // > and the RP MAY be used.
69
- return await this.jwtVerify(jar, {
70
- maxTokenAge: JAR_MAX_AGE / 1000,
71
- })
72
- default:
73
- return await this.jwtVerify(jar, {
74
- maxTokenAge: JAR_MAX_AGE / 1000,
75
- algorithms: [this.metadata.request_object_signing_alg],
76
- })
79
+ // We need to special case the "none" algorithm, as the validation method
80
+ // is different for signed and unsigned JWTs.
81
+ if (this.metadata.request_object_signing_alg === 'none') {
82
+ return await this.jwtVerifyUnsecured(jar, {
83
+ audience,
84
+ maxTokenAge: JAR_MAX_AGE / 1e3,
85
+ allowMissingAudience: true,
86
+ allowMissingIssuer: true,
87
+ })
77
88
  }
89
+
90
+ return await this.jwtVerify(jar, {
91
+ audience,
92
+ maxTokenAge: JAR_MAX_AGE / 1e3,
93
+ algorithms: this.metadata.request_object_signing_alg
94
+ ? [this.metadata.request_object_signing_alg]
95
+ : // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2
96
+ //
97
+ // > The default, if omitted, is that any algorithm supported by the OP
98
+ // > and the RP MAY be used.
99
+ undefined,
100
+ })
78
101
  } catch (err) {
79
102
  const message =
80
103
  err instanceof JOSEError
@@ -87,12 +110,38 @@ export class Client {
87
110
 
88
111
  protected async jwtVerifyUnsecured<PayloadType = JWTPayload>(
89
112
  token: string,
90
- options?: Omit<JWTVerifyOptions, 'issuer'>,
113
+ {
114
+ audience,
115
+ allowMissingAudience = false,
116
+ allowMissingIssuer = false,
117
+ ...options
118
+ }: Omit<JWTClaimVerificationOptions, 'issuer'> & {
119
+ allowMissingIssuer?: boolean
120
+ allowMissingAudience?: boolean
121
+ } = {},
91
122
  ): Promise<UnsecuredResult<PayloadType>> {
92
- return UnsecuredJWT.decode(token, {
93
- ...options,
94
- issuer: this.id,
95
- })
123
+ // jose does not support `allowMissingAudience` and `allowMissingIssuer`
124
+ // options, so we need to handle audience and issuer checks manually (see
125
+ // bellow).
126
+
127
+ const result = UnsecuredJWT.decode<PayloadType>(token, options)
128
+
129
+ if (!allowMissingIssuer || result.payload.iss != null) {
130
+ if (result.payload.iss !== this.id) {
131
+ throw new JOSEError(`Invalid "iss" claim "${result.payload.iss}"`)
132
+ }
133
+ }
134
+
135
+ if (!allowMissingAudience || result.payload.aud != null) {
136
+ if (audience != null) {
137
+ const payloadAud = asArray(result.payload.aud)
138
+ if (!asArray(audience).some((aud) => payloadAud.includes(aud))) {
139
+ throw new JOSEError(`Invalid "aud" claim "${result.payload.aud}"`)
140
+ }
141
+ }
142
+ }
143
+
144
+ return result
96
145
  }
97
146
 
98
147
  protected async jwtVerify<PayloadType = JWTPayload>(
@@ -110,63 +159,103 @@ export class Client {
110
159
  * @see {@link https://datatracker.ietf.org/doc/html/rfc7523#section-3}
111
160
  * @see {@link https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#token-endpoint-auth-method}
112
161
  */
113
- public async verifyCredentials(
162
+ public async authenticate(
114
163
  input: OAuthClientCredentials,
115
164
  checks: {
116
- audience: string
165
+ authorizationServerIdentifier: string
117
166
  },
118
- ): Promise<{
119
- clientAuth: ClientAuth
120
- // for replay protection
121
- nonce?: string
122
- }> {
123
- const method = this.metadata[`token_endpoint_auth_method`]
167
+ ): Promise<ClientAuth> {
168
+ const method = this.metadata.token_endpoint_auth_method
124
169
 
125
170
  if (method === 'none') {
126
- const clientAuth: ClientAuth = { method: 'none' }
127
- return { clientAuth }
171
+ return { method: 'none' }
128
172
  }
129
173
 
130
174
  if (method === 'private_key_jwt') {
131
- if (!('client_assertion_type' in input) || !input.client_assertion_type) {
132
- throw new InvalidRequestError(
133
- `client_assertion_type required for "${method}"`,
134
- )
135
- } else if (!input.client_assertion) {
175
+ if (!('client_assertion' in input)) {
136
176
  throw new InvalidRequestError(
137
- `client_assertion required for "${method}"`,
177
+ `client authentication method "${method}" required a "client_assertion"`,
138
178
  )
139
179
  }
140
180
 
141
181
  if (input.client_assertion_type === CLIENT_ASSERTION_TYPE_JWT_BEARER) {
182
+ // https://www.rfc-editor.org/rfc/rfc7523.html#section-3
183
+
142
184
  const result = await this.jwtVerify<{
143
185
  jti: string
186
+ exp?: number
144
187
  }>(input.client_assertion, {
145
- audience: checks.audience,
188
+ // > 1. The JWT MUST contain an "iss" (issuer) claim that contains a
189
+ // > unique identifier for the entity that issued the JWT.
190
+ //
191
+ // The "issuer" is already checked by jwtVerify()
192
+
193
+ // > 2. The JWT MUST contain a "sub" (subject) claim identifying the
194
+ // > principal that is the subject of the JWT. Two cases need to be
195
+ // > differentiated: [...] For client authentication, the subject
196
+ // > MUST be the "client_id" of the OAuth client.
146
197
  subject: this.id,
198
+
199
+ // > 3. The JWT MUST contain an "aud" (audience) claim containing a
200
+ // > value that identifies the authorization server as an intended
201
+ // > audience. The token endpoint URL of the authorization server
202
+ // > MAY be used as a value for an "aud" element to identify the
203
+ // > authorization server as an intended audience of the JWT.
204
+ audience: checks.authorizationServerIdentifier,
205
+
206
+ requiredClaims: [
207
+ // > 4. The JWT MUST contain an "exp" (expiration time) claim that
208
+ // > limits the time window during which the JWT can be used.
209
+ //
210
+ // @TODO The presence of "exp" didn't use to be enforced by this
211
+ // implementation (or provided by the oauth-client). This is mostly
212
+ // fine because "iat" *is* required, but this makes this
213
+ // implementation non compliant with RFC7523. We can't just make it
214
+ // required as it might break existing clients.
215
+
216
+ // 'exp',
217
+
218
+ // > 7. The JWT MAY contain a "jti" (JWT ID) claim that provides a
219
+ // > unique identifier for the token. The authorization server
220
+ // > MAY ensure that JWTs are not replayed by maintaining the set
221
+ // > of used "jti" values for the length of time for which the
222
+ // > JWT would be considered valid based on the applicable "exp"
223
+ // > instant.
224
+ 'jti',
225
+ ],
226
+
227
+ // > 5. The JWT MAY contain an "nbf" (not before) claim that
228
+ // > identifies the time before which the token MUST NOT be
229
+ // > accepted for processing.
230
+ //
231
+ // This is already enforced by jose
232
+
233
+ // > 6. The JWT MAY contain an "iat" (issued at) claim that identifies
234
+ // > the time at which the JWT was issued. Note that the
235
+ // > authorization server may reject JWTs with an "iat" claim value
236
+ // > that is unreasonably far in the past.
147
237
  maxTokenAge: CLIENT_ASSERTION_MAX_AGE / 1000,
148
- requiredClaims: ['jti'],
149
238
  }).catch((err) => {
150
- if (err instanceof JOSEError) {
151
- const msg = `Validation of "client_assertion" failed: ${err.message}`
152
- throw new InvalidClientError(msg, err)
153
- }
239
+ const msg =
240
+ err instanceof JOSEError
241
+ ? `Validation of "client_assertion" failed: ${err.message}`
242
+ : `Unable to verify "client_assertion" JWT`
154
243
 
155
- throw err
244
+ throw new InvalidClientError(msg, err)
156
245
  })
157
246
 
158
247
  if (!result.protectedHeader.kid) {
159
248
  throw new InvalidClientError(`"kid" required in client_assertion`)
160
249
  }
161
250
 
162
- const clientAuth: ClientAuth = {
163
- method: CLIENT_ASSERTION_TYPE_JWT_BEARER,
251
+ return {
252
+ method: 'private_key_jwt',
253
+ jti: result.payload.jti,
254
+ exp: result.payload.exp,
164
255
  jkt: await authJwkThumbprint(result.key),
165
256
  alg: result.protectedHeader.alg,
166
257
  kid: result.protectedHeader.kid,
167
258
  }
168
-
169
- return { clientAuth, nonce: result.payload.jti }
170
259
  }
171
260
 
172
261
  throw new InvalidClientError(
@@ -189,41 +278,6 @@ export class Client {
189
278
  )
190
279
  }
191
280
 
192
- /**
193
- * Ensures that a {@link ClientAuth} generated in the past is still valid wrt
194
- * the current client metadata & jwks. This is used to invalidate tokens when
195
- * the client stops advertising the key that it used to authenticate itself
196
- * during the initial token request.
197
- */
198
- public async validateClientAuth(clientAuth: ClientAuth): Promise<boolean> {
199
- if (clientAuth.method === 'none') {
200
- return this.metadata[`token_endpoint_auth_method`] === 'none'
201
- }
202
-
203
- if (clientAuth.method === CLIENT_ASSERTION_TYPE_JWT_BEARER) {
204
- if (this.metadata[`token_endpoint_auth_method`] !== 'private_key_jwt') {
205
- return false
206
- }
207
- try {
208
- const key = await this.keyGetter(
209
- {
210
- kid: clientAuth.kid,
211
- alg: clientAuth.alg,
212
- },
213
- { payload: '', signature: '' },
214
- )
215
- const jtk = await authJwkThumbprint(key)
216
-
217
- return jtk === clientAuth.jkt
218
- } catch (e) {
219
- return false
220
- }
221
- }
222
-
223
- // @ts-expect-error
224
- throw new Error(`Invalid method "${clientAuth.method}"`)
225
- }
226
-
227
281
  /**
228
282
  * Validates the request parameters against the client metadata.
229
283
  */
@@ -328,3 +382,13 @@ export class Client {
328
382
  return redirect_uris.length === 1 ? redirect_uris[0] : undefined
329
383
  }
330
384
  }
385
+
386
+ export async function authJwkThumbprint(
387
+ key: Uint8Array | KeyLike,
388
+ ): Promise<string> {
389
+ try {
390
+ return await calculateJwkThumbprint(await exportJWK(key), 'sha512')
391
+ } catch (err) {
392
+ throw new InvalidClientError('Unable to compute JWK thumbprint', err)
393
+ }
394
+ }
package/src/constants.ts CHANGED
@@ -38,17 +38,17 @@ export const TOKEN_MAX_AGE = 60 * MINUTE
38
38
  /** 5 minutes */
39
39
  export const AUTHORIZATION_INACTIVITY_TIMEOUT = 5 * MINUTE
40
40
 
41
- /** 1 months */
42
- export const AUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT = 1 * MONTH
41
+ /** 1 week */
42
+ export const PUBLIC_CLIENT_SESSION_LIFETIME = 1 * WEEK
43
43
 
44
44
  /** 2 days */
45
- export const UNAUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT = 2 * DAY
46
-
47
- /** 1 week */
48
- export const UNAUTHENTICATED_REFRESH_LIFETIME = 1 * WEEK
45
+ export const PUBLIC_CLIENT_REFRESH_LIFETIME = 2 * DAY
49
46
 
50
47
  /** 1 year */
51
- export const AUTHENTICATED_REFRESH_LIFETIME = 1 * YEAR
48
+ export const CONFIDENTIAL_CLIENT_SESSION_LIFETIME = 1 * YEAR
49
+
50
+ /** 1 months */
51
+ export const CONFIDENTIAL_CLIENT_REFRESH_LIFETIME = 1 * MONTH
52
52
 
53
53
  /** 5 minutes */
54
54
  export const PAR_EXPIRES_IN = 5 * MINUTE
@@ -73,3 +73,5 @@ export const SESSION_FIXATION_MAX_AGE = 5 * SECOND
73
73
 
74
74
  /** 1 day */
75
75
  export const CODE_CHALLENGE_REPLAY_TIMEFRAME = 1 * DAY
76
+
77
+ export const NODE_ENV = process.env.NODE_ENV || 'production'
@@ -2,7 +2,7 @@ import { Keyset } from '@atproto/jwk'
2
2
  import {
3
3
  OAuthAuthorizationServerMetadata,
4
4
  OAuthIssuerIdentifier,
5
- oauthAuthorizationServerMetadataSchema,
5
+ oauthAuthorizationServerMetadataValidator,
6
6
  } from '@atproto/oauth-types'
7
7
  import { Client } from '../client/client.js'
8
8
  import { VERIFY_ALGOS } from '../lib/util/crypto.js'
@@ -22,7 +22,7 @@ export function buildMetadata(
22
22
  keyset: Keyset,
23
23
  customMetadata?: CustomMetadata,
24
24
  ): OAuthAuthorizationServerMetadata {
25
- return oauthAuthorizationServerMetadataSchema.parse({
25
+ return oauthAuthorizationServerMetadataValidator.parse({
26
26
  issuer,
27
27
 
28
28
  scopes_supported: [