mongodb 5.3.0 → 5.4.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.
Files changed (64) hide show
  1. package/lib/admin.js +16 -0
  2. package/lib/admin.js.map +1 -1
  3. package/lib/cmap/auth/mongo_credentials.js +29 -2
  4. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  5. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +5 -3
  6. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -1
  7. package/lib/cmap/auth/mongodb_oidc/cache.js +28 -0
  8. package/lib/cmap/auth/mongodb_oidc/cache.js.map +1 -0
  9. package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js +83 -0
  10. package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js.map +1 -0
  11. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +138 -112
  12. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -1
  13. package/lib/cmap/auth/mongodb_oidc/service_workflow.js +4 -2
  14. package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -1
  15. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js +12 -56
  16. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js.map +1 -1
  17. package/lib/cmap/auth/mongodb_oidc.js +17 -13
  18. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  19. package/lib/cmap/connection.js +12 -12
  20. package/lib/cmap/connection.js.map +1 -1
  21. package/lib/cmap/wire_protocol/constants.js +2 -2
  22. package/lib/cmap/wire_protocol/shared.js +2 -2
  23. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  24. package/lib/collection.js +3 -0
  25. package/lib/collection.js.map +1 -1
  26. package/lib/connection_string.js +8 -0
  27. package/lib/connection_string.js.map +1 -1
  28. package/lib/db.js +16 -0
  29. package/lib/db.js.map +1 -1
  30. package/lib/mongo_client.js +15 -0
  31. package/lib/mongo_client.js.map +1 -1
  32. package/lib/mongo_logger.js +40 -6
  33. package/lib/mongo_logger.js.map +1 -1
  34. package/lib/operations/run_command.js.map +1 -1
  35. package/lib/operations/stats.js.map +1 -1
  36. package/lib/utils.js +16 -1
  37. package/lib/utils.js.map +1 -1
  38. package/mongodb.d.ts +108 -24
  39. package/package.json +2 -2
  40. package/src/admin.ts +16 -0
  41. package/src/change_stream.ts +1 -1
  42. package/src/cmap/auth/mongo_credentials.ts +35 -2
  43. package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +6 -3
  44. package/src/cmap/auth/mongodb_oidc/cache.ts +27 -0
  45. package/src/cmap/auth/mongodb_oidc/callback_lock_cache.ts +107 -0
  46. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +208 -171
  47. package/src/cmap/auth/mongodb_oidc/service_workflow.ts +5 -3
  48. package/src/cmap/auth/mongodb_oidc/token_entry_cache.ts +17 -96
  49. package/src/cmap/auth/mongodb_oidc.ts +61 -37
  50. package/src/cmap/connection.ts +13 -12
  51. package/src/cmap/wire_protocol/constants.ts +2 -2
  52. package/src/cmap/wire_protocol/shared.ts +2 -3
  53. package/src/collection.ts +3 -0
  54. package/src/connection_string.ts +11 -0
  55. package/src/db.ts +16 -0
  56. package/src/index.ts +4 -3
  57. package/src/mongo_client.ts +26 -3
  58. package/src/mongo_logger.ts +42 -6
  59. package/src/operations/run_command.ts +40 -3
  60. package/src/operations/stats.ts +11 -2
  61. package/src/utils.ts +17 -0
  62. package/lib/cmap/auth/mongodb_oidc/workflow.js +0 -3
  63. package/lib/cmap/auth/mongodb_oidc/workflow.js.map +0 -1
  64. package/src/cmap/auth/mongodb_oidc/workflow.ts +0 -21
@@ -1,16 +1,33 @@
1
1
  import { Binary, BSON, type Document } from 'bson';
2
2
 
3
- import { MongoInvalidArgumentError, MongoMissingCredentialsError } from '../../../error';
3
+ import { MONGODB_ERROR_CODES, MongoError, MongoMissingCredentialsError } from '../../../error';
4
4
  import { ns } from '../../../utils';
5
5
  import type { Connection } from '../../connection';
6
6
  import type { MongoCredentials } from '../mongo_credentials';
