@axa-fr/oidc-client-service-worker 6.25.2-alpha949

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 (54) hide show
  1. package/dist/OidcServiceWorker.d.ts +1 -0
  2. package/dist/OidcServiceWorker.js +559 -0
  3. package/dist/OidcServiceWorker.js.map +1 -0
  4. package/dist/OidcTrustedDomains.js +26 -0
  5. package/dist/src/OidcServiceWorker.d.ts +2 -0
  6. package/dist/src/OidcServiceWorker.d.ts.map +1 -0
  7. package/dist/src/constants.d.ts +18 -0
  8. package/dist/src/constants.d.ts.map +1 -0
  9. package/dist/src/types.d.ts +87 -0
  10. package/dist/src/types.d.ts.map +1 -0
  11. package/dist/src/utils/__tests__/codeVerifier.spec.d.ts +2 -0
  12. package/dist/src/utils/__tests__/codeVerifier.spec.d.ts.map +1 -0
  13. package/dist/src/utils/__tests__/domains.spec.d.ts +2 -0
  14. package/dist/src/utils/__tests__/domains.spec.d.ts.map +1 -0
  15. package/dist/src/utils/__tests__/serializeHeaders.spec.d.ts +2 -0
  16. package/dist/src/utils/__tests__/serializeHeaders.spec.d.ts.map +1 -0
  17. package/dist/src/utils/__tests__/strings.spec.d.ts +2 -0
  18. package/dist/src/utils/__tests__/strings.spec.d.ts.map +1 -0
  19. package/dist/src/utils/__tests__/testHelper.d.ts +57 -0
  20. package/dist/src/utils/__tests__/testHelper.d.ts.map +1 -0
  21. package/dist/src/utils/__tests__/tokens.spec.d.ts +2 -0
  22. package/dist/src/utils/__tests__/tokens.spec.d.ts.map +1 -0
  23. package/dist/src/utils/codeVerifier.d.ts +2 -0
  24. package/dist/src/utils/codeVerifier.d.ts.map +1 -0
  25. package/dist/src/utils/domains.d.ts +6 -0
  26. package/dist/src/utils/domains.d.ts.map +1 -0
  27. package/dist/src/utils/index.d.ts +6 -0
  28. package/dist/src/utils/index.d.ts.map +1 -0
  29. package/dist/src/utils/serializeHeaders.d.ts +3 -0
  30. package/dist/src/utils/serializeHeaders.d.ts.map +1 -0
  31. package/dist/src/utils/sleep.d.ts +3 -0
  32. package/dist/src/utils/sleep.d.ts.map +1 -0
  33. package/dist/src/utils/strings.d.ts +8 -0
  34. package/dist/src/utils/strings.d.ts.map +1 -0
  35. package/dist/src/utils/tokens.d.ts +22 -0
  36. package/dist/src/utils/tokens.d.ts.map +1 -0
  37. package/package.json +72 -0
  38. package/src/OidcServiceWorker.ts +423 -0
  39. package/src/OidcTrustedDomains.js +26 -0
  40. package/src/constants.ts +32 -0
  41. package/src/types.ts +101 -0
  42. package/src/utils/__tests__/codeVerifier.spec.ts +13 -0
  43. package/src/utils/__tests__/domains.spec.ts +90 -0
  44. package/src/utils/__tests__/serializeHeaders.spec.ts +12 -0
  45. package/src/utils/__tests__/strings.spec.ts +10 -0
  46. package/src/utils/__tests__/testHelper.ts +346 -0
  47. package/src/utils/__tests__/tokens.spec.ts +90 -0
  48. package/src/utils/codeVerifier.ts +4 -0
  49. package/src/utils/domains.ts +104 -0
  50. package/src/utils/index.ts +5 -0
  51. package/src/utils/serializeHeaders.ts +12 -0
  52. package/src/utils/sleep.ts +2 -0
  53. package/src/utils/strings.ts +9 -0
  54. package/src/utils/tokens.ts +207 -0
