@memberjunction/auth-providers 5.32.0 → 5.34.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +25 -0
- package/dist/AuthProviderFactory.d.ts +10 -0
- package/dist/AuthProviderFactory.d.ts.map +1 -1
- package/dist/AuthProviderFactory.js +25 -13
- package/dist/AuthProviderFactory.js.map +1 -1
- package/package.json +3 -3
- package/src/AuthProviderFactory.ts +26 -14
- package/src/__tests__/AuthProviderFactory.test.ts +105 -0
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @memberjunction/auth-providers
|
|
2
2
|
|
|
3
|
+
## 5.34.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [003317f]
|
|
8
|
+
- Updated dependencies [cfffb6d]
|
|
9
|
+
- Updated dependencies [e999e0d]
|
|
10
|
+
- Updated dependencies [389d356]
|
|
11
|
+
- Updated dependencies [ae5cfbd]
|
|
12
|
+
- Updated dependencies [6d8ee1a]
|
|
13
|
+
- Updated dependencies [72cb92e]
|
|
14
|
+
- @memberjunction/core@5.34.0
|
|
15
|
+
- @memberjunction/global@5.34.0
|
|
16
|
+
|
|
17
|
+
## 5.33.0
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [95eb27e]
|
|
22
|
+
- Updated dependencies [74b0be0]
|
|
23
|
+
- Updated dependencies [5cc5326]
|
|
24
|
+
- Updated dependencies [7e4957d]
|
|
25
|
+
- @memberjunction/core@5.33.0
|
|
26
|
+
- @memberjunction/global@5.33.0
|
|
27
|
+
|
|
3
28
|
## 5.32.0
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
|
@@ -12,7 +12,17 @@ import './providers/GoogleProvider.js';
|
|
|
12
12
|
*/
|
|
13
13
|
export declare class AuthProviderFactory extends BaseSingleton<AuthProviderFactory> {
|
|
14
14
|
private providers;
|
|
15
|
+
/**
|
|
16
|
+
* Cache of resolved issuer → provider mappings. Bounded LRU(50) — prior
|
|
17
|
+
* unbounded `Map` was a low-effort DoS vector: a misconfigured/malicious
|
|
18
|
+
* client supplying arbitrary issuer URLs would walk the map up indefinitely.
|
|
19
|
+
* In production there should never be more than a handful of legitimate
|
|
20
|
+
* issuers. See audit R2-C4.
|
|
21
|
+
*/
|
|
15
22
|
private issuerCache;
|
|
23
|
+
/**
|
|
24
|
+
* Cache of issuer → all matching providers. Same LRU bound as `issuerCache`.
|
|
25
|
+
*/
|
|
16
26
|
private issuerMultiCache;
|
|
17
27
|
constructor();
|
|
18
28
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProviderFactory.d.ts","sourceRoot":"","sources":["../src/AuthProviderFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAY,aAAa,
|
|
1
|
+
{"version":3,"file":"AuthProviderFactory.d.ts","sourceRoot":"","sources":["../src/AuthProviderFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAY,aAAa,EAAc,MAAM,wBAAwB,CAAC;AAG7E,OAAO,8BAA8B,CAAC;AACtC,OAAO,6BAA6B,CAAC;AACrC,OAAO,6BAA6B,CAAC;AACrC,OAAO,gCAAgC,CAAC;AACxC,OAAO,+BAA+B,CAAC;AAEvC;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,aAAa,CAAC,mBAAmB,CAAC;IACzE,OAAO,CAAC,SAAS,CAAyC;IAC1D;;;;;;OAMG;IACH,OAAO,CAAC,WAAW,CAA6F;IAChH;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAiG;;IAMzH;;OAEG;IACH,WAAkB,QAAQ,IAAI,mBAAmB,CAEhD;IAED;;;OAGG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,aAAa;IAsBhE;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAcvC;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAmBtD;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE;IAqB/C;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIlD;;OAEG;IACH,eAAe,IAAI,aAAa,EAAE;IAIlC;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,MAAM,CAAC,0BAA0B,IAAI,MAAM,EAAE;IAW7C;;OAEG;IACH,MAAM,CAAC,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CASvD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseAuthProvider } from './BaseAuthProvider.js';
|
|
2
|
-
import { MJGlobal, BaseSingleton } from '@memberjunction/global';
|
|
2
|
+
import { MJGlobal, BaseSingleton, MJLruCache } from '@memberjunction/global';
|
|
3
3
|
// Import providers to ensure they're registered
|
|
4
4
|
import './providers/Auth0Provider.js';
|
|
5
5
|
import './providers/MSALProvider.js';
|
|
@@ -14,8 +14,18 @@ export class AuthProviderFactory extends BaseSingleton {
|
|
|
14
14
|
constructor() {
|
|
15
15
|
super();
|
|
16
16
|
this.providers = new Map();
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Cache of resolved issuer → provider mappings. Bounded LRU(50) — prior
|
|
19
|
+
* unbounded `Map` was a low-effort DoS vector: a misconfigured/malicious
|
|
20
|
+
* client supplying arbitrary issuer URLs would walk the map up indefinitely.
|
|
21
|
+
* In production there should never be more than a handful of legitimate
|
|
22
|
+
* issuers. See audit R2-C4.
|
|
23
|
+
*/
|
|
24
|
+
this.issuerCache = new MJLruCache({ maxSize: 50 });
|
|
25
|
+
/**
|
|
26
|
+
* Cache of issuer → all matching providers. Same LRU bound as `issuerCache`.
|
|
27
|
+
*/
|
|
28
|
+
this.issuerMultiCache = new MJLruCache({ maxSize: 50 });
|
|
19
29
|
}
|
|
20
30
|
/**
|
|
21
31
|
* Gets the singleton instance of the factory
|
|
@@ -52,8 +62,8 @@ export class AuthProviderFactory extends BaseSingleton {
|
|
|
52
62
|
}
|
|
53
63
|
this.providers.set(provider.name, provider);
|
|
54
64
|
// Clear issuer caches when registering new provider
|
|
55
|
-
this.issuerCache.
|
|
56
|
-
this.issuerMultiCache.
|
|
65
|
+
this.issuerCache.Clear();
|
|
66
|
+
this.issuerMultiCache.Clear();
|
|
57
67
|
console.log(`Registered auth provider: ${provider.name} with issuer: ${provider.issuer}`);
|
|
58
68
|
}
|
|
59
69
|
/**
|
|
@@ -61,14 +71,15 @@ export class AuthProviderFactory extends BaseSingleton {
|
|
|
61
71
|
*/
|
|
62
72
|
getByIssuer(issuer) {
|
|
63
73
|
// Check cache first
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
const cached = this.issuerCache.Get(issuer);
|
|
75
|
+
if (cached) {
|
|
76
|
+
return cached;
|
|
66
77
|
}
|
|
67
78
|
// Search through providers
|
|
68
79
|
for (const provider of this.providers.values()) {
|
|
69
80
|
if (provider.matchesIssuer(issuer)) {
|
|
70
81
|
// Cache for future lookups
|
|
71
|
-
this.issuerCache.
|
|
82
|
+
this.issuerCache.Set(issuer, provider);
|
|
72
83
|
return provider;
|
|
73
84
|
}
|
|
74
85
|
}
|
|
@@ -83,8 +94,9 @@ export class AuthProviderFactory extends BaseSingleton {
|
|
|
83
94
|
*/
|
|
84
95
|
getAllByIssuer(issuer) {
|
|
85
96
|
// Check multi-provider cache first
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
const cached = this.issuerMultiCache.Get(issuer);
|
|
98
|
+
if (cached) {
|
|
99
|
+
return cached;
|
|
88
100
|
}
|
|
89
101
|
const matches = [];
|
|
90
102
|
for (const provider of this.providers.values()) {
|
|
@@ -93,7 +105,7 @@ export class AuthProviderFactory extends BaseSingleton {
|
|
|
93
105
|
}
|
|
94
106
|
}
|
|
95
107
|
if (matches.length > 0) {
|
|
96
|
-
this.issuerMultiCache.
|
|
108
|
+
this.issuerMultiCache.Set(issuer, matches);
|
|
97
109
|
}
|
|
98
110
|
return matches;
|
|
99
111
|
}
|
|
@@ -120,8 +132,8 @@ export class AuthProviderFactory extends BaseSingleton {
|
|
|
120
132
|
*/
|
|
121
133
|
clear() {
|
|
122
134
|
this.providers.clear();
|
|
123
|
-
this.issuerCache.
|
|
124
|
-
this.issuerMultiCache.
|
|
135
|
+
this.issuerCache.Clear();
|
|
136
|
+
this.issuerMultiCache.Clear();
|
|
125
137
|
}
|
|
126
138
|
/**
|
|
127
139
|
* Gets all registered provider types from the ClassFactory
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProviderFactory.js","sourceRoot":"","sources":["../src/AuthProviderFactory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"AuthProviderFactory.js","sourceRoot":"","sources":["../src/AuthProviderFactory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAE7E,gDAAgD;AAChD,OAAO,8BAA8B,CAAC;AACtC,OAAO,6BAA6B,CAAC;AACrC,OAAO,6BAA6B,CAAC;AACrC,OAAO,gCAAgC,CAAC;AACxC,OAAO,+BAA+B,CAAC;AAEvC;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,aAAkC;IAezE;QACE,KAAK,EAAE,CAAC;QAfF,cAAS,GAA+B,IAAI,GAAG,EAAE,CAAC;QAC1D;;;;;;WAMG;QACK,gBAAW,GAAsC,IAAI,UAAU,CAAwB,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QAChH;;WAEG;QACK,qBAAgB,GAAwC,IAAI,UAAU,CAA0B,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IAIzH,CAAC;IAED;;OAEG;IACI,MAAM,KAAK,QAAQ;QACxB,OAAO,mBAAmB,CAAC,WAAW,EAAuB,CAAC;IAChE,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,cAAc,CAAC,MAA0B;QAC9C,IAAI,CAAC;YACH,4DAA4D;YAC5D,0EAA0E;YAC1E,0EAA0E;YAC1E,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,cAAc,CAC5D,gBAAgB,EAChB,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,EACzB,MAAM,CACP,CAAC;YAEF,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YACrE,CAAC;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,IAAI,KAAK,CAAC,sDAAsD,MAAM,CAAC,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;QACpG,CAAC;IACH,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,QAAuB;QAC9B,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,uCAAuC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAE5C,oDAAoD;QACpD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAE9B,OAAO,CAAC,GAAG,CAAC,6BAA6B,QAAQ,CAAC,IAAI,iBAAiB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,MAAc;QACxB,oBAAoB;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,2BAA2B;QAC3B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;YAC/C,IAAI,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnC,2BAA2B;gBAC3B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;gBACvC,OAAO,QAAQ,CAAC;YAClB,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;OAMG;IACH,cAAc,CAAC,MAAc;QAC3B,mCAAmC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,OAAO,GAAoB,EAAE,CAAC;QACpC,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;YAC/C,IAAI,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,0BAA0B;QAC/B,+DAA+D;QAC/D,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;QAC3F,0DAA0D;QAC1D,MAAM,aAAa,GAAG,aAAa;aAChC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC;aACnB,MAAM,CAAC,CAAC,GAAG,EAAiB,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,CAAC,CAAC;QACrE,+BAA+B;QAC/B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,wBAAwB,CAAC,IAAY;QAC1C,IAAI,CAAC;YACH,qDAAqD;YACrD,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,eAAe,CAAC,gBAAgB,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YAC1G,OAAO,YAAY,KAAK,IAAI,IAAI,YAAY,KAAK,SAAS,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/auth-providers",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.34.0",
|
|
4
4
|
"description": "Authentication provider interfaces, base classes, and implementations for MemberJunction",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"test:watch": "vitest"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@memberjunction/core": "5.
|
|
15
|
-
"@memberjunction/global": "5.
|
|
14
|
+
"@memberjunction/core": "5.34.0",
|
|
15
|
+
"@memberjunction/global": "5.34.0",
|
|
16
16
|
"graphql": "^16.12.0",
|
|
17
17
|
"jsonwebtoken": "9.0.3",
|
|
18
18
|
"jwks-rsa": "^3.2.2"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AuthProviderConfig } from '@memberjunction/core';
|
|
2
2
|
import { IAuthProvider } from './IAuthProvider.js';
|
|
3
3
|
import { BaseAuthProvider } from './BaseAuthProvider.js';
|
|
4
|
-
import { MJGlobal, BaseSingleton } from '@memberjunction/global';
|
|
4
|
+
import { MJGlobal, BaseSingleton, MJLruCache } from '@memberjunction/global';
|
|
5
5
|
|
|
6
6
|
// Import providers to ensure they're registered
|
|
7
7
|
import './providers/Auth0Provider.js';
|
|
@@ -16,8 +16,18 @@ import './providers/GoogleProvider.js';
|
|
|
16
16
|
*/
|
|
17
17
|
export class AuthProviderFactory extends BaseSingleton<AuthProviderFactory> {
|
|
18
18
|
private providers: Map<string, IAuthProvider> = new Map();
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Cache of resolved issuer → provider mappings. Bounded LRU(50) — prior
|
|
21
|
+
* unbounded `Map` was a low-effort DoS vector: a misconfigured/malicious
|
|
22
|
+
* client supplying arbitrary issuer URLs would walk the map up indefinitely.
|
|
23
|
+
* In production there should never be more than a handful of legitimate
|
|
24
|
+
* issuers. See audit R2-C4.
|
|
25
|
+
*/
|
|
26
|
+
private issuerCache: MJLruCache<string, IAuthProvider> = new MJLruCache<string, IAuthProvider>({ maxSize: 50 });
|
|
27
|
+
/**
|
|
28
|
+
* Cache of issuer → all matching providers. Same LRU bound as `issuerCache`.
|
|
29
|
+
*/
|
|
30
|
+
private issuerMultiCache: MJLruCache<string, IAuthProvider[]> = new MJLruCache<string, IAuthProvider[]>({ maxSize: 50 });
|
|
21
31
|
|
|
22
32
|
public constructor() {
|
|
23
33
|
super();
|
|
@@ -65,10 +75,10 @@ export class AuthProviderFactory extends BaseSingleton<AuthProviderFactory> {
|
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
this.providers.set(provider.name, provider);
|
|
68
|
-
|
|
78
|
+
|
|
69
79
|
// Clear issuer caches when registering new provider
|
|
70
|
-
this.issuerCache.
|
|
71
|
-
this.issuerMultiCache.
|
|
80
|
+
this.issuerCache.Clear();
|
|
81
|
+
this.issuerMultiCache.Clear();
|
|
72
82
|
|
|
73
83
|
console.log(`Registered auth provider: ${provider.name} with issuer: ${provider.issuer}`);
|
|
74
84
|
}
|
|
@@ -78,15 +88,16 @@ export class AuthProviderFactory extends BaseSingleton<AuthProviderFactory> {
|
|
|
78
88
|
*/
|
|
79
89
|
getByIssuer(issuer: string): IAuthProvider | undefined {
|
|
80
90
|
// Check cache first
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
const cached = this.issuerCache.Get(issuer);
|
|
92
|
+
if (cached) {
|
|
93
|
+
return cached;
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
// Search through providers
|
|
86
97
|
for (const provider of this.providers.values()) {
|
|
87
98
|
if (provider.matchesIssuer(issuer)) {
|
|
88
99
|
// Cache for future lookups
|
|
89
|
-
this.issuerCache.
|
|
100
|
+
this.issuerCache.Set(issuer, provider);
|
|
90
101
|
return provider;
|
|
91
102
|
}
|
|
92
103
|
}
|
|
@@ -103,8 +114,9 @@ export class AuthProviderFactory extends BaseSingleton<AuthProviderFactory> {
|
|
|
103
114
|
*/
|
|
104
115
|
getAllByIssuer(issuer: string): IAuthProvider[] {
|
|
105
116
|
// Check multi-provider cache first
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
const cached = this.issuerMultiCache.Get(issuer);
|
|
118
|
+
if (cached) {
|
|
119
|
+
return cached;
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
const matches: IAuthProvider[] = [];
|
|
@@ -115,7 +127,7 @@ export class AuthProviderFactory extends BaseSingleton<AuthProviderFactory> {
|
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
if (matches.length > 0) {
|
|
118
|
-
this.issuerMultiCache.
|
|
130
|
+
this.issuerMultiCache.Set(issuer, matches);
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
return matches;
|
|
@@ -147,8 +159,8 @@ export class AuthProviderFactory extends BaseSingleton<AuthProviderFactory> {
|
|
|
147
159
|
*/
|
|
148
160
|
clear(): void {
|
|
149
161
|
this.providers.clear();
|
|
150
|
-
this.issuerCache.
|
|
151
|
-
this.issuerMultiCache.
|
|
162
|
+
this.issuerCache.Clear();
|
|
163
|
+
this.issuerMultiCache.Clear();
|
|
152
164
|
}
|
|
153
165
|
|
|
154
166
|
/**
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthProviderFactory cache-bound tests (memory-leak fix R2-C4).
|
|
3
|
+
*
|
|
4
|
+
* The pre-fix `issuerCache` and `issuerMultiCache` were unbounded `Map`s keyed
|
|
5
|
+
* by JWT `iss` claims — a misconfigured or malicious client supplying arbitrary
|
|
6
|
+
* issuer URLs would walk the maps up indefinitely. Both are now `MJLruCache`
|
|
7
|
+
* with `maxSize: 50`. These tests verify:
|
|
8
|
+
* - Lookups still hit the cache after the first miss.
|
|
9
|
+
* - LRU eviction kicks in past `maxSize` (no unbounded growth).
|
|
10
|
+
* - `register()` still clears both caches.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
14
|
+
import { AuthProviderFactory } from '../AuthProviderFactory';
|
|
15
|
+
import { IAuthProvider } from '../IAuthProvider';
|
|
16
|
+
|
|
17
|
+
function makeProvider(name: string, issuerSubstr: string): IAuthProvider {
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
issuer: issuerSubstr,
|
|
21
|
+
validateConfig: () => true,
|
|
22
|
+
matchesIssuer: (iss: string) => iss.includes(issuerSubstr),
|
|
23
|
+
getJwksUri: () => 'https://example.com/.well-known/jwks.json',
|
|
24
|
+
getAlgorithm: () => 'RS256',
|
|
25
|
+
getAudience: () => 'aud',
|
|
26
|
+
getRequiredScopes: () => [],
|
|
27
|
+
validateToken: vi.fn(),
|
|
28
|
+
extractUserInfo: vi.fn(),
|
|
29
|
+
} as unknown as IAuthProvider;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('AuthProviderFactory cache (memory-leak fix R2-C4)', () => {
|
|
33
|
+
let factory: AuthProviderFactory;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
factory = AuthProviderFactory.Instance;
|
|
37
|
+
factory.clear();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('caches getByIssuer results so a hot lookup returns from cache', () => {
|
|
41
|
+
const provider = makeProvider('p1', 'auth0.com/foo');
|
|
42
|
+
factory.register(provider);
|
|
43
|
+
|
|
44
|
+
const matchSpy = vi.spyOn(provider, 'matchesIssuer');
|
|
45
|
+
// First call — cache miss, walks providers
|
|
46
|
+
expect(factory.getByIssuer('https://auth0.com/foo')).toBe(provider);
|
|
47
|
+
expect(matchSpy).toHaveBeenCalledTimes(1);
|
|
48
|
+
// Second call — cache hit, no further matchesIssuer calls
|
|
49
|
+
expect(factory.getByIssuer('https://auth0.com/foo')).toBe(provider);
|
|
50
|
+
expect(matchSpy).toHaveBeenCalledTimes(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('caches getAllByIssuer results similarly', () => {
|
|
54
|
+
const p1 = makeProvider('p1', 'shared.example.com');
|
|
55
|
+
const p2 = makeProvider('p2', 'shared.example.com');
|
|
56
|
+
factory.register(p1);
|
|
57
|
+
factory.register(p2);
|
|
58
|
+
|
|
59
|
+
const a = factory.getAllByIssuer('https://shared.example.com');
|
|
60
|
+
const b = factory.getAllByIssuer('https://shared.example.com');
|
|
61
|
+
expect(a).toEqual([p1, p2]);
|
|
62
|
+
expect(b).toBe(a); // cached array, same reference
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('does NOT grow without bound when arbitrary issuers are queried', () => {
|
|
66
|
+
const provider = makeProvider('p1', 'auth0.com');
|
|
67
|
+
factory.register(provider);
|
|
68
|
+
|
|
69
|
+
// Walk past the LRU cap with distinct issuer keys (a malicious or
|
|
70
|
+
// misconfigured client could do this). Cache must stay bounded.
|
|
71
|
+
for (let i = 0; i < 200; i++) {
|
|
72
|
+
factory.getByIssuer(`https://auth0.com/tenant-${i}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Direct field inspection — verify the LRU enforced its cap.
|
|
76
|
+
const issuerCache = (factory as unknown as { issuerCache: { Size: number; MaxSize: number } }).issuerCache;
|
|
77
|
+
expect(issuerCache.Size).toBeLessThanOrEqual(issuerCache.MaxSize);
|
|
78
|
+
expect(issuerCache.MaxSize).toBe(50);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('clears both caches on register()', () => {
|
|
82
|
+
const provider = makeProvider('p1', 'auth0.com');
|
|
83
|
+
factory.register(provider);
|
|
84
|
+
factory.getByIssuer('https://auth0.com/foo'); // populate cache
|
|
85
|
+
|
|
86
|
+
const issuerCache = (factory as unknown as { issuerCache: { Size: number } }).issuerCache;
|
|
87
|
+
expect(issuerCache.Size).toBe(1);
|
|
88
|
+
|
|
89
|
+
factory.register(makeProvider('p2', 'okta.com'));
|
|
90
|
+
expect(issuerCache.Size).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('clears both caches on clear()', () => {
|
|
94
|
+
factory.register(makeProvider('p1', 'auth0.com'));
|
|
95
|
+
factory.getByIssuer('https://auth0.com/foo');
|
|
96
|
+
factory.getAllByIssuer('https://auth0.com/foo');
|
|
97
|
+
|
|
98
|
+
factory.clear();
|
|
99
|
+
expect(factory.hasProviders()).toBe(false);
|
|
100
|
+
const issuerCache = (factory as unknown as { issuerCache: { Size: number } }).issuerCache;
|
|
101
|
+
const issuerMultiCache = (factory as unknown as { issuerMultiCache: { Size: number } }).issuerMultiCache;
|
|
102
|
+
expect(issuerCache.Size).toBe(0);
|
|
103
|
+
expect(issuerMultiCache.Size).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
});
|