@atproto/oauth-provider 0.11.2 → 0.12.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 (52) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/access-token/access-token-mode.d.ts +1 -1
  3. package/dist/access-token/access-token-mode.d.ts.map +1 -1
  4. package/dist/access-token/access-token-mode.js +1 -1
  5. package/dist/access-token/access-token-mode.js.map +1 -1
  6. package/dist/lib/util/function.d.ts +1 -0
  7. package/dist/lib/util/function.d.ts.map +1 -1
  8. package/dist/lib/util/function.js +4 -0
  9. package/dist/lib/util/function.js.map +1 -1
  10. package/dist/oauth-hooks.d.ts +36 -3
  11. package/dist/oauth-hooks.d.ts.map +1 -1
  12. package/dist/oauth-hooks.js.map +1 -1
  13. package/dist/oauth-provider.d.ts +4 -4
  14. package/dist/oauth-provider.d.ts.map +1 -1
  15. package/dist/oauth-provider.js +10 -16
  16. package/dist/oauth-provider.js.map +1 -1
  17. package/dist/oauth-verifier.d.ts +22 -9
  18. package/dist/oauth-verifier.d.ts.map +1 -1
  19. package/dist/oauth-verifier.js +61 -6
  20. package/dist/oauth-verifier.js.map +1 -1
  21. package/dist/signer/{signed-token-payload.d.ts → access-token-payload.d.ts} +3 -3
  22. package/dist/signer/{signed-token-payload.d.ts.map → access-token-payload.d.ts.map} +1 -1
  23. package/dist/signer/{signed-token-payload.js → access-token-payload.js} +3 -3
  24. package/dist/signer/{signed-token-payload.js.map → access-token-payload.js.map} +1 -1
  25. package/dist/signer/signer.d.ts +3 -3
  26. package/dist/signer/signer.d.ts.map +1 -1
  27. package/dist/signer/signer.js +2 -2
  28. package/dist/signer/signer.js.map +1 -1
  29. package/dist/token/token-claims.d.ts +23 -0
  30. package/dist/token/token-claims.d.ts.map +1 -0
  31. package/dist/token/token-claims.js +3 -0
  32. package/dist/token/token-claims.js.map +1 -0
  33. package/dist/token/token-manager.d.ts +11 -6
  34. package/dist/token/token-manager.d.ts.map +1 -1
  35. package/dist/token/token-manager.js +39 -24
  36. package/dist/token/token-manager.js.map +1 -1
  37. package/package.json +7 -7
  38. package/src/access-token/access-token-mode.ts +1 -1
  39. package/src/lib/util/function.ts +4 -0
  40. package/src/oauth-hooks.ts +43 -1
  41. package/src/oauth-provider.ts +17 -31
  42. package/src/oauth-verifier.ts +122 -50
  43. package/src/signer/{signed-token-payload.ts → access-token-payload.ts} +2 -2
  44. package/src/signer/signer.ts +7 -7
  45. package/src/token/token-claims.ts +21 -0
  46. package/src/token/token-manager.ts +56 -51
  47. package/tsconfig.build.tsbuildinfo +1 -1
  48. package/dist/token/verify-token-claims.d.ts +0 -20
  49. package/dist/token/verify-token-claims.d.ts.map +0 -1
  50. package/dist/token/verify-token-claims.js +0 -53
  51. package/dist/token/verify-token-claims.js.map +0 -1
  52. package/src/token/verify-token-claims.ts +0 -101
@@ -4,6 +4,7 @@ import type { Account } from '@atproto/oauth-provider-api'
4
4
  import {
5
5
  OAuthAccessToken,
6
6
  OAuthAuthorizationRequestParameters,
7
+ OAuthScope,
7
8
  OAuthTokenResponse,
8
9
  OAuthTokenType,
9
10
  } from '@atproto/oauth-types'
@@ -20,26 +21,21 @@ import { RequestMetadata } from '../lib/http/request.js'
20
21
  import { dateToEpoch, dateToRelativeSeconds } from '../lib/util/date.js'
21
22
  import { callAsync } from '../lib/util/function.js'
22
23
  import { OAuthHooks } from '../oauth-hooks.js'
23
- import { DpopProof } from '../oauth-verifier.js'
24
24
  import { Sub } from '../oidc/sub.js'
25
25
  import { Code, isCode } from '../request/code.js'
