@atproto/oauth-provider 0.8.0 → 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.
- package/CHANGELOG.md +49 -0
- package/dist/client/client-auth.d.ts +48 -3
- package/dist/client/client-auth.d.ts.map +1 -1
- package/dist/client/client-auth.js +0 -31
- package/dist/client/client-auth.js.map +1 -1
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +19 -19
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client.d.ts +14 -17
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +115 -73
- package/dist/client/client.js.map +1 -1
- package/dist/constants.d.ts +7 -6
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +8 -7
- package/dist/constants.js.map +1 -1
- package/dist/metadata/build-metadata.js +1 -1
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-provider.d.ts +20 -16
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +268 -122
- package/dist/oauth-provider.js.map +1 -1
- package/dist/replay/replay-manager.d.ts +1 -1
- package/dist/replay/replay-manager.d.ts.map +1 -1
- package/dist/replay/replay-manager.js +5 -2
- package/dist/replay/replay-manager.js.map +1 -1
- package/dist/request/request-data.d.ts +3 -2
- package/dist/request/request-data.d.ts.map +1 -1
- package/dist/request/request-data.js.map +1 -1
- package/dist/request/request-info.d.ts +1 -1
- package/dist/request/request-info.d.ts.map +1 -1
- package/dist/request/request-manager.d.ts +73 -9
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +34 -61
- package/dist/request/request-manager.js.map +1 -1
- package/dist/request/request-store.d.ts +6 -2
- package/dist/request/request-store.d.ts.map +1 -1
- package/dist/request/request-store.js +6 -6
- package/dist/request/request-store.js.map +1 -1
- package/dist/router/create-api-middleware.js +1 -1
- package/dist/router/create-api-middleware.js.map +1 -1
- package/dist/router/create-oauth-middleware.d.ts.map +1 -1
- package/dist/router/create-oauth-middleware.js +2 -1
- package/dist/router/create-oauth-middleware.js.map +1 -1
- package/dist/token/token-data.d.ts +2 -2
- package/dist/token/token-data.d.ts.map +1 -1
- package/dist/token/token-manager.d.ts +10 -10
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +64 -201
- package/dist/token/token-manager.js.map +1 -1
- package/package.json +8 -7
- package/src/client/client-auth.ts +52 -33
- package/src/client/client-manager.ts +26 -27
- package/src/client/client.ts +153 -89
- package/src/constants.ts +9 -7
- package/src/metadata/build-metadata.ts +2 -2
- package/src/oauth-provider.ts +391 -191
- package/src/replay/replay-manager.ts +10 -6
- package/src/request/request-data.ts +12 -2
- package/src/request/request-info.ts +1 -1
- package/src/request/request-manager.ts +45 -85
- package/src/request/request-store.ts +11 -8
- package/src/router/create-api-middleware.ts +1 -1
- package/src/router/create-oauth-middleware.ts +7 -1
- package/src/token/token-data.ts +2 -2
- package/src/token/token-manager.ts +112 -312
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/request/request-store-memory.d.ts +0 -16
- package/dist/request/request-store-memory.d.ts.map +0 -1
- package/dist/request/request-store-memory.js +0 -31
- package/dist/request/request-store-memory.js.map +0 -1
- package/dist/request/request-store-redis.d.ts +0 -24
- package/dist/request/request-store-redis.d.ts.map +0 -1
- package/dist/request/request-store-redis.js +0 -58
- package/dist/request/request-store-redis.js.map +0 -1
- package/src/request/request-store-memory.ts +0 -39
- package/src/request/request-store-redis.ts +0 -71
@@ -311,13 +311,7 @@ export class ClientManager {
|
|
311
311
|
)
|
312
312
|
}
|
313
313
|
|
314
|
-
|
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
|
-
|
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
|
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
|
|
package/src/client/client.ts
CHANGED
@@ -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 {
|
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:
|
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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
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
|
162
|
+
public async authenticate(
|
114
163
|
input: OAuthClientCredentials,
|
115
164
|
checks: {
|
116
|
-
|
165
|
+
authorizationServerIdentifier: string
|
117
166
|
},
|
118
|
-
): Promise<{
|
119
|
-
|
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
|
-
|
127
|
-
return { clientAuth }
|
171
|
+
return { method: 'none' }
|
128
172
|
}
|
129
173
|
|
130
174
|
if (method === 'private_key_jwt') {
|
131
|
-
if (!('
|
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
|
-
`
|
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
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
163
|
-
method:
|
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
|
42
|
-
export const
|
41
|
+
/** 1 week */
|
42
|
+
export const PUBLIC_CLIENT_SESSION_LIFETIME = 1 * WEEK
|
43
43
|
|
44
44
|
/** 2 days */
|
45
|
-
export const
|
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
|
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
|
-
|
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
|
25
|
+
return oauthAuthorizationServerMetadataValidator.parse({
|
26
26
|
issuer,
|
27
27
|
|
28
28
|
scopes_supported: [
|