@@ -0,0 +1,423 @@
1
+ import { acceptAnyDomainToken, scriptFilename, TOKEN } from './constants';
2
+ import {
3
+ Database,
4
+ MessageEventData,
5
+ OidcConfig,
6
+ OidcConfiguration,
7
+ TrustedDomains,
8
+ // TrustedDomainsShowAccessToken,
9
+ } from './types';
10
+ import {
11
+ checkDomain,
12
+ getCurrentDatabaseDomain,
13
+ getDomains,
14
+ hideTokens,
15
+ isTokensValid,
16
+ serializeHeaders,
17
+ sleep,
18
+ } from './utils';
19
+ import { replaceCodeVerifier } from './utils/codeVerifier';
20
+
21
+ const _self = self as ServiceWorkerGlobalScope & typeof globalThis;
22
+
23
+ declare let trustedDomains: TrustedDomains;
24
+
25
+ _self.importScripts(scriptFilename);
26
+
27
+ const id = Math.round(new Date().getTime() / 1000).toString();
28
+
29
+ const keepAliveJsonFilename = 'OidcKeepAliveServiceWorker.json';
30
+ const handleInstall = (event: ExtendableEvent) => {
31
+ console.log('[OidcServiceWorker] service worker installed ' + id);
32
+ event.waitUntil(_self.skipWaiting());
33
+ };
34
+
35
+ const handleActivate = (event: ExtendableEvent) => {
36
+ console.log('[OidcServiceWorker] service worker activated ' + id);
37
+ event.waitUntil(_self.clients.claim());
38
+ };
39
+
40
+ let currentLoginCallbackConfigurationName: string | null = null;
41
+ const database: Database = {
42
+ default: {
43
+ configurationName: 'default',
44
+ tokens: null,
45
+ status: null,
46
+ state: null,
47
+ codeVerifier: null,
48
+ nonce: null,
49
+ oidcServerConfiguration: null,
50
+ hideAccessToken: true,
51
+ },
52
+ };
53
+
54
+ const getCurrentDatabasesTokenEndpoint = (database: Database, url: string) => {
55
+ const databases: OidcConfig[] = [];
56
+ for (const [, value] of Object.entries<OidcConfig>(database)) {
57
+ if (
58
+ value.oidcServerConfiguration != null &&
59
+ url.startsWith(value.oidcServerConfiguration.tokenEndpoint)
60
+ ) {
61
+ databases.push(value);
62
+ } else if (
63
+ value.oidcServerConfiguration != null &&
64
+ value.oidcServerConfiguration.revocationEndpoint &&
65
+ url.startsWith(value.oidcServerConfiguration.revocationEndpoint)
66
+ ) {
67
+ databases.push(value);
68
+ }
69
+ }
70
+ return databases;
71
+ };
72
+
73
+ const keepAliveAsync = async (event: FetchEvent) => {
74
+ const originalRequest = event.request;
75
+ const isFromVanilla = originalRequest.headers.has('oidc-vanilla');
76
+ const init = { status: 200, statusText: 'oidc-service-worker' };
77
+ const response = new Response('{}', init);
78
+ if (!isFromVanilla) {
79
+ const originalRequestUrl = new URL(originalRequest.url);
80
+ const minSleepSeconds = Number(originalRequestUrl.searchParams.get('minSleepSeconds')) || 240;
81
+ for (let i = 0; i < minSleepSeconds; i++) {
82
+ await sleep(1000 + Math.floor(Math.random() * 1000));
83
+ const cache = await caches.open('oidc_dummy_cache');
84
+ await cache.put(event.request, response.clone());
85
+ }
86
+ }
87
+ return response;
88
+ };
89
+
90
+ const handleFetch = async (event: FetchEvent) => {
91
+ const originalRequest = event.request;
92
+ const url = originalRequest.url;
93
+ if (originalRequest.url.includes(keepAliveJsonFilename)) {
94
+ event.respondWith(keepAliveAsync(event));
95
+ return;
96
+ }
97
+
98
+ const currentDatabaseForRequestAccessToken = getCurrentDatabaseDomain(
99
+ database,
100
+ originalRequest.url,
101
+ trustedDomains,
102
+ );
103
+ if (
104
+ currentDatabaseForRequestAccessToken &&
105
+ currentDatabaseForRequestAccessToken.tokens &&
106
+ currentDatabaseForRequestAccessToken.tokens.access_token
107
+ ) {
108
+ while (
109
+ currentDatabaseForRequestAccessToken.tokens &&
110
+ !isTokensValid(currentDatabaseForRequestAccessToken.tokens)
111
+ ) {
112
+ await sleep(200);
113
+ }
114
+ const newRequest =
115
+ originalRequest.mode === 'navigate'
116
+ ? new Request(originalRequest, {
117
+ headers: {
118
+ ...serializeHeaders(originalRequest.headers),
119
+ authorization:
120
+ 'Bearer ' +
121
+ currentDatabaseForRequestAccessToken.tokens.access_token,
122
+ },
123
+ })
124
+ : new Request(originalRequest, {
125
+ headers: {
126
+ ...serializeHeaders(originalRequest.headers),
127
+ authorization:
128
+ 'Bearer ' +
129
+ currentDatabaseForRequestAccessToken.tokens.access_token,
130
+ },
131
+ mode: (
132
+ currentDatabaseForRequestAccessToken.oidcConfiguration as OidcConfiguration
133
+ ).service_worker_convert_all_requests_to_cors
134
+ ? 'cors'
135
+ : originalRequest.mode,
136
+ });
137
+
138
+ // @ts-ignore -- TODO: review, waitUntil takes a promise, this returns a void
139
+ event.waitUntil(event.respondWith(fetch(newRequest)));
140
+
141
+ return;
142
+ }
143
+
144
+ if (event.request.method !== 'POST') {
145
+ return;
146
+ }
147
+
148
+ let currentDatabase: OidcConfig | null = null;
149
+ const currentDatabases = getCurrentDatabasesTokenEndpoint(
150
+ database,
151
+ originalRequest.url,
152
+ );
153
+ const numberDatabase = currentDatabases.length;
154
+ if (numberDatabase > 0) {
155
+ const maPromesse = new Promise<Response>((resolve, reject) => {
156
+ const clonedRequest = originalRequest.clone();
157
+ const response = clonedRequest.text().then((actualBody) => {
158
+ if (
159
+ actualBody.includes(TOKEN.REFRESH_TOKEN) ||
160
+ actualBody.includes(TOKEN.ACCESS_TOKEN)
161
+ ) {
162
+ let newBody = actualBody;
163
+ for (let i = 0; i < numberDatabase; i++) {
164
+ const currentDb = currentDatabases[i];
165
+
166
+ if (currentDb && currentDb.tokens != null) {
167
+ const keyRefreshToken =
168
+ TOKEN.REFRESH_TOKEN + '_' + currentDb.configurationName;
169
+ if (actualBody.includes(keyRefreshToken)) {
170
+ newBody = newBody.replace(
171
+ keyRefreshToken,
172
+ encodeURIComponent(currentDb.tokens.refresh_token as string),
173
+ );
174
+ currentDatabase = currentDb;
175
+ break;
176
+ }
177
+ const keyAccessToken =
178
+ TOKEN.ACCESS_TOKEN + '_' + currentDb.configurationName;
179
+ if (actualBody.includes(keyAccessToken)) {
180
+ newBody = newBody.replace(
181
+ keyAccessToken,
182
+ encodeURIComponent(currentDb.tokens.access_token),
183
+ );
184
+ currentDatabase = currentDb;
185
+ break;
186
+ }
187
+ }
188
+ }
189
+ const fetchPromise = fetch(originalRequest, {
190
+ body: newBody,
191
+ method: clonedRequest.method,
192
+ headers: {
193
+ ...serializeHeaders(originalRequest.headers),
194
+ },
195
+ mode: clonedRequest.mode,
196
+ cache: clonedRequest.cache,
197
+ redirect: clonedRequest.redirect,
198
+ referrer: clonedRequest.referrer,
199
+ credentials: clonedRequest.credentials,
200
+ integrity: clonedRequest.integrity,
201
+ });
202
+
203
+ if (
204
+ currentDatabase &&
205
+ currentDatabase.oidcServerConfiguration != null &&
206
+ currentDatabase.oidcServerConfiguration.revocationEndpoint &&
207
+ url.startsWith(
208
+ currentDatabase.oidcServerConfiguration.revocationEndpoint,
209
+ )
210
+ ) {
211
+ return fetchPromise.then(async (response) => {
212
+ const text = await response.text();
213
+ return new Response(text, response);
214
+ });
215
+ }
216
+ return fetchPromise.then(hideTokens(currentDatabase as OidcConfig)); // todo type assertion to OidcConfig but could be null, NEEDS REVIEW
217
+ } else if (
218
+ actualBody.includes('code_verifier=') &&
219
+ currentLoginCallbackConfigurationName
220
+ ) {
221
+ currentDatabase = database[currentLoginCallbackConfigurationName];
222
+ currentLoginCallbackConfigurationName = null;
223
+ let newBody = actualBody;
224
+ if (currentDatabase && currentDatabase.codeVerifier != null) {
225
+ newBody = replaceCodeVerifier(newBody, currentDatabase.codeVerifier);
226
+ }
227
+
228
+ return fetch(originalRequest, {
229
+ body: newBody,
230
+ method: clonedRequest.method,
231
+ headers: {
232
+ ...serializeHeaders(originalRequest.headers),
233
+ },
234
+ mode: clonedRequest.mode,
235
+ cache: clonedRequest.cache,
236
+ redirect: clonedRequest.redirect,
237
+ referrer: clonedRequest.referrer,
238
+ credentials: clonedRequest.credentials,
239
+ integrity: clonedRequest.integrity,
240
+ }).then(hideTokens(currentDatabase));
241
+ }
242
+ return undefined;
243
+ });
244
+ response
245
+ .then((r) => {
246
+ if (r !== undefined) {
247
+ resolve(r);
248
+ } else {
249
+ console.log('success undefined');
250
+ reject(new Error('Response is undefined inside a success'));
251
+ }
252
+ })
253
+ .catch((err) => {
254
+ if (err !== undefined) {
255
+ reject(err);
256
+ } else {
257
+ console.log('error undefined');
258
+ reject(new Error('Response is undefined inside a error'));
259
+ }
260
+ });
261
+ });
262
+
263
+ // @ts-ignore -- TODO: review, waitUntil takes a promise, this returns a void
264
+ event.waitUntil(event.respondWith(maPromesse));
265
+ }
266
+ };
267
+
268
+ type TrustedDomainsShowAccessToken = {
269
+ [key: string]: boolean;
270
+ }
271
+
272
+ const trustedDomainsShowAccessToken: TrustedDomainsShowAccessToken = {};
273
+
274
+ const handleMessage = (event: ExtendableMessageEvent) => {
275
+ const port = event.ports[0];
276
+ const data = event.data as MessageEventData;
277
+ const configurationName = data.configurationName;
278
+ let currentDatabase = database[configurationName];
279
+ if (trustedDomains == null) {
280
+ trustedDomains = {};
281
+ }
282
+ if (!currentDatabase) {
283
+ if (trustedDomainsShowAccessToken[configurationName] === undefined) {
284
+ const trustedDomain = trustedDomains[configurationName];
285
+ trustedDomainsShowAccessToken[configurationName] = Array.isArray(trustedDomain) ? false : trustedDomain.showAccessToken;
286
+ }
287
+ database[configurationName] = {
288
+ tokens: null,
289
+ state: null,
290
+ codeVerifier: null,
291
+ oidcServerConfiguration: null,
292
+ oidcConfiguration: undefined,
293
+ nonce: null,
294
+ status: null,
295
+ configurationName,
296
+ hideAccessToken: !trustedDomainsShowAccessToken[configurationName],
297
+ };
298
+ currentDatabase = database[configurationName];
299
+
300
+ if (!trustedDomains[configurationName]) {
301
+ trustedDomains[configurationName] = [];
302
+ }
303
+ }
304
+
305
+ switch (data.type) {
306
+ case 'clear':
307
+ currentDatabase.tokens = null;
308
+ currentDatabase.state = null;
309
+ currentDatabase.codeVerifier = null;
310
+ currentDatabase.status = data.data.status;
311
+ port.postMessage({ configurationName });
312
+ return;
313
+ case 'init': {
314
+ const oidcServerConfiguration = data.data.oidcServerConfiguration;
315
+ const trustedDomain = trustedDomains[configurationName];
316
+ const domains = getDomains(trustedDomain, 'oidc');
317
+ if (!domains.find((f) => f === acceptAnyDomainToken)) {
318
+ [
319
+ oidcServerConfiguration.tokenEndpoint,
320
+ oidcServerConfiguration.revocationEndpoint,
321
+ oidcServerConfiguration.userInfoEndpoint,
322
+ oidcServerConfiguration.issuer,
323
+ ].forEach((url) => {
324
+ checkDomain(domains, url);
325
+ });
326
+ }
327
+ currentDatabase.oidcServerConfiguration = oidcServerConfiguration;
328
+ currentDatabase.oidcConfiguration = data.data.oidcConfiguration;
329
+ const where = data.data.where;
330
+ if (
331
+ where === 'loginCallbackAsync' ||
332
+ where === 'tryKeepExistingSessionAsync'
333
+ ) {
334
+ currentLoginCallbackConfigurationName = configurationName;
335
+ } else {
336
+ currentLoginCallbackConfigurationName = null;
337
+ }
338
+
339
+ if (!currentDatabase.tokens) {
340
+ port.postMessage({
341
+ tokens: null,
342
+ status: currentDatabase.status,
343
+ configurationName,
344
+ });
345
+ } else {
346
+ const tokens = {
347
+ ...currentDatabase.tokens,
348
+ };
349
+ if (currentDatabase.hideAccessToken) {
350
+ tokens.access_token = TOKEN.ACCESS_TOKEN + '_' + configurationName;
351
+ }
352
+ if (tokens.refresh_token) {
353
+ tokens.refresh_token = TOKEN.REFRESH_TOKEN + '_' + configurationName;
354
+ }
355
+ if (
356
+ tokens.idTokenPayload &&
357
+ tokens.idTokenPayload.nonce &&
358
+ currentDatabase.nonce != null
359
+ ) {
360
+ tokens.idTokenPayload.nonce =
361
+ TOKEN.NONCE_TOKEN + '_' + configurationName;
362
+ }
363
+ port.postMessage({
364
+ tokens,
365
+ status: currentDatabase.status,
366
+ configurationName,
367
+ });
368
+ }
369
+ return;
370
+ }
371
+ case 'setState':
372
+ currentDatabase.state = data.data.state;
373
+ port.postMessage({ configurationName });
374
+ return;
375
+ case 'getState': {
376
+ const state = currentDatabase.state;
377
+ port.postMessage({ configurationName, state });
378
+ return;
379
+ }
380
+ case 'setCodeVerifier':
381
+ currentDatabase.codeVerifier = data.data.codeVerifier;
382
+ port.postMessage({ configurationName });
383
+ return;
384
+ case 'getCodeVerifier': {
385
+ port.postMessage({
386
+ configurationName,
387
+ codeVerifier: currentDatabase.codeVerifier != null ? TOKEN.CODE_VERIFIER + '_' + configurationName : null,
388
+ });
389
+ return;
390
+ }
391
+ case 'setSessionState':
392
+ currentDatabase.sessionState = data.data.sessionState;
393
+ port.postMessage({ configurationName });
394
+ return;
395
+ case 'getSessionState': {
396
+ const sessionState = currentDatabase.sessionState;
397
+ port.postMessage({ configurationName, sessionState });
398
+ return;
399
+ }
400
+ case 'setNonce': {
401
+ const nonce = data.data.nonce;
402
+ if (nonce) {
403
+ currentDatabase.nonce = nonce;
404
+ }
405
+ port.postMessage({ configurationName });
406
+ return;
407
+ }
408
+ case 'getNonce': {
409
+ const keyNonce = TOKEN.NONCE_TOKEN + '_' + configurationName;
410
+ const nonce = currentDatabase.nonce ? keyNonce : null;
411
+ port.postMessage({ configurationName, nonce });
412
+ return;
413
+ }
414
+ default:
415
+ currentDatabase.items = { ...data.data };
416
+ port.postMessage({ configurationName });
417
+ }
418
+ };
419
+
420
+ _self.addEventListener('install', handleInstall);
421
+ _self.addEventListener('activate', handleActivate);
422
+ _self.addEventListener('fetch', handleFetch);
423
+ _self.addEventListener('message', handleMessage);
@@ -0,0 +1,26 @@
1
+ // Add bellow trusted domains, access tokens will automatically injected to be send to
2
+ // trusted domain can also be a path like https://www.myapi.com/users,
3
+ // then all subroute like https://www.myapi.com/useers/1 will be authorized to send access_token to.
4
+
5
+ // Domains used by OIDC server must be also declared here
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ const trustedDomains = {
8
+ default: ['https://demo.duendesoftware.com', 'https://kdhttps.auth0.com'],
9
+ config_classic: ['https://demo.duendesoftware.com'],
10
+ config_without_silent_login: ['https://demo.duendesoftware.com'],
11
+ config_without_refresh_token: ['https://demo.duendesoftware.com'],
12
+ config_without_refresh_token_silent_login: ['https://demo.duendesoftware.com'],
13
+ config_google: ['https://oauth2.googleapis.com', 'https://openidconnect.googleapis.com'],
14
+ config_with_hash: ['https://demo.duendesoftware.com'],
15
+ };
16
+
17
+ // Service worker will continue to give access token to the JavaScript client
18
+ // Ideal to hide refresh token from client JavaScript, but to retrieve access_token for some
19
+ // scenarios which require it. For example, to send it via websocket connection.
20
+ trustedDomains.config_show_access_token = { domains: ['https://demo.duendesoftware.com'], showAccessToken: true };
21
+
22
+ // This example defines domains used by OIDC server separately from domains to which access tokens will be injected.
23
+ trustedDomains.config_separate_oidc_access_token_domains = {
24
+ oidcDomains: ['https://demo.duendesoftware.com'],
25
+ accessTokenDomains: ['https://myapi'],
26
+ };
@@ -0,0 +1,32 @@
1
+ const scriptFilename = 'OidcTrustedDomains.js';
2
+ const acceptAnyDomainToken = '*';
3
+
4
+ type TokenType = {
5
+ readonly REFRESH_TOKEN: string;
6
+ readonly ACCESS_TOKEN: string;
7
+ readonly NONCE_TOKEN: string;
8
+ readonly CODE_VERIFIER: string;
9
+ };
10
+
11
+ const TOKEN: TokenType = {
12
+ REFRESH_TOKEN: 'REFRESH_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER',
13
+ ACCESS_TOKEN: 'ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER',
14
+ NONCE_TOKEN: 'NONCE_SECURED_BY_OIDC_SERVICE_WORKER',
15
+ CODE_VERIFIER: 'CODE_VERIFIER_SECURED_BY_OIDC_SERVICE_WORKER',
16
+ };
17
+
18
+ type TokenRenewModeType = {
19
+ readonly access_token_or_id_token_invalid: string;
20
+ readonly access_token_invalid: string;
21
+ readonly id_token_invalid: string;
22
+ };
23
+
24
+ const TokenRenewMode: TokenRenewModeType = {
25
+ access_token_or_id_token_invalid: 'access_token_or_id_token_invalid',
26
+ access_token_invalid: 'access_token_invalid',
27
+ id_token_invalid: 'id_token_invalid',
28
+ };
29
+
30
+ const openidWellknownUrlEndWith = '/.well-known/openid-configuration';
31
+
32
+ export { acceptAnyDomainToken, openidWellknownUrlEndWith, scriptFilename, TOKEN, TokenRenewMode };
package/src/types.ts ADDED
@@ -0,0 +1,101 @@
1
+ export type DomainDetails = {
2
+ domains?: Domain[];
3
+ oidcDomains?: Domain[];
4
+ accessTokenDomains?: Domain[];
5
+ showAccessToken: boolean;
6
+ }
7
+
8
+ export type Domain = string | RegExp;
9
+
10
+ export type TrustedDomains = {
11
+ [key: string]: Domain[] | DomainDetails;
12
+ } | null;
13
+
14
+ export type OidcServerConfiguration = {
15
+ revocationEndpoint: string;
16
+ issuer: string;
17
+ authorizationEndpoint: string;
18
+ tokenEndpoint: string;
19
+ userInfoEndpoint: string;
20
+ }
21
+
22
+ export type OidcConfiguration = {
23
+ token_renew_mode: string;
24
+ service_worker_convert_all_requests_to_cors: boolean;
25
+ }
26
+
27
+ // Uncertain why the Headers interface in lib.webworker.d.ts does not have a keys() function, so extending
28
+ export interface FetchHeaders extends Headers {
29
+ keys(): string[];
30
+ }
31
+
32
+ export type Status = 'LOGGED' | 'LOGGED_IN' | 'LOGGED_OUT' | 'NOT_CONNECTED' | 'LOGOUT_FROM_ANOTHER_TAB' | 'SESSION_LOST' | 'REQUIRE_SYNC_TOKENS' | 'FORCE_REFRESH' | null;
33
+ export type MessageEventType = 'clear' | 'init' | 'setState' | 'getState' | 'setCodeVerifier' | 'getCodeVerifier' | 'setSessionState' | 'getSessionState' | 'setNonce' | 'getNonce';
34
+
35
+ export type MessageData = {
36
+ status: Status;
37
+ oidcServerConfiguration: OidcServerConfiguration;
38
+ oidcConfiguration: OidcConfiguration;
39
+ where: string;
40
+ state: string;
41
+ codeVerifier: string;
42
+ sessionState: string;
43
+ nonce: Nonce;
44
+ }
45
+
46
+ export type MessageEventData = {
47
+ configurationName: string;
48
+ type: MessageEventType;
49
+ data: MessageData;
50
+ }
51
+
52
+ export type Nonce = {
53
+ nonce: string;
54
+ } | null;
55
+
56
+ export type OidcConfig = {
57
+ configurationName: string;
58
+ tokens: Tokens | null;
59
+ status: Status;
60
+ state: string | null;
61
+ codeVerifier: string | null;
62
+ nonce: Nonce;
63
+ oidcServerConfiguration: OidcServerConfiguration | null;
64
+ oidcConfiguration?: OidcConfiguration;
65
+ sessionState?: string | null;
66
+ items?: MessageData;
67
+ hideAccessToken: boolean;
68
+ }
69
+
70
+ export type IdTokenPayload = {
71
+ iss: string;
72
+ /**
73
+ * (Expiration Time) Claim
74
+ */
75
+ exp: number;
76
+ /**
77
+ * (Issued At) Claim
78
+ */
79
+ iat: number;
80
+ nonce: string | null;
81
+ }
82
+
83
+ export type AccessTokenPayload = {
84
+ exp: number;
85
+ sub: string;
86
+ }
87
+
88
+ export type Tokens = {
89
+ issued_at: number;
90
+ access_token: string;
91
+ accessTokenPayload: AccessTokenPayload | null;
92
+ id_token: null | string;
93
+ idTokenPayload: IdTokenPayload;
94
+ refresh_token?: string;
95
+ expiresAt: number;
96
+ expires_in: number;
97
+ };
98
+
99
+ export type Database = {
100
+ [key: string]: OidcConfig;
101
+ }
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { replaceCodeVerifier } from '../codeVerifier';
4
+
5
+ describe('replaceCodeVerifier should', () => {
6
+ it.each([
7
+ { body: 'code=F5CDCDB9AADB9ADA59560DE80CAAA6688BC5C8BA1CC1C1F9839F7E7B32171B3D-1&grant_type=authorization_code&client_id=interactive.public.short&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fauthentication%2Fcallback&code_verifier=ONskPfcbfAYPp5xqhpMstHSz017896R7sy3wqrRdqC8lYB8yQciCCNLooqLC9qHFTF2FFhDQP4m8PEFNSry8eoCbQ9baYcoWjF1bEH6vGWExdTIMqauicjeVxqz58FO8', bodyExpected: 'code=F5CDCDB9AADB9ADA59560DE80CAAA6688BC5C8BA1CC1C1F9839F7E7B32171B3D-1&grant_type=authorization_code&client_id=interactive.public.short&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fauthentication%2Fcallback&code_verifier=1234' },
8
+ { body: 'code=F5CDCDB9AADB9ADA59560DE80CAAA6688BC5C8BA1CC1C1F9839F7E7B32171B3D-1&code_verifier=ONskPfcbfAYPp5xqhpMstHSz017896R7sy3wqrRdqC8lYB8yQciCCNLooqLC9qHFTF2FFhDQP4m8PEFNSry8eoCbQ9baYcoWjF1bEH6vGWExdTIMqauicjeVxqz58FO8&grant_type=authorization_code&client_id=interactive.public.short&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fauthentication%2Fcallback', bodyExpected: 'code=F5CDCDB9AADB9ADA59560DE80CAAA6688BC5C8BA1CC1C1F9839F7E7B32171B3D-1&code_verifier=1234&grant_type=authorization_code&client_id=interactive.public.short&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fauthentication%2Fcallback' },
9
+ ])('inject new codeVerifier', async ({ body, bodyExpected }) => {
10
+ const result = replaceCodeVerifier(body, '1234');
11
+ expect(bodyExpected).toEqual(result);
12
+ });
13
+ });
@@ -0,0 +1,90 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { openidWellknownUrlEndWith } from '../../constants';
4
+ import { checkDomain, getCurrentDatabaseDomain } from '..';
5
+ import { Database, OidcServerConfiguration, Tokens, TrustedDomains } from './../../types';
6
+
7
+ describe('domains', () => {
8
+ describe('can check domain matches', () => {
9
+ it('can check string domains and return void', () => {
10
+ const result = () =>
11
+ checkDomain(
12
+ ['https://securesite.com:3000'],
13
+ 'https://securesite.com:3000',
14
+ );
15
+ expect(result()).toBeUndefined();
16
+ });
17
+
18
+ it('can check regExp domains and return void when valid', () => {
19
+ const result = () =>
20
+ checkDomain(
21
+ [/^https:\/\/securesite\.com/],
22
+ 'https://securesite.com:3000',
23
+ );
24
+ expect(result()).toBeUndefined();
25
+ });
26
+
27
+ it('will throw error when domain is not trusted', () => {
28
+ const result = () =>
29
+ checkDomain(
30
+ ['https://notsecuresite.com'],
31
+ 'https://securesite.com:3000',
32
+ );
33
+ expect(result).toThrowError();
34
+ });
35
+
36
+ it('will return void when endpoint is falsy', () => {
37
+ const result = () => checkDomain(['https://securesite.com:3000'], '');
38
+ expect(result()).toBeUndefined();
39
+ });
40
+ });
41
+ describe('getCurrentDatabaseDomain', () => {
42
+ const db: Database = {
43
+ default: {
44
+ configurationName: 'config',
45
+ tokens: {} as Tokens,
46
+ status: 'NOT_CONNECTED',
47
+ state: null,
48
+ codeVerifier: null,
49
+ nonce: null,
50
+ oidcServerConfiguration: {} as OidcServerConfiguration,
51
+ hideAccessToken: true,
52
+ },
53
+ };
54
+
55
+ it('will return null when url ends with openidWellknownUrlEndWith', () => {
56
+ const trustedDomains: TrustedDomains = {
57
+ default: [
58
+ 'https://demo.duendesoftware.com',
59
+ 'https://kdhttps.auth0.com',
60
+ ],
61
+ };
62
+ const url = 'http://url' + openidWellknownUrlEndWith;
63
+ expect(getCurrentDatabaseDomain(db, url, trustedDomains)).toBeNull();
64
+ });
65
+
66
+ it('will test urls against domains list if accessTokenDomains list is not present', () => {
67
+ const trustedDomains: TrustedDomains = {
68
+ default: {
69
+ domains: ['https://domain'],
70
+ showAccessToken: false,
71
+ },
72
+ };
73
+
74
+ expect(getCurrentDatabaseDomain(db, 'https://domain/test', trustedDomains)).toBe(db.default);
75
+ });
76
+
77
+ it('will test urls against accessTokenDomains list if it is present and ignore domains list', () => {
78
+ const trustedDomains: TrustedDomains = {
79
+ default: {
80
+ domains: ['https://domain'],
81
+ accessTokenDomains: ['https://myapi'],
82
+ showAccessToken: false,
83
+ },
84
+ };
85
+
86
+ expect(getCurrentDatabaseDomain(db, 'https://myapi/test', trustedDomains)).toBe(db.default);
87
+ expect(getCurrentDatabaseDomain(db, 'https://domain/test', trustedDomains)).toBeNull();
88
+ });
89
+ });
90
+ });