26
- import { SignedTokenPayload } from '../signer/signed-token-payload.js'
26
+ import { AccessTokenPayload } from '../signer/access-token-payload.js'
27
27
  import { Signer } from '../signer/signer.js'
28
28
  import {
29
29
  RefreshToken,
30
30
  generateRefreshToken,
31
31
  isRefreshToken,
32
32
  } from './refresh-token.js'
33
+ import { TokenClaims } from './token-claims.js'
33
34
  import { TokenId, generateTokenId, isTokenId } from './token-id.js'
34
35
  import { CreateTokenData, TokenInfo, TokenStore } from './token-store.js'
35
- import {
36
- VerifyTokenClaimsOptions,
37
- VerifyTokenClaimsResult,
38
- verifyTokenClaims,
39
- } from './verify-token-claims.js'
40
36
 
41
37
  export { AccessTokenMode, Signer }
42
- export type { OAuthHooks, TokenStore, VerifyTokenClaimsResult }
38
+ export type { OAuthHooks, TokenStore }
43
39
 
44
40
  export class TokenManager {
45
41
  constructor(
@@ -55,29 +51,44 @@ export class TokenManager {
55
51
  return new Date(now.getTime() + this.tokenMaxAge)
56
52
  }
57
53
 
58
- protected async buildAccessToken(
54
+ protected async createAccessToken(
59
55
  tokenId: TokenId,
60
- account: Account,
61
56
  client: Client,
57
+ account: Account,
62
58
  parameters: OAuthAuthorizationRequestParameters,
63
- createdAt: Date,
59
+ issuedAt: Date,
64
60
  expiresAt: Date,
65
- scope: string,
61
+ scope: OAuthScope,
66
62
  ): Promise<OAuthAccessToken> {
67
- return this.signer.createAccessToken({
63
+ const claims: TokenClaims = {
68
64
  jti: tokenId,
69
65
  sub: account.sub,
66
+ iat: dateToEpoch(issuedAt),
70
67
  exp: dateToEpoch(expiresAt),
71
- iat: dateToEpoch(createdAt),
72
- cnf: parameters.dpop_jkt ? { jkt: parameters.dpop_jkt } : undefined,
68
+ aud: account.aud,
73
69
 
70
+ ...(parameters.dpop_jkt && {
71
+ cnf: { jkt: parameters.dpop_jkt },
72
+ }),
73
+
74
+ // Because tokens can end-up being quite big, we only include the scope in
75
+ // stateless mode.
74
76
  ...(this.accessTokenMode === AccessTokenMode.stateless && {
75
- aud: account.aud,
76
77
  scope,
77
- // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3
78
- client_id: client.id,
79
78
  }),
79
+
80
+ // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3
81
+ client_id: client.id,
82
+ }
83
+
84
+ const claimsOverride = await callAsync(this.hooks.onCreateToken, {
85
+ client,
86
+ account,
87
+ parameters,
88
+ claims,
80
89
  })
90
+
91
+ return this.signer.createAccessToken(claimsOverride ?? claims)
81
92
  }
82
93
 
83
94
  async createToken(
@@ -111,10 +122,10 @@ export class TokenManager {
111
122
  throw err
112
123
  })
113
124
 
114
- const accessToken = await this.buildAccessToken(
125
+ const accessToken = await this.createAccessToken(
115
126
  tokenId,
116
- account,
117
127
  client,
128
+ account,
118
129
  parameters,
119
130
  now,
120
131
  expiresAt,
@@ -238,10 +249,10 @@ export class TokenManager {
238
249
  scope,
239
250
  })
240
251
 
241
- const accessToken = await this.buildAccessToken(
252
+ const accessToken = await this.createAccessToken(
242
253
  nextTokenId,
243
- account,
244
254
  client,
255
+ account,
245
256
  parameters,
246
257
  now,
247
258
  expiresAt,
@@ -350,13 +361,16 @@ export class TokenManager {
350
361
  return this.store.readToken(tokenId)
351
362
  }
352
363
 
353
- async verifyToken(
354
- token: OAuthAccessToken,
364
+ /**
365
+ * This method is called to when decoding a token that was encoded in
366
+ * {@link AccessTokenMode.light} mode, using data from the store to fill the
367
+ * data that was omitted in the token itself.
368
+ */
369
+ async loadTokenClaims(
355
370
  tokenType: OAuthTokenType,
356
- tokenId: TokenId,
357
- dpopProof: null | DpopProof,
358
- verifyOptions?: VerifyTokenClaimsOptions,
359
- ): Promise<VerifyTokenClaimsResult> {
371
+ tokenPayload: AccessTokenPayload,
372
+ ): Promise<TokenClaims> {
373
+ const tokenId = tokenPayload.jti
360
374
  const tokenInfo = await this.getTokenInfo(tokenId).catch((err) => {
361
375
  throw InvalidTokenError.from(err, tokenType)
362
376
  })
@@ -365,40 +379,31 @@ export class TokenManager {
365
379
  throw new InvalidTokenError(tokenType, `Invalid token`)
366
380
  }
367
381
 
382
+ const { account, data } = tokenInfo
383
+
384
+ // Fool proof, make sure that the database & token payload are consistent.
385
+ // These should both be either undefined or a string so it's safe to compare
386
+ // the values directly.
387
+ if (tokenPayload.cnf?.jkt !== data.parameters.dpop_jkt) {
388
+ await this.deleteToken(tokenId)
389
+ throw new InvalidTokenError(tokenType, `Invalid token`)
390
+ }
391
+
368
392
  if (isCurrentTokenExpired(tokenInfo)) {
369
393
  await this.deleteToken(tokenId)
370
394
  throw new InvalidTokenError(tokenType, `Token expired`)
371
395
  }
372
396
 
373
- const { account, data } = tokenInfo
374
- const { parameters } = data
375
-
376
- // Construct a list of claim, as if the token was a JWT.
377
- const tokenClaims: SignedTokenPayload = {
378
- iss: this.signer.issuer,
397
+ return {
379
398
  jti: tokenId,
380
399
  sub: account.sub,
381
- exp: dateToEpoch(data.expiresAt),
382
400
  iat: dateToEpoch(data.updatedAt),
383
- cnf: parameters.dpop_jkt ? { jkt: parameters.dpop_jkt } : undefined,
384
-
385
- // These are not stored in the JWT access token in "light" access token
386
- // mode. See `buildAccessToken`.
401
+ exp: dateToEpoch(data.expiresAt),
387
402
  aud: account.aud,
388
- // Note we fallback to parameters.scope for sessions created before
389
- // TokenData.scope was introduced.
390
- scope: data.scope ?? parameters.scope,
403
+ scope: data.scope ?? data.parameters.scope,
404
+ // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3
391
405
  client_id: data.clientId,
392
406
  }
393
-
394
- return verifyTokenClaims(
395
- token,
396
- tokenId,
397
- tokenType,
398
- tokenClaims,
399
- dpopProof,
400
- verifyOptions,
401
- )
402
407
  }
403
408
 
404
409
  async listAccountTokens(sub: Sub): Promise<TokenInfo[]> {
@@ -1 +1 @@
1
- {"root":["./src/constants.ts","./src/index.ts","./src/oauth-client.ts","./src/oauth-dpop.ts","./src/oauth-errors.ts","./src/oauth-hooks.ts","./src/oauth-middleware.ts","./src/oauth-provider.ts","./src/oauth-store.ts","./src/oauth-verifier.ts","./src/access-token/access-token-mode.ts","./src/account/account-manager.ts","./src/account/account-store.ts","./src/account/sign-in-data.ts","./src/account/sign-up-input.ts","./src/client/client-auth.ts","./src/client/client-data.ts","./src/client/client-id.ts","./src/client/client-info.ts","./src/client/client-manager.ts","./src/client/client-store.ts","./src/client/client-utils.ts","./src/client/client.ts","./src/customization/branding.ts","./src/customization/build-customization-css.ts","./src/customization/build-customization-data.ts","./src/customization/colors.ts","./src/customization/customization.ts","./src/customization/links.ts","./src/device/device-data.ts","./src/device/device-id.ts","./src/device/device-manager.ts","./src/device/device-store.ts","./src/device/session-id.ts","./src/dpop/dpop-manager.ts","./src/dpop/dpop-nonce.ts","./src/dpop/dpop-proof.ts","./src/errors/access-denied-error.ts","./src/errors/account-selection-required-error.ts","./src/errors/authorization-error.ts","./src/errors/consent-required-error.ts","./src/errors/error-parser.ts","./src/errors/handle-unavailable-error.ts","./src/errors/invalid-authorization-details-error.ts","./src/errors/invalid-client-error.ts","./src/errors/invalid-client-id-error.ts","./src/errors/invalid-client-metadata-error.ts","./src/errors/invalid-dpop-key-binding-error.ts","./src/errors/invalid-dpop-proof-error.ts","./src/errors/invalid-grant-error.ts","./src/errors/invalid-invite-code-error.ts","./src/errors/invalid-redirect-uri-error.ts","./src/errors/invalid-request-error.ts","./src/errors/invalid-scope-error.ts","./src/errors/invalid-token-error.ts","./src/errors/login-required-error.ts","./src/errors/oauth-error.ts","./src/errors/second-authentication-factor-required-error.ts","./src/errors/unauthorized-client-error.ts","./src/errors/use-dpop-nonce-error.ts","./src/errors/www-authenticate-error.ts","./src/lexicon/lexicon-data.ts","./src/lexicon/lexicon-getter.ts","./src/lexicon/lexicon-manager.ts","./src/lexicon/lexicon-store.ts","./src/lib/hcaptcha.ts","./src/lib/nsid.ts","./src/lib/redis.ts","./src/lib/send-web-page.ts","./src/lib/csp/index.ts","./src/lib/html/build-document.ts","./src/lib/html/escapers.ts","./src/lib/html/html.ts","./src/lib/html/hydration-data.ts","./src/lib/html/index.ts","./src/lib/html/tags.ts","./src/lib/html/util.ts","./src/lib/http/accept.ts","./src/lib/http/context.ts","./src/lib/http/headers.ts","./src/lib/http/index.ts","./src/lib/http/method.ts","./src/lib/http/middleware.ts","./src/lib/http/parser.ts","./src/lib/http/path.ts","./src/lib/http/request.ts","./src/lib/http/response.ts","./src/lib/http/route.ts","./src/lib/http/router.ts","./src/lib/http/security-headers.ts","./src/lib/http/stream.ts","./src/lib/http/types.ts","./src/lib/http/url.ts","./src/lib/util/authorization-header.ts","./src/lib/util/cast.ts","./src/lib/util/color.ts","./src/lib/util/crypto.ts","./src/lib/util/date.ts","./src/lib/util/error.ts","./src/lib/util/function.ts","./src/lib/util/locale.ts","./src/lib/util/redirect-uri.ts","./src/lib/util/time.ts","./src/lib/util/type.ts","./src/lib/util/ui8.ts","./src/lib/util/well-known.ts","./src/lib/util/zod-error.ts","./src/metadata/build-metadata.ts","./src/oidc/sub.ts","./src/replay/replay-manager.ts","./src/replay/replay-store-memory.ts","./src/replay/replay-store-redis.ts","./src/replay/replay-store.ts","./src/request/code.ts","./src/request/request-data.ts","./src/request/request-id.ts","./src/request/request-manager.ts","./src/request/request-store.ts","./src/request/request-uri.ts","./src/result/authorization-redirect-parameters.ts","./src/result/authorization-result-authorize-page.ts","./src/result/authorization-result-redirect.ts","./src/router/create-account-page-middleware.ts","./src/router/create-api-middleware.ts","./src/router/create-authorization-page-middleware.ts","./src/router/create-oauth-middleware.ts","./src/router/error-handler.ts","./src/router/middleware-options.ts","./src/router/send-redirect.ts","./src/router/assets/assets-manifest.ts","./src/router/assets/assets.ts","./src/router/assets/csrf.ts","./src/router/assets/send-account-page.ts","./src/router/assets/send-authorization-page.ts","./src/router/assets/send-error-page.ts","./src/signer/api-token-payload.ts","./src/signer/signed-token-payload.ts","./src/signer/signer.ts","./src/token/refresh-token.ts","./src/token/token-data.ts","./src/token/token-id.ts","./src/token/token-manager.ts","./src/token/token-store.ts","./src/token/verify-token-claims.ts","./src/types/authorization-response-error.ts","./src/types/color-hue.ts","./src/types/email-otp.ts","./src/types/email.ts","./src/types/handle.ts","./src/types/invite-code.ts","./src/types/par-response-error.ts","./src/types/password.ts","./src/types/rgb-color.ts"],"version":"5.8.3"}
1
+ {"root":["./src/constants.ts","./src/index.ts","./src/oauth-client.ts","./src/oauth-dpop.ts","./src/oauth-errors.ts","./src/oauth-hooks.ts","./src/oauth-middleware.ts","./src/oauth-provider.ts","./src/oauth-store.ts","./src/oauth-verifier.ts","./src/access-token/access-token-mode.ts","./src/account/account-manager.ts","./src/account/account-store.ts","./src/account/sign-in-data.ts","./src/account/sign-up-input.ts","./src/client/client-auth.ts","./src/client/client-data.ts","./src/client/client-id.ts","./src/client/client-info.ts","./src/client/client-manager.ts","./src/client/client-store.ts","./src/client/client-utils.ts","./src/client/client.ts","./src/customization/branding.ts","./src/customization/build-customization-css.ts","./src/customization/build-customization-data.ts","./src/customization/colors.ts","./src/customization/customization.ts","./src/customization/links.ts","./src/device/device-data.ts","./src/device/device-id.ts","./src/device/device-manager.ts","./src/device/device-store.ts","./src/device/session-id.ts","./src/dpop/dpop-manager.ts","./src/dpop/dpop-nonce.ts","./src/dpop/dpop-proof.ts","./src/errors/access-denied-error.ts","./src/errors/account-selection-required-error.ts","./src/errors/authorization-error.ts","./src/errors/consent-required-error.ts","./src/errors/error-parser.ts","./src/errors/handle-unavailable-error.ts","./src/errors/invalid-authorization-details-error.ts","./src/errors/invalid-client-error.ts","./src/errors/invalid-client-id-error.ts","./src/errors/invalid-client-metadata-error.ts","./src/errors/invalid-dpop-key-binding-error.ts","./src/errors/invalid-dpop-proof-error.ts","./src/errors/invalid-grant-error.ts","./src/errors/invalid-invite-code-error.ts","./src/errors/invalid-redirect-uri-error.ts","./src/errors/invalid-request-error.ts","./src/errors/invalid-scope-error.ts","./src/errors/invalid-token-error.ts","./src/errors/login-required-error.ts","./src/errors/oauth-error.ts","./src/errors/second-authentication-factor-required-error.ts","./src/errors/unauthorized-client-error.ts","./src/errors/use-dpop-nonce-error.ts","./src/errors/www-authenticate-error.ts","./src/lexicon/lexicon-data.ts","./src/lexicon/lexicon-getter.ts","./src/lexicon/lexicon-manager.ts","./src/lexicon/lexicon-store.ts","./src/lib/hcaptcha.ts","./src/lib/nsid.ts","./src/lib/redis.ts","./src/lib/send-web-page.ts","./src/lib/csp/index.ts","./src/lib/html/build-document.ts","./src/lib/html/escapers.ts","./src/lib/html/html.ts","./src/lib/html/hydration-data.ts","./src/lib/html/index.ts","./src/lib/html/tags.ts","./src/lib/html/util.ts","./src/lib/http/accept.ts","./src/lib/http/context.ts","./src/lib/http/headers.ts","./src/lib/http/index.ts","./src/lib/http/method.ts","./src/lib/http/middleware.ts","./src/lib/http/parser.ts","./src/lib/http/path.ts","./src/lib/http/request.ts","./src/lib/http/response.ts","./src/lib/http/route.ts","./src/lib/http/router.ts","./src/lib/http/security-headers.ts","./src/lib/http/stream.ts","./src/lib/http/types.ts","./src/lib/http/url.ts","./src/lib/util/authorization-header.ts","./src/lib/util/cast.ts","./src/lib/util/color.ts","./src/lib/util/crypto.ts","./src/lib/util/date.ts","./src/lib/util/error.ts","./src/lib/util/function.ts","./src/lib/util/locale.ts","./src/lib/util/redirect-uri.ts","./src/lib/util/time.ts","./src/lib/util/type.ts","./src/lib/util/ui8.ts","./src/lib/util/well-known.ts","./src/lib/util/zod-error.ts","./src/metadata/build-metadata.ts","./src/oidc/sub.ts","./src/replay/replay-manager.ts","./src/replay/replay-store-memory.ts","./src/replay/replay-store-redis.ts","./src/replay/replay-store.ts","./src/request/code.ts","./src/request/request-data.ts","./src/request/request-id.ts","./src/request/request-manager.ts","./src/request/request-store.ts","./src/request/request-uri.ts","./src/result/authorization-redirect-parameters.ts","./src/result/authorization-result-authorize-page.ts","./src/result/authorization-result-redirect.ts","./src/router/create-account-page-middleware.ts","./src/router/create-api-middleware.ts","./src/router/create-authorization-page-middleware.ts","./src/router/create-oauth-middleware.ts","./src/router/error-handler.ts","./src/router/middleware-options.ts","./src/router/send-redirect.ts","./src/router/assets/assets-manifest.ts","./src/router/assets/assets.ts","./src/router/assets/csrf.ts","./src/router/assets/send-account-page.ts","./src/router/assets/send-authorization-page.ts","./src/router/assets/send-error-page.ts","./src/signer/access-token-payload.ts","./src/signer/api-token-payload.ts","./src/signer/signer.ts","./src/token/refresh-token.ts","./src/token/token-claims.ts","./src/token/token-data.ts","./src/token/token-id.ts","./src/token/token-manager.ts","./src/token/token-store.ts","./src/types/authorization-response-error.ts","./src/types/color-hue.ts","./src/types/email-otp.ts","./src/types/email.ts","./src/types/handle.ts","./src/types/invite-code.ts","./src/types/par-response-error.ts","./src/types/password.ts","./src/types/rgb-color.ts"],"version":"5.8.3"}
@@ -1,20 +0,0 @@
1
- import { OAuthAccessToken, OAuthTokenType } from '@atproto/oauth-types';
2
- import { DpopProof } from '../oauth-verifier.js';
3
- import { SignedTokenPayload } from '../signer/signed-token-payload.js';
4
- import { TokenId } from './token-id.js';
5
- export type { DpopProof, OAuthAccessToken, OAuthTokenType, SignedTokenPayload, TokenId, };
6
- export type VerifyTokenClaimsOptions = {
7
- /** One of these audience must be included in the token audience(s) */
8
- audience?: [string, ...string[]];
9
- /** One of these scope must be included in the token scope(s) */
10
- scope?: [string, ...string[]];
11
- };
12
- export type VerifyTokenClaimsResult = {
13
- token: OAuthAccessToken;
14
- tokenId: TokenId;
15
- tokenType: OAuthTokenType;
16
- tokenClaims: SignedTokenPayload;
17
- dpopProof: null | DpopProof;
18
- };
19
- export declare function verifyTokenClaims(token: OAuthAccessToken, tokenId: TokenId, tokenType: OAuthTokenType, tokenClaims: SignedTokenPayload, dpopProof: null | DpopProof, options?: VerifyTokenClaimsOptions): VerifyTokenClaimsResult;
20
- //# sourceMappingURL=verify-token-claims.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"verify-token-claims.d.ts","sourceRoot":"","sources":["../../src/token/verify-token-claims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAKvE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAKvC,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,OAAO,GACR,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;IAChC,gEAAgE;IAChE,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,gBAAgB,CAAA;IACvB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,cAAc,CAAA;IACzB,WAAW,EAAE,kBAAkB,CAAA;IAC/B,SAAS,EAAE,IAAI,GAAG,SAAS,CAAA;CAC5B,CAAA;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,cAAc,EACzB,WAAW,EAAE,kBAAkB,EAC/B,SAAS,EAAE,IAAI,GAAG,SAAS,EAC3B,OAAO,CAAC,EAAE,wBAAwB,GACjC,uBAAuB,CA0DzB"}
@@ -1,53 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verifyTokenClaims = verifyTokenClaims;
4
- const invalid_dpop_key_binding_error_js_1 = require("../errors/invalid-dpop-key-binding-error.js");
5
- const invalid_dpop_proof_error_js_1 = require("../errors/invalid-dpop-proof-error.js");
6
- const cast_js_1 = require("../lib/util/cast.js");
7
- const oauth_errors_js_1 = require("../oauth-errors.js");
8
- const BEARER = 'Bearer';
9
- const DPOP = 'DPoP';
10
- function verifyTokenClaims(token, tokenId, tokenType, tokenClaims, dpopProof, options) {
11
- const dateReference = Date.now();
12
- if (tokenClaims.cnf?.jkt) {
13
- // An access token with a cnf.jkt claim must be a DPoP token
14
- if (tokenType !== DPOP) {
15
- throw new oauth_errors_js_1.InvalidTokenError(DPOP, `Access token is bound to a DPoP proof, but token type is ${tokenType}`);
16
- }
17
- // DPoP token type must be used with a DPoP proof
18
- if (!dpopProof) {
19
- throw new invalid_dpop_proof_error_js_1.InvalidDpopProofError(`DPoP proof required`);
20
- }
21
- // DPoP proof must be signed with the key that matches the "cnf" claim
22
- if (tokenClaims.cnf.jkt !== dpopProof.jkt) {
23
- throw new invalid_dpop_key_binding_error_js_1.InvalidDpopKeyBindingError();
24
- }
25
- }
26
- else {
27
- // An access token without a cnf.jkt claim must be a Bearer token
28
- if (tokenType !== BEARER) {
29
- throw new oauth_errors_js_1.InvalidTokenError(BEARER, `Bearer token type must be used without a DPoP proof`);
30
- }
31
- // Unexpected DPoP proof received for a Bearer token
32
- if (dpopProof) {
33
- throw new oauth_errors_js_1.InvalidTokenError(BEARER, `DPoP proof not expected for Bearer token type`);
34
- }
35
- }
36
- if (options?.audience) {
37
- const aud = (0, cast_js_1.asArray)(tokenClaims.aud);
38
- if (!options.audience.some((v) => aud.includes(v))) {
39
- throw new oauth_errors_js_1.InvalidTokenError(tokenType, `Invalid audience`);
40
- }
41
- }
42
- if (options?.scope) {
43
- const scopes = tokenClaims.scope?.split(' ');
44
- if (!scopes || !options.scope.some((v) => scopes.includes(v))) {
45
- throw new oauth_errors_js_1.InvalidTokenError(tokenType, `Invalid scope`);
46
- }
47
- }
48
- if (tokenClaims.exp != null && tokenClaims.exp * 1000 <= dateReference) {
49
- throw new oauth_errors_js_1.InvalidTokenError(tokenType, `Token expired`);
50
- }
51
- return { token, tokenId, tokenType, tokenClaims, dpopProof };
52
- }
53
- //# sourceMappingURL=verify-token-claims.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"verify-token-claims.js","sourceRoot":"","sources":["../../src/token/verify-token-claims.ts"],"names":[],"mappings":";;AAmCA,8CAiEC;AAnGD,mGAAwF;AACxF,uFAA6E;AAC7E,iDAA6C;AAC7C,wDAAsD;AAKtD,MAAM,MAAM,GAAG,QAAiC,CAAA;AAChD,MAAM,IAAI,GAAG,MAA+B,CAAA;AAyB5C,SAAgB,iBAAiB,CAC/B,KAAuB,EACvB,OAAgB,EAChB,SAAyB,EACzB,WAA+B,EAC/B,SAA2B,EAC3B,OAAkC;IAElC,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAEhC,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;QACzB,4DAA4D;QAC5D,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,MAAM,IAAI,mCAAiB,CACzB,IAAI,EACJ,4DAA4D,SAAS,EAAE,CACxE,CAAA;QACH,CAAC;QAED,iDAAiD;QACjD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,mDAAqB,CAAC,qBAAqB,CAAC,CAAA;QACxD,CAAC;QAED,sEAAsE;QACtE,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG,EAAE,CAAC;YAC1C,MAAM,IAAI,8DAA0B,EAAE,CAAA;QACxC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,iEAAiE;QACjE,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;YACzB,MAAM,IAAI,mCAAiB,CACzB,MAAM,EACN,qDAAqD,CACtD,CAAA;QACH,CAAC;QAED,oDAAoD;QACpD,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,IAAI,mCAAiB,CACzB,MAAM,EACN,+CAA+C,CAChD,CAAA;QACH,CAAC;IACH,CAAC;IAED,IAAI,OAAO,EAAE,QAAQ,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,IAAA,iBAAO,EAAC,WAAW,CAAC,GAAG,CAAC,CAAA;QACpC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,mCAAiB,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAA;QAC5D,CAAC;IACH,CAAC;IAED,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;QAC5C,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,MAAM,IAAI,mCAAiB,CAAC,SAAS,EAAE,eAAe,CAAC,CAAA;QACzD,CAAC;IACH,CAAC;IAED,IAAI,WAAW,CAAC,GAAG,IAAI,IAAI,IAAI,WAAW,CAAC,GAAG,GAAG,IAAI,IAAI,aAAa,EAAE,CAAC;QACvE,MAAM,IAAI,mCAAiB,CAAC,SAAS,EAAE,eAAe,CAAC,CAAA;IACzD,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,CAAA;AAC9D,CAAC"}
@@ -1,101 +0,0 @@
1
- import { OAuthAccessToken, OAuthTokenType } from '@atproto/oauth-types'
2
- import { InvalidDpopKeyBindingError } from '../errors/invalid-dpop-key-binding-error.js'
3
- import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js'
4
- import { asArray } from '../lib/util/cast.js'
5
- import { InvalidTokenError } from '../oauth-errors.js'
6
- import { DpopProof } from '../oauth-verifier.js'
7
- import { SignedTokenPayload } from '../signer/signed-token-payload.js'
8
- import { TokenId } from './token-id.js'
9
-
10
- const BEARER = 'Bearer' satisfies OAuthTokenType
11
- const DPOP = 'DPoP' satisfies OAuthTokenType
12
-
13
- export type {
14
- DpopProof,
15
- OAuthAccessToken,
16
- OAuthTokenType,
17
- SignedTokenPayload,
18
- TokenId,
19
- }
20
-
21
- export type VerifyTokenClaimsOptions = {
22
- /** One of these audience must be included in the token audience(s) */
23
- audience?: [string, ...string[]]
24
- /** One of these scope must be included in the token scope(s) */
25
- scope?: [string, ...string[]]
26
- }
27
-
28
- export type VerifyTokenClaimsResult = {
29
- token: OAuthAccessToken
30
- tokenId: TokenId
31
- tokenType: OAuthTokenType
32
- tokenClaims: SignedTokenPayload
33
- dpopProof: null | DpopProof
34
- }
35
-
36
- export function verifyTokenClaims(
37
- token: OAuthAccessToken,
38
- tokenId: TokenId,
39
- tokenType: OAuthTokenType,
40
- tokenClaims: SignedTokenPayload,
41
- dpopProof: null | DpopProof,
42
- options?: VerifyTokenClaimsOptions,
43
- ): VerifyTokenClaimsResult {
44
- const dateReference = Date.now()
45
-
46
- if (tokenClaims.cnf?.jkt) {
47
- // An access token with a cnf.jkt claim must be a DPoP token
48
- if (tokenType !== DPOP) {
49
- throw new InvalidTokenError(
50
- DPOP,
51
- `Access token is bound to a DPoP proof, but token type is ${tokenType}`,
52
- )
53
- }
54
-
55
- // DPoP token type must be used with a DPoP proof
56
- if (!dpopProof) {
57
- throw new InvalidDpopProofError(`DPoP proof required`)
58
- }
59
-
60
- // DPoP proof must be signed with the key that matches the "cnf" claim
61
- if (tokenClaims.cnf.jkt !== dpopProof.jkt) {
62
- throw new InvalidDpopKeyBindingError()
63
- }
64
- } else {
65
- // An access token without a cnf.jkt claim must be a Bearer token
66
- if (tokenType !== BEARER) {
67
- throw new InvalidTokenError(
68
- BEARER,
69
- `Bearer token type must be used without a DPoP proof`,
70
- )
71
- }
72
-
73
- // Unexpected DPoP proof received for a Bearer token
74
- if (dpopProof) {
75
- throw new InvalidTokenError(
76
- BEARER,
77
- `DPoP proof not expected for Bearer token type`,
78
- )
79
- }
80
- }
81
-
82
- if (options?.audience) {
83
- const aud = asArray(tokenClaims.aud)
84
- if (!options.audience.some((v) => aud.includes(v))) {
85
- throw new InvalidTokenError(tokenType, `Invalid audience`)
86
- }
87
- }
88
-
89
- if (options?.scope) {
90
- const scopes = tokenClaims.scope?.split(' ')
91
- if (!scopes || !options.scope.some((v) => scopes.includes(v))) {
92
- throw new InvalidTokenError(tokenType, `Invalid scope`)
93
- }
94
- }
95
-
96
- if (tokenClaims.exp != null && tokenClaims.exp * 1000 <= dateReference) {
97
- throw new InvalidTokenError(tokenType, `Token expired`)
98
- }
99
-
100
- return { token, tokenId, tokenType, tokenClaims, dpopProof }
101
- }