7
- import type { OIDCMechanismServerStep1, OIDCRequestTokenResult } from '../mongodb_oidc';
7
+ import type {
8
+ IdPServerInfo,
9
+ IdPServerResponse,
10
+ OIDCCallbackContext,
11
+ OIDCRefreshFunction,
12
+ OIDCRequestFunction,
13
+ Workflow
14
+ } from '../mongodb_oidc';
8
15
  import { AuthMechanism } from '../providers';
16
+ import { CallbackLockCache } from './callback_lock_cache';
9
17
  import { TokenEntryCache } from './token_entry_cache';
10
- import type { Workflow } from './workflow';
11
18
 
12
- /* 5 minutes in milliseconds */
13
- const TIMEOUT_MS = 300000;
19
+ /** The current version of OIDC implementation. */
20
+ const OIDC_VERSION = 0;
21
+
22
+ /** 5 minutes in seconds */
23
+ const TIMEOUT_S = 300;
24
+
25
+ /** Properties allowed on results of callbacks. */
26
+ const RESULT_PROPERTIES = ['accessToken', 'expiresInSeconds', 'refreshToken'];
27
+
28
+ /** Error message when the callback result is invalid. */
29
+ const CALLBACK_RESULT_ERROR =
30
+ 'User provided OIDC callbacks must return a valid object with an accessToken.';
14
31
 
15
32
  /**
16
33
  * OIDC implementation of a callback based workflow.
@@ -18,206 +35,248 @@ const TIMEOUT_MS = 300000;
18
35
  */
