@atproto/oauth-provider 0.11.1 → 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.
- package/CHANGELOG.md +26 -0
- package/dist/access-token/access-token-mode.d.ts +1 -1
- package/dist/access-token/access-token-mode.d.ts.map +1 -1
- package/dist/access-token/access-token-mode.js +1 -1
- package/dist/access-token/access-token-mode.js.map +1 -1
- package/dist/lib/util/function.d.ts +1 -0
- package/dist/lib/util/function.d.ts.map +1 -1
- package/dist/lib/util/function.js +4 -0
- package/dist/lib/util/function.js.map +1 -1
- package/dist/oauth-hooks.d.ts +36 -3
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-hooks.js.map +1 -1
- package/dist/oauth-provider.d.ts +4 -4
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +10 -16
- package/dist/oauth-provider.js.map +1 -1
- package/dist/oauth-verifier.d.ts +22 -9
- package/dist/oauth-verifier.d.ts.map +1 -1
- package/dist/oauth-verifier.js +61 -6
- package/dist/oauth-verifier.js.map +1 -1
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +11 -7
- package/dist/request/request-manager.js.map +1 -1
- package/dist/signer/{signed-token-payload.d.ts → access-token-payload.d.ts} +3 -3
- package/dist/signer/{signed-token-payload.d.ts.map → access-token-payload.d.ts.map} +1 -1
- package/dist/signer/{signed-token-payload.js → access-token-payload.js} +3 -3
- package/dist/signer/{signed-token-payload.js.map → access-token-payload.js.map} +1 -1
- package/dist/signer/signer.d.ts +3 -3
- package/dist/signer/signer.d.ts.map +1 -1
- package/dist/signer/signer.js +2 -2
- package/dist/signer/signer.js.map +1 -1
- package/dist/token/token-claims.d.ts +23 -0
- package/dist/token/token-claims.d.ts.map +1 -0
- package/dist/token/token-claims.js +3 -0
- package/dist/token/token-claims.js.map +1 -0
- package/dist/token/token-manager.d.ts +11 -6
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +46 -28
- package/dist/token/token-manager.js.map +1 -1
- package/package.json +8 -8
- package/src/access-token/access-token-mode.ts +1 -1
- package/src/lib/util/function.ts +4 -0
- package/src/oauth-hooks.ts +43 -1
- package/src/oauth-provider.ts +17 -31
- package/src/oauth-verifier.ts +122 -50
- package/src/request/request-manager.ts +12 -8
- package/src/signer/{signed-token-payload.ts → access-token-payload.ts} +2 -2
- package/src/signer/signer.ts +7 -7
- package/src/token/token-claims.ts +21 -0
- package/src/token/token-manager.ts +64 -58
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/token/verify-token-claims.d.ts +0 -20
- package/dist/token/verify-token-claims.d.ts.map +0 -1
- package/dist/token/verify-token-claims.js +0 -53
- package/dist/token/verify-token-claims.js.map +0 -1
- 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 {
|
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
|
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
|
54
|
+
protected async createAccessToken(
|
59
55
|
tokenId: TokenId,
|
60
|
-
account: Account,
|
61
56
|
client: Client,
|
57
|
+
account: Account,
|
62
58
|
parameters: OAuthAuthorizationRequestParameters,
|
63
|
-
|
59
|
+
issuedAt: Date,
|
64
60
|
expiresAt: Date,
|
65
|
-
scope:
|
61
|
+
scope: OAuthScope,
|
66
62
|
): Promise<OAuthAccessToken> {
|
67
|
-
|
63
|
+
const claims: TokenClaims = {
|
68
64
|
jti: tokenId,
|
69
65
|
sub: account.sub,
|
66
|
+
iat: dateToEpoch(issuedAt),
|
70
67
|
exp: dateToEpoch(expiresAt),
|
71
|
-
|
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(
|
@@ -101,19 +112,20 @@ export class TokenManager {
|
|
101
112
|
|
102
113
|
const scope = await this.lexiconManager
|
103
114
|
.buildTokenScope(parameters.scope!)
|
104
|
-
.catch((
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
115
|
+
.catch((err) => {
|
116
|
+
// Parse expected errors
|
117
|
+
if (err instanceof LexiconResolutionError) {
|
118
|
+
throw new InvalidRequestError(err.message, err)
|
119
|
+
}
|
120
|
+
|
121
|
+
// Unexpected error
|
122
|
+
throw err
|
111
123
|
})
|
112
124
|
|
113
|
-
const accessToken = await this.
|
125
|
+
const accessToken = await this.createAccessToken(
|
114
126
|
tokenId,
|
115
|
-
account,
|
116
127
|
client,
|
128
|
+
account,
|
117
129
|
parameters,
|
118
130
|
now,
|
119
131
|
expiresAt,
|
@@ -237,10 +249,10 @@ export class TokenManager {
|
|
237
249
|
scope,
|
238
250
|
})
|
239
251
|
|
240
|
-
const accessToken = await this.
|
252
|
+
const accessToken = await this.createAccessToken(
|
241
253
|
nextTokenId,
|
242
|
-
account,
|
243
254
|
client,
|
255
|
+
account,
|
244
256
|
parameters,
|
245
257
|
now,
|
246
258
|
expiresAt,
|
@@ -349,13 +361,16 @@ export class TokenManager {
|
|
349
361
|
return this.store.readToken(tokenId)
|
350
362
|
}
|
351
363
|
|
352
|
-
|
353
|
-
|
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(
|
354
370
|
tokenType: OAuthTokenType,
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
): Promise<VerifyTokenClaimsResult> {
|
371
|
+
tokenPayload: AccessTokenPayload,
|
372
|
+
): Promise<TokenClaims> {
|
373
|
+
const tokenId = tokenPayload.jti
|
359
374
|
const tokenInfo = await this.getTokenInfo(tokenId).catch((err) => {
|
360
375
|
throw InvalidTokenError.from(err, tokenType)
|
361
376
|
})
|
@@ -364,40 +379,31 @@ export class TokenManager {
|
|
364
379
|
throw new InvalidTokenError(tokenType, `Invalid token`)
|
365
380
|
}
|
366
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
|
+
|
367
392
|
if (isCurrentTokenExpired(tokenInfo)) {
|
368
393
|
await this.deleteToken(tokenId)
|
369
394
|
throw new InvalidTokenError(tokenType, `Token expired`)
|
370
395
|
}
|
371
396
|
|
372
|
-
|
373
|
-
const { parameters } = data
|
374
|
-
|
375
|
-
// Construct a list of claim, as if the token was a JWT.
|
376
|
-
const tokenClaims: SignedTokenPayload = {
|
377
|
-
iss: this.signer.issuer,
|
397
|
+
return {
|
378
398
|
jti: tokenId,
|
379
399
|
sub: account.sub,
|
380
|
-
exp: dateToEpoch(data.expiresAt),
|
381
400
|
iat: dateToEpoch(data.updatedAt),
|
382
|
-
|
383
|
-
|
384
|
-
// These are not stored in the JWT access token in "light" access token
|
385
|
-
// mode. See `buildAccessToken`.
|
401
|
+
exp: dateToEpoch(data.expiresAt),
|
386
402
|
aud: account.aud,
|
387
|
-
|
388
|
-
//
|
389
|
-
scope: data.scope ?? parameters.scope,
|
403
|
+
scope: data.scope ?? data.parameters.scope,
|
404
|
+
// https://datatracker.ietf.org/doc/html/rfc8693#section-4.3
|
390
405
|
client_id: data.clientId,
|
391
406
|
}
|
392
|
-
|
393
|
-
return verifyTokenClaims(
|
394
|
-
token,
|
395
|
-
tokenId,
|
396
|
-
tokenType,
|
397
|
-
tokenClaims,
|
398
|
-
dpopProof,
|
399
|
-
verifyOptions,
|
400
|
-
)
|
401
407
|
}
|
402
408
|
|
403
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/
|
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
|
-
}
|