@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.
- package/CHANGELOG.md +20 -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/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 +39 -24
- package/dist/token/token-manager.js.map +1 -1
- package/package.json +7 -7
- 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/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 +56 -51
- 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
package/src/oauth-hooks.ts
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
import { Jwks } from '@atproto/jwk'
|
2
2
|
import type { Account } from '@atproto/oauth-provider-api'
|
3
3
|
import {
|
4
|
+
OAuthAccessToken,
|
4
5
|
OAuthAuthorizationDetails,
|
5
6
|
OAuthAuthorizationRequestParameters,
|
6
7
|
OAuthClientMetadata,
|
7
8
|
OAuthTokenResponse,
|
9
|
+
OAuthTokenType,
|
8
10
|
} from '@atproto/oauth-types'
|
9
11
|
import { SignInData } from './account/sign-in-data.js'
|
10
12
|
import { SignUpInput } from './account/sign-up-input.js'
|
@@ -12,6 +14,7 @@ import { ClientAuth } from './client/client-auth.js'
|
|
12
14
|
import { ClientId } from './client/client-id.js'
|
13
15
|
import { ClientInfo } from './client/client-info.js'
|
14
16
|
import { Client } from './client/client.js'
|
17
|
+
import { DpopProof } from './dpop/dpop-proof.js'
|
15
18
|
import { AccessDeniedError } from './errors/access-denied-error.js'
|
16
19
|
import { AuthorizationError } from './errors/authorization-error.js'
|
17
20
|
import { InvalidRequestError } from './errors/invalid-request-error.js'
|
@@ -22,13 +25,16 @@ import {
|
|
22
25
|
HcaptchaVerifyResult,
|
23
26
|
} from './lib/hcaptcha.js'
|
24
27
|
import { RequestMetadata } from './lib/http/request.js'
|
25
|
-
import { Awaitable } from './lib/util/type.js'
|
28
|
+
import { Awaitable, OmitKey } from './lib/util/type.js'
|
26
29
|
import { DeviceId, SignUpData } from './oauth-store.js'
|
27
30
|
import { RequestId } from './request/request-id.js'
|
31
|
+
import { AccessTokenPayload } from './signer/access-token-payload.js'
|
32
|
+
import { TokenClaims } from './token/token-claims.js'
|
28
33
|
|
29
34
|
// Make sure all types needed to implement the OAuthHooks are exported
|
30
35
|
export {
|
31
36
|
AccessDeniedError,
|
37
|
+
type AccessTokenPayload,
|
32
38
|
type Account,
|
33
39
|
AuthorizationError,
|
34
40
|
type Awaitable,
|
@@ -37,20 +43,24 @@ export {
|
|
37
43
|
type ClientId,
|
38
44
|
type ClientInfo,
|
39
45
|
type DeviceId,
|
46
|
+
type DpopProof,
|
40
47
|
type HcaptchaClientTokens,
|
41
48
|
type HcaptchaConfig,
|
42
49
|
type HcaptchaVerifyResult,
|
43
50
|
InvalidRequestError,
|
44
51
|
type Jwks,
|
52
|
+
type OAuthAccessToken,
|
45
53
|
type OAuthAuthorizationDetails,
|
46
54
|
type OAuthAuthorizationRequestParameters,
|
47
55
|
type OAuthClientMetadata,
|
48
56
|
OAuthError,
|
49
57
|
type OAuthTokenResponse,
|
58
|
+
type OAuthTokenType,
|
50
59
|
type RequestMetadata,
|
51
60
|
type SignInData,
|
52
61
|
type SignUpData,
|
53
62
|
type SignUpInput,
|
63
|
+
type TokenClaims,
|
54
64
|
}
|
55
65
|
|
56
66
|
export type OAuthHooks = {
|
@@ -151,6 +161,38 @@ export type OAuthHooks = {
|
|
151
161
|
requestId: RequestId
|
152
162
|
}) => Awaitable<void>
|
153
163
|
|
164
|
+
/**
|
165
|
+
* This hook is called whenever a token is about to be created. You can use
|
166
|
+
* it to modify the token claims or perform additional validation.
|
167
|
+
*
|
168
|
+
* This hook should never throw an error.
|
169
|
+
*/
|
170
|
+
onCreateToken?: (data: {
|
171
|
+
client: Client
|
172
|
+
account: Account
|
173
|
+
parameters: OAuthAuthorizationRequestParameters
|
174
|
+
claims: TokenClaims
|
175
|
+
}) => Awaitable<void | OmitKey<AccessTokenPayload, 'iss'>>
|
176
|
+
|
177
|
+
/**
|
178
|
+
* This hook is called whenever a token was just decoded, and basic validation
|
179
|
+
* was performed (signature, expiration, not-before).
|
180
|
+
*
|
181
|
+
* It can be used to modify the payload (e.g., to add custom claims), or to
|
182
|
+
* perform additional validation.
|
183
|
+
*
|
184
|
+
* This hook is called when authenticating requests through the
|
185
|
+
* `authenticateRequest()` method in `OAuthVerifier` and `OAuthProvider`.
|
186
|
+
*
|
187
|
+
* Any error thrown here will be propagated.
|
188
|
+
*/
|
189
|
+
onDecodeToken?: (data: {
|
190
|
+
tokenType: OAuthTokenType
|
191
|
+
token: OAuthAccessToken
|
192
|
+
payload: AccessTokenPayload
|
193
|
+
dpopProof: null | DpopProof
|
194
|
+
}) => Promise<AccessTokenPayload | void>
|
195
|
+
|
154
196
|
/**
|
155
197
|
* This hook is called when an authorized client exchanges an authorization
|
156
198
|
* code for an access token.
|
package/src/oauth-provider.ts
CHANGED
@@ -85,6 +85,7 @@ import {
|
|
85
85
|
DpopProof,
|
86
86
|
OAuthVerifier,
|
87
87
|
OAuthVerifierOptions,
|
88
|
+
VerifyTokenPayloadOptions,
|
88
89
|
} from './oauth-verifier.js'
|
89
90
|
import { ReplayStore, ifReplayStore } from './replay/replay-store.js'
|
90
91
|
import { codeSchema } from './request/code.js'
|
@@ -95,6 +96,7 @@ import { AuthorizationRedirectParameters } from './result/authorization-redirect
|
|
95
96
|
import { AuthorizationResultAuthorizePage } from './result/authorization-result-authorize-page.js'
|
96
97
|
import { AuthorizationResultRedirect } from './result/authorization-result-redirect.js'
|
97
98
|
import { ErrorHandler } from './router/error-handler.js'
|
99
|
+
import { AccessTokenPayload } from './signer/access-token-payload.js'
|
98
100
|
import { TokenData } from './token/token-data.js'
|
99
101
|
import { TokenManager } from './token/token-manager.js'
|
100
102
|
import {
|
@@ -102,14 +104,11 @@ import {
|
|
102
104
|
asTokenStore,
|
103
105
|
refreshTokenSchema,
|
104
106
|
} from './token/token-store.js'
|
105
|
-
import {
|
106
|
-
VerifyTokenClaimsOptions,
|
107
|
-
VerifyTokenClaimsResult,
|
108
|
-
} from './token/verify-token-claims.js'
|
109
107
|
import { isPARResponseError } from './types/par-response-error.js'
|
110
108
|
|
111
109
|
export { AccessTokenMode, Keyset }
|
112
110
|
export type {
|
111
|
+
AccessTokenPayload,
|
113
112
|
AuthorizationRedirectParameters,
|
114
113
|
AuthorizationResultAuthorizePage as AuthorizationResultAuthorize,
|
115
114
|
AuthorizationResultRedirect,
|
@@ -123,6 +122,7 @@ export type {
|
|
123
122
|
LexiconResolver,
|
124
123
|
MultiLangString,
|
125
124
|
OAuthAuthorizationServerMetadata,
|
125
|
+
VerifyTokenPayloadOptions,
|
126
126
|
}
|
127
127
|
|
128
128
|
type OAuthProviderConfig = {
|
@@ -1075,41 +1075,27 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1075
1075
|
}
|
1076
1076
|
}
|
1077
1077
|
|
1078
|
-
protected override async
|
1078
|
+
protected override async decodeToken(
|
1079
1079
|
tokenType: OAuthTokenType,
|
1080
1080
|
token: OAuthAccessToken,
|
1081
1081
|
dpopProof: null | DpopProof,
|
1082
|
-
|
1083
|
-
|
1084
|
-
if (this.accessTokenMode === AccessTokenMode.stateless) {
|
1085
|
-
return super.verifyToken(tokenType, token, dpopProof, verifyOptions)
|
1086
|
-
}
|
1087
|
-
|
1088
|
-
if (this.accessTokenMode === AccessTokenMode.light) {
|
1089
|
-
const { tokenClaims } = await super.verifyToken(
|
1090
|
-
tokenType,
|
1091
|
-
token,
|
1092
|
-
dpopProof,
|
1093
|
-
// Do not verify the scope and audience in case of "light" tokens.
|
1094
|
-
// these will be checked through the tokenManager hereafter.
|
1095
|
-
undefined,
|
1096
|
-
)
|
1082
|
+
): Promise<AccessTokenPayload> {
|
1083
|
+
const tokenPayload = await super.decodeToken(tokenType, token, dpopProof)
|
1097
1084
|
|
1098
|
-
|
1085
|
+
if (this.accessTokenMode !== AccessTokenMode.stateless) {
|
1086
|
+
// @NOTE in non stateless mode, some claims can be omitted (most notably
|
1087
|
+
// "scope"). We load the token claims here (allowing to ensure that the
|
1088
|
+
// token is still valid, and to retrieve a (potentially updated) set of
|
1089
|
+
// claims).
|
1099
1090
|
|
1100
|
-
|
1101
|
-
// also verify the tokenId is still valid using a database to fetch
|
1102
|
-
// missing data from "light" token.
|
1103
|
-
return this.tokenManager.verifyToken(
|
1104
|
-
token,
|
1091
|
+
const tokenClaims = await this.tokenManager.loadTokenClaims(
|
1105
1092
|
tokenType,
|
1106
|
-
|
1107
|
-
dpopProof,
|
1108
|
-
verifyOptions,
|
1093
|
+
tokenPayload,
|
1109
1094
|
)
|
1095
|
+
|
1096
|
+
Object.assign(tokenPayload, tokenClaims)
|
1110
1097
|
}
|
1111
1098
|
|
1112
|
-
|
1113
|
-
throw new Error('Invalid access token mode')
|
1099
|
+
return tokenPayload
|
1114
1100
|
}
|
1115
1101
|
}
|
package/src/oauth-verifier.ts
CHANGED
@@ -9,53 +9,65 @@ import {
|
|
9
9
|
import { DpopManager, DpopManagerOptions } from './dpop/dpop-manager.js'
|
10
10
|
import { DpopNonce } from './dpop/dpop-nonce.js'
|
11
11
|
import { DpopProof } from './dpop/dpop-proof.js'
|
12
|
+
import { InvalidDpopKeyBindingError } from './errors/invalid-dpop-key-binding-error.js'
|
12
13
|
import { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js'
|
13
14
|
import { InvalidTokenError } from './errors/invalid-token-error.js'
|
14
15
|
import { UseDpopNonceError } from './errors/use-dpop-nonce-error.js'
|
15
16
|
import { WWWAuthenticateError } from './errors/www-authenticate-error.js'
|
16
17
|
import { parseAuthorizationHeader } from './lib/util/authorization-header.js'
|
17
|
-
import {
|
18
|
+
import { includedIn } from './lib/util/function.js'
|
19
|
+
import { OAuthHooks } from './oauth-hooks.js'
|
18
20
|
import { ReplayManager } from './replay/replay-manager.js'
|
19
21
|
import { ReplayStoreMemory } from './replay/replay-store-memory.js'
|
20
22
|
import { ReplayStoreRedis } from './replay/replay-store-redis.js'
|
21
23
|
import { ReplayStore } from './replay/replay-store.js'
|
24
|
+
import { AccessTokenPayload } from './signer/access-token-payload.js'
|
22
25
|
import { Signer } from './signer/signer.js'
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
26
|
+
|
27
|
+
export type DecodeTokenHook = OAuthHooks['onDecodeToken']
|
28
|
+
|
29
|
+
export type OAuthVerifierOptions = DpopManagerOptions & {
|
30
|
+
/**
|
31
|
+
* The "issuer" identifier of the OAuth provider, this is the base URL of the
|
32
|
+
* OAuth provider.
|
33
|
+
*/
|
34
|
+
issuer: URL | string
|
35
|
+
|
36
|
+
/**
|
37
|
+
* The keyset used to sign access tokens.
|
38
|
+
*/
|
39
|
+
keyset: Keyset | Iterable<Key | undefined | null | false>
|
40
|
+
|
41
|
+
/**
|
42
|
+
* A redis instance to use for replay protection. If not provided, replay
|
43
|
+
* protection will use memory storage.
|
44
|
+
*/
|
45
|
+
redis?: Redis | RedisOptions | string
|
46
|
+
|
47
|
+
replayStore?: ReplayStore
|
48
|
+
|
49
|
+
onDecodeToken?: DecodeTokenHook
|
50
|
+
}
|
51
|
+
|
52
|
+
export type VerifyTokenPayloadOptions = {
|
53
|
+
/** One of these audience must be included in the token audience(s) */
|
54
|
+
audience?: [string, ...string[]]
|
55
|
+
/** One of these scope must be included in the token scope(s) */
|
56
|
+
scope?: [string, ...string[]]
|
57
|
+
}
|
54
58
|
|
55
59
|
export { DpopNonce, Key, Keyset }
|
56
|
-
export type {
|
60
|
+
export type {
|
61
|
+
AccessTokenPayload,
|
62
|
+
DpopProof,
|
63
|
+
OAuthTokenType,
|
64
|
+
RedisOptions,
|
65
|
+
ReplayStore,
|
66
|
+
}
|
57
67
|
|
58
68
|
export class OAuthVerifier {
|
69
|
+
private readonly onDecodeToken?: DecodeTokenHook
|
70
|
+
|
59
71
|
public readonly issuer: OAuthIssuerIdentifier
|
60
72
|
public readonly keyset: Keyset
|
61
73
|
|
@@ -70,6 +82,7 @@ export class OAuthVerifier {
|
|
70
82
|
replayStore = redis != null
|
71
83
|
? new ReplayStoreRedis({ redis })
|
72
84
|
: new ReplayStoreMemory(),
|
85
|
+
onDecodeToken,
|
73
86
|
|
74
87
|
...rest
|
75
88
|
}: OAuthVerifierOptions) {
|
@@ -118,12 +131,11 @@ export class OAuthVerifier {
|
|
118
131
|
return dpopProof
|
119
132
|
}
|
120
133
|
|
121
|
-
protected async
|
134
|
+
protected async decodeToken(
|
122
135
|
tokenType: OAuthTokenType,
|
123
136
|
token: OAuthAccessToken,
|
124
137
|
dpopProof: null | DpopProof,
|
125
|
-
|
126
|
-
): Promise<VerifyTokenClaimsResult> {
|
138
|
+
): Promise<AccessTokenPayload> {
|
127
139
|
if (!isSignedJwt(token)) {
|
128
140
|
throw new InvalidTokenError(tokenType, `Malformed token`)
|
129
141
|
}
|
@@ -134,22 +146,56 @@ export class OAuthVerifier {
|
|
134
146
|
throw InvalidTokenError.from(err, tokenType)
|
135
147
|
})
|
136
148
|
|
137
|
-
|
138
|
-
token
|
139
|
-
|
149
|
+
if (payload.cnf?.jkt) {
|
150
|
+
// An access token with a cnf.jkt claim must be a DPoP token
|
151
|
+
if (tokenType !== 'DPoP') {
|
152
|
+
throw new InvalidTokenError(
|
153
|
+
'DPoP',
|
154
|
+
`Access token is bound to a DPoP proof, but token type is ${tokenType}`,
|
155
|
+
)
|
156
|
+
}
|
157
|
+
|
158
|
+
// DPoP token type must be used with a DPoP proof
|
159
|
+
if (!dpopProof) {
|
160
|
+
throw new InvalidDpopProofError(`DPoP proof required`)
|
161
|
+
}
|
162
|
+
|
163
|
+
// DPoP proof must be signed with the key that matches the "cnf" claim
|
164
|
+
if (payload.cnf.jkt !== dpopProof.jkt) {
|
165
|
+
throw new InvalidDpopKeyBindingError()
|
166
|
+
}
|
167
|
+
} else {
|
168
|
+
// An access token without a cnf.jkt claim must be a Bearer token
|
169
|
+
if (tokenType !== 'Bearer') {
|
170
|
+
throw new InvalidTokenError(
|
171
|
+
'Bearer',
|
172
|
+
`Bearer token type must be used without a DPoP proof`,
|
173
|
+
)
|
174
|
+
}
|
175
|
+
|
176
|
+
// @NOTE We ignore (but allow) DPoP proofs for Bearer tokens
|
177
|
+
}
|
178
|
+
|
179
|
+
const payloadOverride = await this.onDecodeToken?.call(null, {
|
140
180
|
tokenType,
|
181
|
+
token,
|
141
182
|
payload,
|
142
183
|
dpopProof,
|
143
|
-
|
144
|
-
|
184
|
+
})
|
185
|
+
|
186
|
+
return payloadOverride ?? payload
|
145
187
|
}
|
146
188
|
|
189
|
+
/**
|
190
|
+
* @throws {WWWAuthenticateError}
|
191
|
+
* @throws {InvalidTokenError}
|
192
|
+
*/
|
147
193
|
public async authenticateRequest(
|
148
194
|
httpMethod: string,
|
149
195
|
httpUrl: Readonly<URL>,
|
150
196
|
httpHeaders: Record<string, undefined | string | string[]>,
|
151
|
-
verifyOptions?:
|
152
|
-
): Promise<
|
197
|
+
verifyOptions?: VerifyTokenPayloadOptions,
|
198
|
+
): Promise<AccessTokenPayload> {
|
153
199
|
const [tokenType, token] = parseAuthorizationHeader(
|
154
200
|
httpHeaders['authorization'],
|
155
201
|
)
|
@@ -161,14 +207,11 @@ export class OAuthVerifier {
|
|
161
207
|
token,
|
162
208
|
)
|
163
209
|
|
164
|
-
const
|
165
|
-
tokenType,
|
166
|
-
token,
|
167
|
-
dpopProof,
|
168
|
-
verifyOptions,
|
169
|
-
)
|
210
|
+
const tokenPayload = await this.decodeToken(tokenType, token, dpopProof)
|
170
211
|
|
171
|
-
|
212
|
+
this.verifyTokenPayload(tokenType, tokenPayload, verifyOptions)
|
213
|
+
|
214
|
+
return tokenPayload
|
172
215
|
} catch (err) {
|
173
216
|
if (err instanceof UseDpopNonceError) throw err.toWwwAuthenticateError()
|
174
217
|
if (err instanceof WWWAuthenticateError) throw err
|
@@ -176,4 +219,33 @@ export class OAuthVerifier {
|
|
176
219
|
throw InvalidTokenError.from(err, tokenType)
|
177
220
|
}
|
178
221
|
}
|
222
|
+
|
223
|
+
protected verifyTokenPayload(
|
224
|
+
tokenType: OAuthTokenType,
|
225
|
+
tokenPayload: AccessTokenPayload,
|
226
|
+
options?: VerifyTokenPayloadOptions,
|
227
|
+
): void {
|
228
|
+
if (options?.audience) {
|
229
|
+
const { aud } = tokenPayload
|
230
|
+
const hasMatch =
|
231
|
+
aud != null &&
|
232
|
+
(Array.isArray(aud)
|
233
|
+
? options.audience.some(includedIn, aud)
|
234
|
+
: options.audience.includes(aud))
|
235
|
+
if (!hasMatch) {
|
236
|
+
throw new InvalidTokenError(tokenType, `Invalid audience`)
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
if (options?.scope) {
|
241
|
+
const scopes = tokenPayload.scope?.split(' ')
|
242
|
+
if (!scopes || !options.scope.some(includedIn, scopes)) {
|
243
|
+
throw new InvalidTokenError(tokenType, `Invalid scope`)
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
if (tokenPayload.exp != null && tokenPayload.exp * 1000 <= Date.now()) {
|
248
|
+
throw new InvalidTokenError(tokenType, `Token expired`)
|
249
|
+
}
|
250
|
+
}
|
179
251
|
}
|
@@ -4,7 +4,7 @@ import { clientIdSchema } from '../client/client-id.js'
|
|
4
4
|
import { subSchema } from '../oidc/sub.js'
|
5
5
|
import { tokenIdSchema } from '../token/token-id.js'
|
6
6
|
|
7
|
-
export const
|
7
|
+
export const accessTokenPayloadSchema = jwtPayloadSchema
|
8
8
|
.partial()
|
9
9
|
.extend({
|
10
10
|
// Following are required
|
@@ -22,4 +22,4 @@ export const signedTokenPayloadSchema = jwtPayloadSchema
|
|
22
22
|
})
|
23
23
|
.passthrough()
|
24
24
|
|
25
|
-
export type
|
25
|
+
export type AccessTokenPayload = z.infer<typeof accessTokenPayloadSchema>
|
package/src/signer/signer.ts
CHANGED
@@ -9,11 +9,11 @@ import {
|
|
9
9
|
import { EPHEMERAL_SESSION_MAX_AGE } from '../constants.js'
|
10
10
|
import { dateToEpoch } from '../lib/util/date.js'
|
11
11
|
import { OmitKey, RequiredKey } from '../lib/util/type.js'
|
12
|
-
import { ApiTokenPayload, apiTokenPayloadSchema } from './api-token-payload.js'
|
13
12
|
import {
|
14
|
-
|
15
|
-
|
16
|
-
} from './
|
13
|
+
AccessTokenPayload,
|
14
|
+
accessTokenPayloadSchema,
|
15
|
+
} from './access-token-payload.js'
|
16
|
+
import { ApiTokenPayload, apiTokenPayloadSchema } from './api-token-payload.js'
|
17
17
|
|
18
18
|
export type SignPayload = JwtPayload & { iss?: never }
|
19
19
|
|
@@ -49,7 +49,7 @@ export class Signer {
|
|
49
49
|
}
|
50
50
|
|
51
51
|
async createAccessToken(
|
52
|
-
payload: OmitKey<
|
52
|
+
payload: OmitKey<AccessTokenPayload, 'iss'>,
|
53
53
|
): Promise<SignedJwt> {
|
54
54
|
return this.sign(
|
55
55
|
{
|
@@ -68,8 +68,8 @@ export class Signer {
|
|
68
68
|
const result = await this.verify<C>(token, { ...options, typ: 'at+jwt' })
|
69
69
|
return {
|
70
70
|
protectedHeader: result.protectedHeader,
|
71
|
-
payload:
|
72
|
-
|
71
|
+
payload: accessTokenPayloadSchema.parse(result.payload) as RequiredKey<
|
72
|
+
AccessTokenPayload,
|
73
73
|
C
|
74
74
|
>,
|
75
75
|
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { OAuthScope } from '@atproto/oauth-types'
|
2
|
+
import { ClientId } from '../client/client-id.js'
|
3
|
+
import { TokenId } from './token-id.js'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* The access token claims that will be set by the {@link TokenManager} and that
|
7
|
+
* will be passed to the "onCreateToken" hook.
|
8
|
+
*
|
9
|
+
* @note "iss" is missing here because it cannot be altered and will always be
|
10
|
+
* set to the Authorization Server's identifier.
|
11
|
+
*/
|
12
|
+
export type TokenClaims = {
|
13
|
+
jti: TokenId
|
14
|
+
sub: string
|
15
|
+
iat: number
|
16
|
+
exp: number
|
17
|
+
aud: string | [string, ...string[]]
|
18
|
+
cnf?: { jkt: string }
|
19
|
+
scope?: OAuthScope
|
20
|
+
client_id: ClientId
|
21
|
+
}
|