@udibo/oauth2 0.34.0 → 0.36.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/dist/adapters/hono/bff/auth-request-store.d.ts +4 -1
- package/dist/adapters/hono/bff/auth-request-store.js +4 -1
- package/dist/cli/idp/server.js +2 -1
- package/dist/server/authorization-server.d.ts +32 -2
- package/dist/server/authorization-server.js +21 -2
- package/dist/server/grants/authorization-code.d.ts +56 -5
- package/dist/server/grants/authorization-code.js +97 -21
- package/dist/server/services/token.d.ts +58 -9
- package/dist/testing/contract/code-exchange.d.ts +100 -0
- package/dist/testing/contract/code-exchange.js +309 -0
- package/dist/testing/contract/mod.d.ts +6 -0
- package/dist/testing/contract/mod.js +6 -0
- package/dist/testing/contract/token-rotation.js +13 -0
- package/dist/testing/contract/token.d.ts +8 -0
- package/dist/testing/contract/token.js +121 -19
- package/dist/testing/server.js +3 -2
- package/dist/testing/services.d.ts +26 -6
- package/dist/testing/services.js +55 -10
- package/package.json +1 -1
package/dist/testing/services.js
CHANGED
|
@@ -208,11 +208,19 @@ export class MemoryTokenService {
|
|
|
208
208
|
#tokensByCodeHash = new Map();
|
|
209
209
|
#revokedByRefreshHash = new Map();
|
|
210
210
|
#revokedFamilies = new Set();
|
|
211
|
+
/**
|
|
212
|
+
* Claims a code from the paired `authorizationCodeService` and saves the
|
|
213
|
+
* token issued from it in one step, or does neither, resolving `undefined`
|
|
214
|
+
* when the code was already claimed. Present only when the service was
|
|
215
|
+
* constructed with `authorizationCodeService`, so the grant falls back to
|
|
216
|
+
* two calls for a token service that has no code store to claim from.
|
|
217
|
+
*/
|
|
218
|
+
exchangeCode;
|
|
211
219
|
/**
|
|
212
220
|
* Creates an empty token store.
|
|
213
221
|
*
|
|
214
|
-
* @param options Backing client/user services
|
|
215
|
-
* lifetimes
|
|
222
|
+
* @param options Backing client/user services, optional default token
|
|
223
|
+
* lifetimes, and the code store that enables `exchangeCode`.
|
|
216
224
|
*/
|
|
217
225
|
constructor(options) {
|
|
218
226
|
this.#clientService = options.clientService;
|
|
@@ -221,6 +229,21 @@ export class MemoryTokenService {
|
|
|
221
229
|
this.refreshTokenLifetime =
|
|
222
230
|
options.refreshTokenLifetime ?? 14 * 24 * 60 * 60;
|
|
223
231
|
this.refreshTokenMaxLifetime = options.refreshTokenMaxLifetime;
|
|
232
|
+
const codes = options.authorizationCodeService;
|
|
233
|
+
if (codes) {
|
|
234
|
+
this.exchangeCode = (authorizationCode, token) => this.#exchangeCode(codes, authorizationCode, token);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async #exchangeCode(codes, authorizationCode, token) {
|
|
238
|
+
const codeHash = await sha256Hash(authorizationCode.code);
|
|
239
|
+
const stored = await this.#prepare({
|
|
240
|
+
...token,
|
|
241
|
+
code: authorizationCode.code,
|
|
242
|
+
});
|
|
243
|
+
if (!claimStoredCode(codes, codeHash))
|
|
244
|
+
return undefined;
|
|
245
|
+
this.#store(stored);
|
|
246
|
+
return token;
|
|
224
247
|
}
|
|
225
248
|
/**
|
|
226
249
|
* Accepts the requested scope verbatim; the stub performs no scope
|
|
@@ -344,10 +367,16 @@ export class MemoryTokenService {
|
|
|
344
367
|
if (stored.codeHash) {
|
|
345
368
|
let bucket = this.#tokensByCodeHash.get(stored.codeHash);
|
|
346
369
|
if (!bucket) {
|
|
347
|
-
bucket =
|
|
370
|
+
bucket = {
|
|
371
|
+
clientId: stored.clientId,
|
|
372
|
+
accessTokenHashes: new Set(),
|
|
373
|
+
familyIds: new Set(),
|
|
374
|
+
};
|
|
348
375
|
this.#tokensByCodeHash.set(stored.codeHash, bucket);
|
|
349
376
|
}
|
|
350
|
-
bucket.add(stored.accessTokenHash);
|
|
377
|
+
bucket.accessTokenHashes.add(stored.accessTokenHash);
|
|
378
|
+
if (stored.familyId)
|
|
379
|
+
bucket.familyIds.add(stored.familyId);
|
|
351
380
|
}
|
|
352
381
|
if (stored.refreshTokenHash) {
|
|
353
382
|
if (stored.familyId && this.#revokedFamilies.has(stored.familyId)) {
|
|
@@ -460,6 +489,9 @@ export class MemoryTokenService {
|
|
|
460
489
|
* live token was removed.
|
|
461
490
|
*/
|
|
462
491
|
revokeFamily(familyId) {
|
|
492
|
+
return Promise.resolve(this.#revokeFamily(familyId));
|
|
493
|
+
}
|
|
494
|
+
#revokeFamily(familyId) {
|
|
463
495
|
this.#revokedFamilies.add(familyId);
|
|
464
496
|
let revoked = false;
|
|
465
497
|
for (const [hash, stored] of this.#tokensByRefreshHash) {
|
|
@@ -476,18 +508,27 @@ export class MemoryTokenService {
|
|
|
476
508
|
revoked = true;
|
|
477
509
|
}
|
|
478
510
|
}
|
|
479
|
-
return
|
|
511
|
+
return revoked;
|
|
480
512
|
}
|
|
481
513
|
/**
|
|
482
514
|
* Removes every token issued from the given authorization code, used when a
|
|
483
|
-
* code is replayed
|
|
515
|
+
* code is replayed, and revokes the rotation families of those tokens as
|
|
516
|
+
* {@linkcode revokeFamily} does, so their rotated successors end with them.
|
|
517
|
+
* Resolves `true` when tokens were issued from the code.
|
|
518
|
+
* Removes nothing and resolves `false` when `client` is given and is not the
|
|
519
|
+
* client the code's tokens were issued to.
|
|
484
520
|
*/
|
|
485
|
-
async revokeCode(code) {
|
|
521
|
+
async revokeCode(code, client) {
|
|
486
522
|
const codeHash = await sha256Hash(code);
|
|
487
|
-
const
|
|
488
|
-
if (!
|
|
523
|
+
const bucket = this.#tokensByCodeHash.get(codeHash);
|
|
524
|
+
if (!bucket || bucket.accessTokenHashes.size === 0)
|
|
525
|
+
return false;
|
|
526
|
+
if (client && client.id.toString() !== bucket.clientId.toString()) {
|
|
489
527
|
return false;
|
|
490
|
-
|
|
528
|
+
}
|
|
529
|
+
for (const familyId of bucket.familyIds)
|
|
530
|
+
this.#revokeFamily(familyId);
|
|
531
|
+
for (const accessTokenHash of bucket.accessTokenHashes) {
|
|
491
532
|
const stored = this.#tokensByAccessHash.get(accessTokenHash);
|
|
492
533
|
if (stored) {
|
|
493
534
|
this.#tokensByAccessHash.delete(accessTokenHash);
|
|
@@ -500,6 +541,7 @@ export class MemoryTokenService {
|
|
|
500
541
|
return true;
|
|
501
542
|
}
|
|
502
543
|
}
|
|
544
|
+
let claimStoredCode;
|
|
503
545
|
/** In-memory `AuthorizationCodeServiceInterface` implementation. */
|
|
504
546
|
export class MemoryAuthorizationCodeService {
|
|
505
547
|
/** Authorization-code lifetime in seconds applied by {@linkcode expiresAt}. */
|
|
@@ -507,6 +549,9 @@ export class MemoryAuthorizationCodeService {
|
|
|
507
549
|
#clientService;
|
|
508
550
|
#userService;
|
|
509
551
|
#codes = new Map();
|
|
552
|
+
static {
|
|
553
|
+
claimStoredCode = (service, codeHash) => service.#codes.delete(codeHash);
|
|
554
|
+
}
|
|
510
555
|
/**
|
|
511
556
|
* Creates an empty authorization-code store.
|
|
512
557
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@udibo/oauth2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "A flexible OAuth2 toolkit: framework-agnostic authorization & resource servers, a typed client, Hono adapters (including a backend-for-frontend), and React bindings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"authorization-server",
|