19
36
  export class CallbackWorkflow implements Workflow {
20
37
  cache: TokenEntryCache;
38
+ callbackCache: CallbackLockCache;
21
39
 
22
40
  /**
23
41
  * Instantiate the workflow
24
42
  */
25
43
  constructor() {
26
44
  this.cache = new TokenEntryCache();
45
+ this.callbackCache = new CallbackLockCache();
27
46
  }
28
47
 
29
48
  /**
30
- * Get the document to add for speculative authentication. Is empty when
31
- * callbacks are in play.
49
+ * Get the document to add for speculative authentication. This also needs
50
+ * to add a db field from the credentials source.
32
51
  */
33
- speculativeAuth(): Promise<Document> {
34
- return Promise.resolve({});
52
+ async speculativeAuth(credentials: MongoCredentials): Promise<Document> {
53
+ const document = startCommandDocument(credentials);
54
+ document.db = credentials.source;
55
+ return { speculativeAuthenticate: document };
35
56
  }
36
57
 
37
58
  /**
38
- * Execute the workflow.
39
- *
40
- * Steps:
41
- * - If an entry is in the cache
42
- * - If it is not expired
43
- * - Skip step one and use the entry to execute step two.
44
- * - If it is expired
45
- * - If the refresh callback exists
46
- * - remove expired entry from cache
47
- * - call the refresh callback.
48
- * - put the new entry in the cache.
49
- * - execute step two.
50
- * - If the refresh callback does not exist.
51
- * - remove expired entry from cache
52
- * - call the request callback.
53
- * - put the new entry in the cache.
54
- * - execute step two.
55
- * - If no entry is in the cache.
56
- * - execute step one.
57
- * - call the refresh callback.
58
- * - put the new entry in the cache.
59
- * - execute step two.
59
+ * Execute the OIDC callback workflow.
60
60
  */
61
61
  async execute(
62
62
  connection: Connection,
63
63
  credentials: MongoCredentials,
64
- reauthenticate = false
64
+ reauthenticating: boolean,
65
+ response?: Document
65
66
  ): Promise<Document> {
66
- const request = credentials.mechanismProperties.REQUEST_TOKEN_CALLBACK;
67
- const refresh = credentials.mechanismProperties.REFRESH_TOKEN_CALLBACK;
68
-
69
- const entry = this.cache.getEntry(
70
- connection.address,
71
- credentials.username,
72
- request || null,
73
- refresh || null
67
+ // Get the callbacks with locks from the callback lock cache.
68
+ const { requestCallback, refreshCallback, callbackHash } = this.callbackCache.getCallbacks(
69
+ connection,
70
+ credentials
74
71
  );
72
+ // Look for an existing entry in the cache.
73
+ const entry = this.cache.getEntry(connection.address, credentials.username, callbackHash);
74
+ let result;
75
75
  if (entry) {
76
- // Check if the entry is not expired and if we are reauthenticating.
77
- if (!reauthenticate && entry.isValid()) {
78
- // Skip step one and execute the step two saslContinue.
79
- try {
80
- const result = await finishAuth(entry.tokenResult, undefined, connection, credentials);
81
- return result;
82
- } catch (error) {
83
- // If authentication errors when using a cached token we remove it from
84
- // the cache.
85
- this.cache.deleteEntry(
86
- connection.address,
87
- credentials.username || '',
88
- request || null,
89
- refresh || null
90
- );
91
- throw error;
92
- }
93
- } else {
94
- // Remove the expired entry from the cache.
95
- this.cache.deleteEntry(
96
- connection.address,
97
- credentials.username || '',
98
- request || null,
99
- refresh || null
76
+ // Reauthentication cannot use a token from the cache since the server has
77
+ // stated it is invalid by the request for reauthentication.
78
+ if (entry.isValid() && !reauthenticating) {
79
+ // Presence of a valid cache entry means we can skip to the finishing step.
80
+ result = await this.finishAuthentication(
81
+ connection,
82
+ credentials,
83
+ entry.tokenResult,
84
+ response?.speculativeAuthenticate?.conversationId
100
85
  );
101
- // Execute a refresh of the token and finish auth.
102
- return this.refreshAndFinish(
86
+ } else {
87
+ // Presence of an expired cache entry means we must fetch a new one and
88
+ // then execute the final step.
89
+ const tokenResult = await this.fetchAccessToken(
103
90
  connection,
104
91
  credentials,
105
- entry.serverResult,
106
- entry.tokenResult
92
+ entry.serverInfo,
93
+ reauthenticating,
94
+ callbackHash,
95
+ requestCallback,
96
+ refreshCallback
107
97
  );
98
+ try {
99
+ result = await this.finishAuthentication(
100
+ connection,
101
+ credentials,
102
+ tokenResult,
103
+ reauthenticating ? undefined : response?.speculativeAuthenticate?.conversationId
104
+ );
105
+ } catch (error) {
106
+ // If we are reauthenticating and this errors with reauthentication
107
+ // required, we need to do the entire process over again and clear
108
+ // the cache entry.
109
+ if (
110
+ reauthenticating &&
111
+ error instanceof MongoError &&
112
+ error.code === MONGODB_ERROR_CODES.Reauthenticate
113
+ ) {
114
+ this.cache.deleteEntry(connection.address, credentials.username, callbackHash);
115
+ result = await this.execute(connection, credentials, reauthenticating);
116
+ } else {
117
+ throw error;
118
+ }
119
+ }
108
120
  }
109
121
  } else {
110
- // No entry means to start with the step one saslStart.
111
- const result = await connection.commandAsync(
112
- ns(credentials.source),
113
- startCommandDocument(credentials),
114
- undefined
122
+ // No entry in the cache requires us to do all authentication steps
123
+ // from start to finish, including getting a fresh token for the cache.
124
+ const startDocument = await this.startAuthentication(
125
+ connection,
126
+ credentials,
127
+ reauthenticating,
128
+ response
129
+ );
130
+ const conversationId = startDocument.conversationId;
131
+ const serverResult = BSON.deserialize(startDocument.payload.buffer) as IdPServerInfo;
132
+ const tokenResult = await this.fetchAccessToken(
133
+ connection,
134
+ credentials,
135
+ serverResult,
136
+ reauthenticating,
137
+ callbackHash,
138
+ requestCallback,
139
+ refreshCallback
140
+ );
141
+ result = await this.finishAuthentication(
142
+ connection,
143
+ credentials,
144
+ tokenResult,
145
+ conversationId
115
146
  );
116
- const stepOne = BSON.deserialize(result.payload.buffer) as OIDCMechanismServerStep1;
117
- // Call the request callback and finish auth.
118
- return this.requestAndFinish(connection, credentials, stepOne, result.conversationId);
119
147
  }
148
+ return result;
120
149
  }
121
150
 
122
151
  /**
123
- * Execute the refresh callback if it exists, otherwise the request callback, then
124
- * finish the authentication.
152
+ * Starts the callback authentication process. If there is a speculative
153
+ * authentication document from the initial handshake, then we will use that
154
+ * value to get the issuer, otherwise we will send the saslStart command.
125
155
  */
126
- private async refreshAndFinish(
156
+ private async startAuthentication(
127
157
  connection: Connection,
128
158
  credentials: MongoCredentials,
129
- stepOneResult: OIDCMechanismServerStep1,
130
- tokenResult: OIDCRequestTokenResult,
131
- conversationId?: number
159
+ reauthenticating: boolean,
160
+ response?: Document
132
161
  ): Promise<Document> {
133
- const request = credentials.mechanismProperties.REQUEST_TOKEN_CALLBACK;
134
- const refresh = credentials.mechanismProperties.REFRESH_TOKEN_CALLBACK;
135
- // If a refresh callback exists, use it. Otherwise use the request callback.
136
- if (refresh) {
137
- const result: OIDCRequestTokenResult = await refresh(
138
- credentials.username,
139
- stepOneResult,
140
- tokenResult,
141
- TIMEOUT_MS
142
- );
143
- // Validate the result.
144
- if (!result || !result.accessToken) {
145
- throw new MongoMissingCredentialsError(
146
- 'REFRESH_TOKEN_CALLBACK must return a valid object with an accessToken'
147
- );
148
- }
149
- // Cache a new entry and continue with the saslContinue.
150
- this.cache.addEntry(
151
- connection.address,
152
- credentials.username || '',
153
- request || null,
154
- refresh,
155
- result,
156
- stepOneResult
157
- );
158
- return finishAuth(result, conversationId, connection, credentials);
162
+ let result;
163
+ if (!reauthenticating && response?.speculativeAuthenticate) {
164
+ result = response.speculativeAuthenticate;
159
165
  } else {
160
- // Fallback to using the request callback.
161
- return this.requestAndFinish(connection, credentials, stepOneResult, conversationId);
166
+ result = await connection.commandAsync(
167
+ ns(credentials.source),
168
+ startCommandDocument(credentials),
169
+ undefined
170
+ );
162
171
  }
172
+ return result;
163
173
  }
164
174
 
165
175
  /**
166
- * Execute the request callback and finish authentication.
176
+ * Finishes the callback authentication process.
167
177
  */
168
- private async requestAndFinish(
178
+ private async finishAuthentication(
169
179
  connection: Connection,
170
180
  credentials: MongoCredentials,
171
- stepOneResult: OIDCMechanismServerStep1,
181
+ tokenResult: IdPServerResponse,
172
182
  conversationId?: number
173
183
  ): Promise<Document> {
174
- // Call the request callback.
175
- const request = credentials.mechanismProperties.REQUEST_TOKEN_CALLBACK;
176
- const refresh = credentials.mechanismProperties.REFRESH_TOKEN_CALLBACK;
177
- // Always clear expired entries from the cache on each finish as cleanup.
178
- this.cache.deleteExpiredEntries();
179
- if (!request) {
180
- // Request callback must be present.
181
- throw new MongoInvalidArgumentError(
182
- 'Auth mechanism property REQUEST_TOKEN_CALLBACK is required.'
183
- );
184
+ const result = await connection.commandAsync(
185
+ ns(credentials.source),
186
+ finishCommandDocument(tokenResult.accessToken, conversationId),
187
+ undefined
188
+ );
189
+ return result;
190
+ }
191
+
192
+ /**
193
+ * Fetches an access token using either the request or refresh callbacks and
194
+ * puts it in the cache.
195
+ */
196
+ private async fetchAccessToken(
197
+ connection: Connection,
198
+ credentials: MongoCredentials,
199
+ serverInfo: IdPServerInfo,
200
+ reauthenticating: boolean,
201
+ callbackHash: string,
202
+ requestCallback: OIDCRequestFunction,
203
+ refreshCallback?: OIDCRefreshFunction
204
+ ): Promise<IdPServerResponse> {
205
+ // Get the token from the cache.
206
+ const entry = this.cache.getEntry(connection.address, credentials.username, callbackHash);
207
+ let result;
208
+ const context: OIDCCallbackContext = { timeoutSeconds: TIMEOUT_S, version: OIDC_VERSION };
209
+ // Check if there's a token in the cache.
210
+ if (entry) {
211
+ // If the cache entry is valid, return the token result.
212
+ if (entry.isValid() && !reauthenticating) {
213
+ return entry.tokenResult;
214
+ }
215
+ // If the cache entry is not valid, remove it from the cache and first attempt
216
+ // to use the refresh callback to get a new token. If no refresh callback
217
+ // exists, then fallback to the request callback.
218
+ if (refreshCallback) {
219
+ context.refreshToken = entry.tokenResult.refreshToken;
220
+ result = await refreshCallback(serverInfo, context);
221
+ } else {
222
+ result = await requestCallback(serverInfo, context);
223
+ }
224
+ } else {
225
+ // With no token in the cache we use the request callback.
226
+ result = await requestCallback(serverInfo, context);
184
227
  }
185
- const tokenResult = await request(credentials.username, stepOneResult, TIMEOUT_MS);
186
- // Validate the result.
187
- if (!tokenResult || !tokenResult.accessToken) {
188
- throw new MongoMissingCredentialsError(
189
- 'REQUEST_TOKEN_CALLBACK must return a valid object with an accessToken'
190
- );
228
+ // Validate that the result returned by the callback is acceptable. If it is not
229
+ // we must clear the token result from the cache.
230
+ if (isCallbackResultInvalid(result)) {
231
+ this.cache.deleteEntry(connection.address, credentials.username, callbackHash);
232
+ throw new MongoMissingCredentialsError(CALLBACK_RESULT_ERROR);
191
233
  }
192
- // Cache a new entry and continue with the saslContinue.
234
+ // Cleanup the cache.
235
+ this.cache.deleteExpiredEntries();
236
+ // Put the new entry into the cache.
193
237
  this.cache.addEntry(
194
238
  connection.address,
195
239
  credentials.username || '',
196
- request,
197
- refresh || null,
198
- tokenResult,
199
- stepOneResult
240
+ callbackHash,
241
+ result,
242
+ serverInfo
200
243
  );
201
- return finishAuth(tokenResult, conversationId, connection, credentials);
244
+ return result;
202
245
  }
203
246
  }
204
247
 
205
248
  /**
206
- * Cache the result of the user supplied callback and execute the
207
- * step two saslContinue.
249
+ * Generate the finishing command document for authentication. Will be a
250
+ * saslStart or saslContinue depending on the presence of a conversation id.
208
251
  */
209
- async function finishAuth(
210
- result: OIDCRequestTokenResult,
211
- conversationId: number | undefined,
212
- connection: Connection,
213
- credentials: MongoCredentials
214
- ): Promise<Document> {
215
- // Execute the step two saslContinue.
216
- return connection.commandAsync(
217
- ns(credentials.source),
218
- continueCommandDocument(result.accessToken, conversationId),
219
- undefined
220
- );
252
+ function finishCommandDocument(token: string, conversationId?: number): Document {
253
+ if (conversationId != null && typeof conversationId === 'number') {
254
+ return {
255
+ saslContinue: 1,
256
+ conversationId: conversationId,
257
+ payload: new Binary(BSON.serialize({ jwt: token }))
258
+ };
259
+ }
260
+ // saslContinue requires a conversationId in the command to be valid so in this
261
+ // case the server allows "step two" to actually be a saslStart with the token
262
+ // as the jwt since the use of the cached value has no correlating conversating
263
+ // on the particular connection.
264
+ return {
265
+ saslStart: 1,
266
+ mechanism: AuthMechanism.MONGODB_OIDC,
267
+ payload: new Binary(BSON.serialize({ jwt: token }))
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Determines if a result returned from a request or refresh callback
273
+ * function is invalid. This means the result is nullish, doesn't contain
274
+ * the accessToken required field, and does not contain extra fields.
275
+ */
276
+ function isCallbackResultInvalid(tokenResult: unknown): boolean {
277
+ if (tokenResult == null || typeof tokenResult !== 'object') return true;
278
+ if (!('accessToken' in tokenResult)) return true;
279
+ return !Object.getOwnPropertyNames(tokenResult).every(prop => RESULT_PROPERTIES.includes(prop));
221
280
  }
222
281
 
223
282
  /**
@@ -235,25 +294,3 @@ function startCommandDocument(credentials: MongoCredentials): Document {
235
294
  payload: new Binary(BSON.serialize(payload))
236
295
  };
237
296
  }
238
-
239
- /**
240
- * Generate the saslContinue command document.
241
- */
242
- function continueCommandDocument(token: string, conversationId?: number): Document {
243
- if (conversationId) {
244
- return {
245
- saslContinue: 1,
246
- conversationId: conversationId,
247
- payload: new Binary(BSON.serialize({ jwt: token }))
248
- };
249
- }
250
- // saslContinue requires a conversationId in the command to be valid so in this
251
- // case the server allows "step two" to actually be a saslStart with the token
252
- // as the jwt since the use of the cached value has no correlating conversating
253
- // on the particular connection.
254
- return {
255
- saslStart: 1,
256
- mechanism: AuthMechanism.MONGODB_OIDC,
257
- payload: new Binary(BSON.serialize({ jwt: token }))
258
- };
259
- }
@@ -3,8 +3,8 @@ import { BSON, type Document } from 'bson';
3
3
  import { ns } from '../../../utils';
4
4
  import type { Connection } from '../../connection';
5
5
  import type { MongoCredentials } from '../mongo_credentials';
6
+ import type { Workflow } from '../mongodb_oidc';
6
7
  import { AuthMechanism } from '../providers';
7
- import type { Workflow } from './workflow';
8
8
 
9
9
  /**
10
10
  * Common behaviour for OIDC device workflows.
@@ -24,9 +24,11 @@ export abstract class ServiceWorkflow implements Workflow {
24
24
  /**
25
25
  * Get the document to add for speculative authentication.
26
26
  */
27
- async speculativeAuth(): Promise<Document> {
27
+ async speculativeAuth(credentials: MongoCredentials): Promise<Document> {
28
28
  const token = await this.getToken();
29
- return { speculativeAuthenticate: commandDocument(token) };
29
+ const document = commandDocument(token);
30
+ document.db = credentials.source;
31
+ return { speculativeAuthenticate: document };
30
32
  }
31
33
 
32
34
  /**
@@ -1,41 +1,22 @@
1
- import type {
2
- OIDCMechanismServerStep1,
3
- OIDCRefreshFunction,
4
- OIDCRequestFunction,
5
- OIDCRequestTokenResult
6
- } from '../mongodb_oidc';
1
+ import type { IdPServerInfo, IdPServerResponse } from '../mongodb_oidc';
2
+ import { Cache } from './cache';
7
3
 
8
- /* 5 minutes in milliseonds */
4
+ /* 5 minutes in milliseconds */
9
5
  const EXPIRATION_BUFFER_MS = 300000;
10
6
  /* Default expiration is now for when no expiration provided */
11
7
  const DEFAULT_EXPIRATION_SECS = 0;
12
- /* Counter for function "hashes".*/
13
- let FN_HASH_COUNTER = 0;
14
- /* No function present function */
15
- const NO_FUNCTION: OIDCRequestFunction = () => {
16
- return Promise.resolve({ accessToken: 'test' });
17
- };
18
- /* The map of function hashes */
19
- const FN_HASHES = new WeakMap<OIDCRequestFunction | OIDCRefreshFunction, number>();
20
- /* Put the no function hash in the map. */
21
- FN_HASHES.set(NO_FUNCTION, FN_HASH_COUNTER);
22
-
23
8
  /** @internal */
24
9
  export class TokenEntry {
25
- tokenResult: OIDCRequestTokenResult;
26
- serverResult: OIDCMechanismServerStep1;
10
+ tokenResult: IdPServerResponse;
11
+ serverInfo: IdPServerInfo;
27
12
  expiration: number;
28
13
 
29
14
  /**
30
15
  * Instantiate the entry.
31
16
  */
32
- constructor(
33
- tokenResult: OIDCRequestTokenResult,
34
- serverResult: OIDCMechanismServerStep1,
35
- expiration: number
36
- ) {
17
+ constructor(tokenResult: IdPServerResponse, serverInfo: IdPServerInfo, expiration: number) {
37
18
  this.tokenResult = tokenResult;
38
- this.serverResult = serverResult;
19
+ this.serverInfo = serverInfo;
39
20
  this.expiration = expiration;
40
21
  }
41
22
 
@@ -52,62 +33,38 @@ export class TokenEntry {
52
33
  * Cache of OIDC token entries.
53
34
  * @internal
54
35
  */
55
- export class TokenEntryCache {
56
- entries: Map<string, TokenEntry>;
57
-
58
- constructor() {
59
- this.entries = new Map();
60
- }
61
-
36
+ export class TokenEntryCache extends Cache<TokenEntry> {
62
37
  /**
63
38
  * Set an entry in the token cache.
64
39
  */
65
40
  addEntry(
66
41
  address: string,
67
42
  username: string,
68
- requestFn: OIDCRequestFunction | null,
69
- refreshFn: OIDCRefreshFunction | null,
70
- tokenResult: OIDCRequestTokenResult,
71
- serverResult: OIDCMechanismServerStep1
43
+ callbackHash: string,
44
+ tokenResult: IdPServerResponse,
45
+ serverInfo: IdPServerInfo
72
46
  ): TokenEntry {
73
47
  const entry = new TokenEntry(
74
48
  tokenResult,
75
- serverResult,
49
+ serverInfo,
76
50
  expirationTime(tokenResult.expiresInSeconds)
77
51
  );
78
- this.entries.set(cacheKey(address, username, requestFn, refreshFn), entry);
52
+ this.entries.set(this.cacheKey(address, username, callbackHash), entry);
79
53
  return entry;
80
54
  }
81
55
 
82
- /**
83
- * Clear the cache.
84
- */
85
- clear(): void {
86
- this.entries.clear();
87
- }
88
-
89
56
  /**
90
57
  * Delete an entry from the cache.
91
58
  */
92
- deleteEntry(
93
- address: string,
94
- username: string,
95
- requestFn: OIDCRequestFunction | null,
96
- refreshFn: OIDCRefreshFunction | null
97
- ): void {
98
- this.entries.delete(cacheKey(address, username, requestFn, refreshFn));
59
+ deleteEntry(address: string, username: string, callbackHash: string): void {
60
+ this.entries.delete(this.cacheKey(address, username, callbackHash));
99
61
  }
100
62
 
101
63
  /**
102
64
  * Get an entry from the cache.
103
65
  */
104
- getEntry(
105
- address: string,
106
- username: string,
107
- requestFn: OIDCRequestFunction | null,
108
- refreshFn: OIDCRefreshFunction | null
109
- ): TokenEntry | undefined {
110
- return this.entries.get(cacheKey(address, username, requestFn, refreshFn));
66
+ getEntry(address: string, username: string, callbackHash: string): TokenEntry | undefined {
67
+ return this.entries.get(this.cacheKey(address, username, callbackHash));
111
68
  }
112
69
 
113
70
  /**
@@ -128,39 +85,3 @@ export class TokenEntryCache {
128
85
  function expirationTime(expiresInSeconds?: number): number {
129
86
  return Date.now() + (expiresInSeconds ?? DEFAULT_EXPIRATION_SECS) * 1000;
130
87
  }
131
-
132
- /**
133
- * Create a cache key from the address and username.
134
- */
135
- function cacheKey(
136
- address: string,
137
- username: string,
138
- requestFn: OIDCRequestFunction | null,
139
- refreshFn: OIDCRefreshFunction | null
140
- ): string {
141
- return `${address}-${username}-${hashFunctions(requestFn, refreshFn)}`;
142
- }
143
-
144
- /**
145
- * Get the hash string for the request and refresh functions.
146
- */
147
- function hashFunctions(
148
- requestFn: OIDCRequestFunction | null,
149
- refreshFn: OIDCRefreshFunction | null
150
- ): string {
151
- let requestHash = FN_HASHES.get(requestFn || NO_FUNCTION);
152
- let refreshHash = FN_HASHES.get(refreshFn || NO_FUNCTION);
153
- if (!requestHash && requestFn) {
154
- // Create a new one for the function and put it in the map.
155
- FN_HASH_COUNTER++;
156
- requestHash = FN_HASH_COUNTER;
157
- FN_HASHES.set(requestFn, FN_HASH_COUNTER);
158
- }
159
- if (!refreshHash && refreshFn) {
160
- // Create a new one for the function and put it in the map.
161
- FN_HASH_COUNTER++;
162
- refreshHash = FN_HASH_COUNTER;
163
- FN_HASHES.set(refreshFn, FN_HASH_COUNTER);
164
- }
165
- return `${requestHash}-${refreshHash}`;
166
- }