@inrupt/solid-client-authn-browser 1.17.2 → 1.17.4

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 (30) hide show
  1. package/dist/ClientAuthentication.d.ts +7 -0
  2. package/dist/Session.d.ts +174 -0
  3. package/dist/defaultSession.d.ts +70 -0
  4. package/dist/dependencies.d.ts +12 -0
  5. package/dist/index.js +783 -16
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +785 -18
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/login/oidc/AggregateOidcHandler.d.ts +10 -0
  10. package/dist/login/oidc/AggregateRedirectHandler.d.ts +10 -0
  11. package/dist/login/oidc/ClientRegistrar.d.ts +7 -0
  12. package/dist/login/oidc/IssuerConfigFetcher.d.ts +10 -0
  13. package/dist/login/oidc/OidcLoginHandler.d.ts +10 -0
  14. package/dist/login/oidc/Redirector.d.ts +7 -0
  15. package/dist/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.d.ts +3 -0
  16. package/dist/login/oidc/incomingRedirectHandler/ErrorOidcHandler.d.ts +10 -0
  17. package/dist/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.d.ts +10 -0
  18. package/dist/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.d.ts +12 -0
  19. package/dist/login/oidc/refresh/TokenRefresher.d.ts +7 -0
  20. package/dist/logout/buildRpInitiatedLogout.d.ts +7 -0
  21. package/dist/sessionInfo/SessionInfoManager.d.ts +18 -0
  22. package/dist/solid-client-authn.bundle.js +7 -7
  23. package/dist/solid-client-authn.bundle.js.map +1 -1
  24. package/dist/storage/BrowserStorage.d.ts +7 -0
  25. package/dist/storage/StorageUtility.d.ts +14 -0
  26. package/dist/util/UuidGenerator.d.ts +6 -0
  27. package/package.json +7 -7
  28. package/src/ClientAuthentication.spec.ts +33 -31
  29. package/src/ClientAuthentication.ts +19 -17
  30. package/src/Session.spec.ts +10 -13
package/dist/index.js CHANGED
@@ -6,29 +6,98 @@ var EventEmitter = require('events');
6
6
  var oidcClientExt = require('@inrupt/oidc-client-ext');
7
7
  var universalFetch = require('@inrupt/universal-fetch');
8
8
 
9
+ //
10
+ // Copyright Inrupt Inc.
11
+ //
12
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ // of this software and associated documentation files (the "Software"), to deal in
14
+ // the Software without restriction, including without limitation the rights to use,
15
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
16
+ // Software, and to permit persons to whom the Software is furnished to do so,
17
+ // subject to the following conditions:
18
+ //
19
+ // The above copyright notice and this permission notice shall be included in
20
+ // all copies or substantial portions of the Software.
21
+ //
22
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
23
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
24
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+ //
29
+ /**
30
+ * This class in a no-value-added extension of StorageUtility from the core module.
31
+ * The reason it has to be declared is for TSyringe to find the decorators in the
32
+ * same modules as where the dependency container is declared (in this case,
33
+ * the browser module, with the dependancy container in dependencies.ts).
34
+ * @hidden
35
+ */
9
36
  class StorageUtilityBrowser extends solidClientAuthnCore.StorageUtility {
10
37
  constructor(secureStorage, insecureStorage) {
11
38
  super(secureStorage, insecureStorage);
12
39
  }
13
40
  }
14
41
 
42
+ //
43
+ // Copyright Inrupt Inc.
44
+ //
45
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
46
+ // of this software and associated documentation files (the "Software"), to deal in
47
+ // the Software without restriction, including without limitation the rights to use,
48
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
49
+ // Software, and to permit persons to whom the Software is furnished to do so,
50
+ // subject to the following conditions:
51
+ //
52
+ // The above copyright notice and this permission notice shall be included in
53
+ // all copies or substantial portions of the Software.
54
+ //
55
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
56
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
57
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
58
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
59
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
60
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
61
+ //
62
+ /**
63
+ * @hidden
64
+ */
15
65
  class ClientAuthentication extends solidClientAuthnCore.ClientAuthentication {
16
66
  constructor() {
17
67
  super(...arguments);
68
+ // Define these functions as properties so that they don't get accidentally re-bound.
69
+ // Isn't Javascript fun?
18
70
  this.login = async (options, eventEmitter) => {
19
71
  var _a, _b;
72
+ // In order to get a clean start, make sure that the session is logged out
73
+ // on login.
74
+ // But we may want to preserve our client application info, particularly if
75
+ // we used Dynamic Client Registration to register (since we don't
76
+ // necessarily want the user to have to register this app each time they
77
+ // login).
20
78
  await this.sessionInfoManager.clear(options.sessionId);
21
- const redirectUrl = (_a = options.redirectUrl) !== null && _a !== void 0 ? _a : oidcClientExt.removeOidcQueryParam(window.location.href);
79
+ // In the case of the user hitting the 'back' button in their browser, they
80
+ // could return to a previous redirect URL that contains OIDC params that
81
+ // are now longer valid. To be safe, strip relevant params now.
82
+ // If the user is providing a redirect IRI, it should not be modified, so
83
+ // normalization only applies if we default to the current location (which is
84
+ // a bad practice and should be discouraged).
85
+ const redirectUrl = (_a = options.redirectUrl) !== null && _a !== void 0 ? _a : oidcClientExt.normalizeCallbackUrl(window.location.href);
22
86
  if (!solidClientAuthnCore.isValidRedirectUrl(redirectUrl)) {
23
87
  throw new Error(`${redirectUrl} is not a valid redirect URL, it is either a malformed IRI, includes a hash fragment, or reserved query parameters ('code' or 'state').`);
24
88
  }
25
89
  await this.loginHandler.handle({
26
90
  ...options,
27
91
  redirectUrl,
92
+ // If no clientName is provided, the clientId may be used instead.
28
93
  clientName: (_b = options.clientName) !== null && _b !== void 0 ? _b : options.clientId,
29
94
  eventEmitter,
30
95
  });
31
96
  };
97
+ // Collects session information from storage, and returns them. Returns null
98
+ // if the expected information cannot be found.
99
+ // Note that the ID token is not stored, which means the session information
100
+ // cannot be validated at this point.
32
101
  this.validateCurrentSession = async (currentSessionId) => {
33
102
  const sessionInfo = await this.sessionInfoManager.get(currentSessionId);
34
103
  if (sessionInfo === undefined ||
@@ -41,9 +110,14 @@ class ClientAuthentication extends solidClientAuthnCore.ClientAuthentication {
41
110
  this.handleIncomingRedirect = async (url, eventEmitter) => {
42
111
  try {
43
112
  const redirectInfo = await this.redirectHandler.handle(url, eventEmitter);
113
+ // The `FallbackRedirectHandler` directly returns the global `fetch` for
114
+ // his value, so we should ensure it's bound to `window` rather than to
115
+ // ClientAuthentication, to avoid the following error:
116
+ // > 'fetch' called on an object that does not implement interface Window.
44
117
  this.fetch = redirectInfo.fetch.bind(window);
45
118
  this.boundLogout = redirectInfo.getLogoutUrl;
46
- this.cleanUrlAfterRedirect(url);
119
+ // Strip the oauth params:
120
+ await this.cleanUrlAfterRedirect(url);
47
121
  return {
48
122
  isLoggedIn: redirectInfo.isLoggedIn,
49
123
  webId: redirectInfo.webId,
@@ -52,31 +126,69 @@ class ClientAuthentication extends solidClientAuthnCore.ClientAuthentication {
52
126
  };
53
127
  }
54
128
  catch (err) {
55
- this.cleanUrlAfterRedirect(url);
129
+ // Strip the oauth params:
130
+ await this.cleanUrlAfterRedirect(url);
131
+ // FIXME: EVENTS.ERROR should be errorCode, errorDescription
132
+ //
133
+ // I'm not sure if "redirect" is a good error code, and in theory `err`
134
+ // maybe an Error object and not a string; Maybe we want to just hardcode
135
+ // a description instead?
56
136
  eventEmitter.emit(solidClientAuthnCore.EVENTS.ERROR, "redirect", err);
57
137
  return undefined;
58
138
  }
59
139
  };
60
140
  }
61
- cleanUrlAfterRedirect(url) {
62
- const cleanedUpUrl = new URL(url);
63
- cleanedUpUrl.searchParams.delete("state");
64
- cleanedUpUrl.searchParams.delete("code");
65
- cleanedUpUrl.searchParams.delete("id_token");
66
- cleanedUpUrl.searchParams.delete("access_token");
67
- cleanedUpUrl.searchParams.delete("error");
68
- cleanedUpUrl.searchParams.delete("error_description");
69
- cleanedUpUrl.searchParams.delete("iss");
70
- window.history.replaceState(null, "", cleanedUpUrl.toString());
141
+ async cleanUrlAfterRedirect(url) {
142
+ const cleanedUpUrl = solidClientAuthnCore.removeOpenIdParams(url).href;
143
+ // Remove OAuth-specific query params (since the login flow finishes with
144
+ // the browser being redirected back with OAuth2 query params (e.g. for
145
+ // 'code' and 'state'), and so if the user simply refreshes this page our
146
+ // authentication library will be called again with what are now invalid
147
+ // query parameters!).
148
+ window.history.replaceState(null, "", cleanedUpUrl);
149
+ while (window.location.href !== cleanedUpUrl) {
150
+ // Poll the current URL every ms. Active polling is required because
151
+ // window.history.replaceState is asynchronous, but the associated
152
+ // 'popstate' event which should be listened to is only sent on active
153
+ // navigation, which we will not have here.
154
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event#when_popstate_is_sent
155
+ // eslint-disable-next-line no-await-in-loop
156
+ await new Promise((resolve) => {
157
+ setTimeout(() => resolve(), 1);
158
+ });
159
+ }
71
160
  }
72
161
  }
73
162
 
163
+ //
164
+ // Copyright Inrupt Inc.
165
+ //
166
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
167
+ // of this software and associated documentation files (the "Software"), to deal in
168
+ // the Software without restriction, including without limitation the rights to use,
169
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
170
+ // Software, and to permit persons to whom the Software is furnished to do so,
171
+ // subject to the following conditions:
172
+ //
173
+ // The above copyright notice and this permission notice shall be included in
174
+ // all copies or substantial portions of the Software.
175
+ //
176
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
177
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
178
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
179
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
180
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
181
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
182
+ //
74
183
  function hasIssuer(options) {
75
184
  return typeof options.oidcIssuer === "string";
76
185
  }
77
186
  function hasRedirectUrl(options) {
78
187
  return typeof options.redirectUrl === "string";
79
188
  }
189
+ /**
190
+ * @hidden
191
+ */
80
192
  class OidcLoginHandler {
81
193
  constructor(storageUtility, oidcHandler, issuerConfigFetcher, clientRegistrar) {
82
194
  this.storageUtility = storageUtility;
@@ -98,22 +210,57 @@ class OidcLoginHandler {
98
210
  if (!hasRedirectUrl(options)) {
99
211
  throw new solidClientAuthnCore.ConfigurationError(`OidcLoginHandler requires a redirect URL: missing property 'redirectUrl' in ${JSON.stringify(options)}`);
100
212
  }
213
+ // Fetch issuer config.
101
214
  const issuerConfig = await this.issuerConfigFetcher.fetchConfig(options.oidcIssuer);
102
215
  const clientRegistration = await solidClientAuthnCore.handleRegistration(options, issuerConfig, this.storageUtility, this.clientRegistrar);
216
+ // Construct OIDC Options
103
217
  const OidcOptions = {
218
+ // Note that here, the issuer is not the one from the received options, but
219
+ // from the issuer's config. This enforces the canonical URL is used and stored,
220
+ // which is also the one present in the ID token, so storing a technically
221
+ // valid, but different issuer URL (e.g. using a trailing slash or not) now
222
+ // could prevent from validating the ID token later.
104
223
  issuer: issuerConfig.issuer,
224
+ // TODO: differentiate if DPoP should be true
105
225
  dpop: options.tokenType.toLowerCase() === "dpop",
106
226
  ...options,
107
227
  issuerConfiguration: issuerConfig,
108
228
  client: clientRegistration,
109
229
  };
230
+ // Call proper OIDC Handler
110
231
  return this.oidcHandler.handle(OidcOptions);
111
232
  }
112
233
  }
113
234
 
235
+ //
236
+ // Copyright Inrupt Inc.
237
+ //
238
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
239
+ // of this software and associated documentation files (the "Software"), to deal in
240
+ // the Software without restriction, including without limitation the rights to use,
241
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
242
+ // Software, and to permit persons to whom the Software is furnished to do so,
243
+ // subject to the following conditions:
244
+ //
245
+ // The above copyright notice and this permission notice shall be included in
246
+ // all copies or substantial portions of the Software.
247
+ //
248
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
249
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
250
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
251
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
252
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
253
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
254
+ //
255
+ /**
256
+ * @hidden
257
+ * Authorization code flow spec: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
258
+ * PKCE: https://tools.ietf.org/html/rfc7636
259
+ */
114
260
  class AuthorizationCodeWithPkceOidcHandler extends solidClientAuthnCore.AuthorizationCodeWithPkceOidcHandlerBase {
115
261
  async handle(oidcLoginOptions) {
116
262
  var _a;
263
+ /* eslint-disable camelcase */
117
264
  const oidcOptions = {
118
265
  authority: oidcLoginOptions.issuer.toString(),
119
266
  client_id: oidcLoginOptions.client.clientId,
@@ -123,28 +270,58 @@ class AuthorizationCodeWithPkceOidcHandler extends solidClientAuthnCore.Authoriz
123
270
  response_type: "code",
124
271
  scope: solidClientAuthnCore.DEFAULT_SCOPES,
125
272
  filterProtocolClaims: true,
273
+ // The userinfo endpoint on NSS fails, so disable this for now
274
+ // Note that in Solid, information should be retrieved from the
275
+ // profile referenced by the WebId.
126
276
  loadUserInfo: false,
127
277
  code_verifier: true,
128
278
  prompt: (_a = oidcLoginOptions.prompt) !== null && _a !== void 0 ? _a : "consent",
129
279
  };
280
+ /* eslint-enable camelcase */
130
281
  const oidcClientLibrary = new oidcClientExt.OidcClient(oidcOptions);
131
282
  try {
132
283
  const signingRequest = await oidcClientLibrary.createSigninRequest();
284
+ // Make sure to await the promise before returning so that the error is caught.
133
285
  return await this.handleRedirect({
134
286
  oidcLoginOptions,
287
+ // eslint-disable-next-line no-underscore-dangle
135
288
  state: signingRequest.state._id,
289
+ // eslint-disable-next-line no-underscore-dangle
136
290
  codeVerifier: signingRequest.state._code_verifier,
137
291
  targetUrl: signingRequest.url.toString(),
138
292
  });
139
293
  }
140
294
  catch (err) {
295
+ // eslint-disable-next-line no-console
141
296
  console.error(err);
142
297
  }
298
+ // The login is only completed AFTER redirect, so nothing to return here.
143
299
  return undefined;
144
300
  }
145
301
  }
146
302
 
303
+ //
304
+ // Copyright Inrupt Inc.
305
+ //
306
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
307
+ // of this software and associated documentation files (the "Software"), to deal in
308
+ // the Software without restriction, including without limitation the rights to use,
309
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
310
+ // Software, and to permit persons to whom the Software is furnished to do so,
311
+ // subject to the following conditions:
312
+ //
313
+ // The above copyright notice and this permission notice shall be included in
314
+ // all copies or substantial portions of the Software.
315
+ //
316
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
317
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
318
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
319
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
320
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
321
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
322
+ //
147
323
  const WELL_KNOWN_OPENID_CONFIG = ".well-known/openid-configuration";
324
+ /* eslint-disable camelcase */
148
325
  const issuerConfigKeyMap = {
149
326
  issuer: {
150
327
  toKey: "issuer",
@@ -232,10 +409,15 @@ const issuerConfigKeyMap = {
232
409
  convertToUrl: true,
233
410
  },
234
411
  };
412
+ /* eslint-enable camelcase */
235
413
  function processConfig(config) {
236
414
  const parsedConfig = {};
237
415
  Object.keys(config).forEach((key) => {
238
416
  if (issuerConfigKeyMap[key]) {
417
+ // TODO: PMcB55: Validate URL if "issuerConfigKeyMap[key].convertToUrl"
418
+ // if (issuerConfigKeyMap[key].convertToUrl) {
419
+ // validateUrl(config[key]);
420
+ // }
239
421
  parsedConfig[issuerConfigKeyMap[key].toKey] = config[key];
240
422
  }
241
423
  });
@@ -244,33 +426,71 @@ function processConfig(config) {
244
426
  }
245
427
  return parsedConfig;
246
428
  }
429
+ /**
430
+ * @hidden
431
+ */
247
432
  class IssuerConfigFetcher {
248
433
  constructor(storageUtility) {
249
434
  this.storageUtility = storageUtility;
250
435
  this.storageUtility = storageUtility;
251
436
  }
437
+ // This method needs no state (so can be static), and can be exposed to allow
438
+ // callers to know where this implementation puts state it needs.
252
439
  static getLocalStorageKey(issuer) {
253
440
  return `issuerConfig:${issuer}`;
254
441
  }
255
442
  async fetchConfig(issuer) {
256
443
  let issuerConfig;
257
- const openIdConfigUrl = new URL(WELL_KNOWN_OPENID_CONFIG, issuer.endsWith("/") ? issuer : `${issuer}/`).href;
444
+ const openIdConfigUrl = new URL(WELL_KNOWN_OPENID_CONFIG,
445
+ // Make sure to append a slash at issuer URL, so that the .well-known URL
446
+ // includes the full issuer path. See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig.
447
+ issuer.endsWith("/") ? issuer : `${issuer}/`).href;
258
448
  const issuerConfigRequestBody = await universalFetch.fetch.call(globalThis, openIdConfigUrl);
449
+ // Check the validity of the fetched config
259
450
  try {
260
451
  issuerConfig = processConfig(await issuerConfigRequestBody.json());
261
452
  }
262
453
  catch (err) {
263
454
  throw new solidClientAuthnCore.ConfigurationError(`[${issuer.toString()}] has an invalid configuration: ${err.message}`);
264
455
  }
456
+ // Update store with fetched config
265
457
  await this.storageUtility.set(IssuerConfigFetcher.getLocalStorageKey(issuer), JSON.stringify(issuerConfig));
266
458
  return issuerConfig;
267
459
  }
268
460
  }
269
461
 
462
+ //
463
+ // Copyright Inrupt Inc.
464
+ //
465
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
466
+ // of this software and associated documentation files (the "Software"), to deal in
467
+ // the Software without restriction, including without limitation the rights to use,
468
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
469
+ // Software, and to permit persons to whom the Software is furnished to do so,
470
+ // subject to the following conditions:
471
+ //
472
+ // The above copyright notice and this permission notice shall be included in
473
+ // all copies or substantial portions of the Software.
474
+ //
475
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
476
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
477
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
478
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
479
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
480
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
481
+ //
482
+ /**
483
+ * @param sessionId
484
+ * @param storage
485
+ * @hidden
486
+ */
270
487
  async function clear(sessionId, storage) {
271
488
  await solidClientAuthnCore.clear(sessionId, storage);
272
489
  await oidcClientExt.clearOidcPersistentStorage();
273
490
  }
491
+ /**
492
+ * @hidden
493
+ */
274
494
  class SessionInfoManager extends solidClientAuthnCore.SessionInfoManagerBase {
275
495
  async get(sessionId) {
276
496
  const [isLoggedIn, webId, clientId, clientSecret, redirectUrl, refreshToken, issuer, tokenType,] = await Promise.all([
@@ -300,6 +520,9 @@ class SessionInfoManager extends solidClientAuthnCore.SessionInfoManagerBase {
300
520
  }),
301
521
  ]);
302
522
  if (typeof redirectUrl === "string" && !solidClientAuthnCore.isValidRedirectUrl(redirectUrl)) {
523
+ // This resolves the issue for people experiencing https://github.com/inrupt/solid-client-authn-js/issues/2891.
524
+ // An invalid redirect URL is present in the storage, and the session should
525
+ // be cleared to get a fresh start. This will require the user to log back in.
303
526
  await Promise.all([
304
527
  this.storageUtility.deleteAllUserData(sessionId, { secure: false }),
305
528
  this.storageUtility.deleteAllUserData(sessionId, { secure: true }),
@@ -324,17 +547,52 @@ class SessionInfoManager extends solidClientAuthnCore.SessionInfoManagerBase {
324
547
  issuer,
325
548
  clientAppId: clientId,
326
549
  clientAppSecret: clientSecret,
550
+ // Default the token type to DPoP if unspecified.
327
551
  tokenType: tokenType !== null && tokenType !== void 0 ? tokenType : "DPoP",
328
552
  };
329
553
  }
554
+ /**
555
+ * This function removes all session-related information from storage.
556
+ * @param sessionId the session identifier
557
+ * @param storage the storage where session info is stored
558
+ * @hidden
559
+ */
330
560
  async clear(sessionId) {
331
561
  return clear(sessionId, this.storageUtility);
332
562
  }
333
563
  }
334
564
 
565
+ //
566
+ // Copyright Inrupt Inc.
567
+ //
568
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
569
+ // of this software and associated documentation files (the "Software"), to deal in
570
+ // the Software without restriction, including without limitation the rights to use,
571
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
572
+ // Software, and to permit persons to whom the Software is furnished to do so,
573
+ // subject to the following conditions:
574
+ //
575
+ // The above copyright notice and this permission notice shall be included in
576
+ // all copies or substantial portions of the Software.
577
+ //
578
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
579
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
580
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
581
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
582
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
583
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
584
+ //
585
+ /**
586
+ * This class handles redirect IRIs without any query params, and returns an unauthenticated
587
+ * session. It serves as a fallback so that consuming libraries don't have to test
588
+ * for the query params themselves, and can always try to use them as a redirect IRI.
589
+ * @hidden
590
+ */
335
591
  class FallbackRedirectHandler {
336
592
  async canHandle(redirectUrl) {
337
593
  try {
594
+ // The next URL object is built for validating it.
595
+ // eslint-disable-next-line no-new
338
596
  new URL(redirectUrl);
339
597
  return true;
340
598
  }
@@ -342,12 +600,44 @@ class FallbackRedirectHandler {
342
600
  throw new Error(`[${redirectUrl}] is not a valid URL, and cannot be used as a redirect URL: ${e}`);
343
601
  }
344
602
  }
345
- async handle(_redirectUrl) {
603
+ async handle(
604
+ // The argument is ignored, but must be present to implement the interface
605
+ _redirectUrl) {
346
606
  return solidClientAuthnCore.getUnauthenticatedSession();
347
607
  }
348
608
  }
349
609
 
610
+ //
611
+ // Copyright Inrupt Inc.
612
+ //
613
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
614
+ // of this software and associated documentation files (the "Software"), to deal in
615
+ // the Software without restriction, including without limitation the rights to use,
616
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
617
+ // Software, and to permit persons to whom the Software is furnished to do so,
618
+ // subject to the following conditions:
619
+ //
620
+ // The above copyright notice and this permission notice shall be included in
621
+ // all copies or substantial portions of the Software.
622
+ //
623
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
624
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
625
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
626
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
627
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
628
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
629
+ //
630
+ /**
631
+ * @hidden
632
+ * @packageDocumentation
633
+ */
634
+ // FIXME: The following doesn't work in the browser, it results in all the fetches
635
+ // being unauthenticated. This should be looked into when migrating to universal-fetch.
636
+ // import { fetch } from "cross-fetch";
350
637
  const globalFetch = (...args) => universalFetch.fetch.call(globalThis, ...args);
638
+ /**
639
+ * @hidden
640
+ */
351
641
  class AuthCodeRedirectHandler {
352
642
  constructor(storageUtility, sessionInfoManager, issuerConfigFetcher, clientRegistrar, tokerRefresher) {
353
643
  this.storageUtility = storageUtility;
@@ -397,10 +687,15 @@ class AuthCodeRedirectHandler {
397
687
  if (isDpop) {
398
688
  tokens = await oidcClientExt.getDpopToken(issuerConfig, client, {
399
689
  grantType: "authorization_code",
690
+ // We rely on our 'canHandle' function checking that the OAuth 'code'
691
+ // parameter is present in our query string.
400
692
  code: url.searchParams.get("code"),
401
693
  codeVerifier,
402
694
  redirectUrl: storedRedirectIri,
403
695
  });
696
+ // Delete oidc-client-specific session information from storage. This is
697
+ // done automatically when retrieving a bearer token, but since the DPoP
698
+ // binding uses our custom code, this needs to be done manually.
404
699
  window.localStorage.removeItem(`oidc.${oauthState}`);
405
700
  }
406
701
  else {
@@ -441,12 +736,58 @@ class AuthCodeRedirectHandler {
441
736
  }
442
737
  }
443
738
 
739
+ //
740
+ // Copyright Inrupt Inc.
741
+ //
742
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
743
+ // of this software and associated documentation files (the "Software"), to deal in
744
+ // the Software without restriction, including without limitation the rights to use,
745
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
746
+ // Software, and to permit persons to whom the Software is furnished to do so,
747
+ // subject to the following conditions:
748
+ //
749
+ // The above copyright notice and this permission notice shall be included in
750
+ // all copies or substantial portions of the Software.
751
+ //
752
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
753
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
754
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
755
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
756
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
757
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
758
+ //
759
+ /**
760
+ * @hidden
761
+ */
444
762
  class AggregateRedirectHandler extends solidClientAuthnCore.AggregateHandler {
445
763
  constructor(redirectHandlers) {
446
764
  super(redirectHandlers);
447
765
  }
448
766
  }
449
767
 
768
+ //
769
+ // Copyright Inrupt Inc.
770
+ //
771
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
772
+ // of this software and associated documentation files (the "Software"), to deal in
773
+ // the Software without restriction, including without limitation the rights to use,
774
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
775
+ // Software, and to permit persons to whom the Software is furnished to do so,
776
+ // subject to the following conditions:
777
+ //
778
+ // The above copyright notice and this permission notice shall be included in
779
+ // all copies or substantial portions of the Software.
780
+ //
781
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
782
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
783
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
784
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
785
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
786
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
787
+ //
788
+ /**
789
+ * @hidden
790
+ */
450
791
  class BrowserStorage {
451
792
  get storage() {
452
793
  return window.localStorage;
@@ -462,6 +803,29 @@ class BrowserStorage {
462
803
  }
463
804
  }
464
805
 
806
+ //
807
+ // Copyright Inrupt Inc.
808
+ //
809
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
810
+ // of this software and associated documentation files (the "Software"), to deal in
811
+ // the Software without restriction, including without limitation the rights to use,
812
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
813
+ // Software, and to permit persons to whom the Software is furnished to do so,
814
+ // subject to the following conditions:
815
+ //
816
+ // The above copyright notice and this permission notice shall be included in
817
+ // all copies or substantial portions of the Software.
818
+ //
819
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
820
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
821
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
822
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
823
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
824
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
825
+ //
826
+ /**
827
+ * @hidden
828
+ */
465
829
  class Redirector {
466
830
  redirect(redirectUrl, options) {
467
831
  if (options && options.handleRedirect) {
@@ -476,19 +840,49 @@ class Redirector {
476
840
  }
477
841
  }
478
842
 
843
+ //
844
+ // Copyright Inrupt Inc.
845
+ //
846
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
847
+ // of this software and associated documentation files (the "Software"), to deal in
848
+ // the Software without restriction, including without limitation the rights to use,
849
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
850
+ // Software, and to permit persons to whom the Software is furnished to do so,
851
+ // subject to the following conditions:
852
+ //
853
+ // The above copyright notice and this permission notice shall be included in
854
+ // all copies or substantial portions of the Software.
855
+ //
856
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
857
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
858
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
859
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
860
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
861
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
862
+ //
863
+ /**
864
+ * @hidden
865
+ */
479
866
  class ClientRegistrar {
480
867
  constructor(storageUtility) {
481
868
  this.storageUtility = storageUtility;
482
869
  this.storageUtility = storageUtility;
483
870
  }
484
871
  async getClient(options, issuerConfig) {
485
- const [storedClientId, storedClientSecret,] = await Promise.all([
872
+ // If client secret and/or client id are stored in storage, use those.
873
+ const [storedClientId, storedClientSecret,
874
+ // storedClientName,
875
+ ] = await Promise.all([
486
876
  this.storageUtility.getForUser(options.sessionId, "clientId", {
487
877
  secure: false,
488
878
  }),
489
879
  this.storageUtility.getForUser(options.sessionId, "clientSecret", {
490
880
  secure: false,
491
881
  }),
882
+ // this.storageUtility.getForUser(options.sessionId, "clientName", {
883
+ // // FIXME: figure out how to persist secure storage at reload
884
+ // secure: false,
885
+ // }),
492
886
  ]);
493
887
  if (storedClientId) {
494
888
  return {
@@ -499,6 +893,7 @@ class ClientRegistrar {
499
893
  }
500
894
  try {
501
895
  const registeredClient = await oidcClientExt.registerClient(options, issuerConfig);
896
+ // Save info
502
897
  const infoToSave = {
503
898
  clientId: registeredClient.clientId,
504
899
  };
@@ -510,6 +905,9 @@ class ClientRegistrar {
510
905
  registeredClient.idTokenSignedResponseAlg;
511
906
  }
512
907
  await this.storageUtility.setForUser(options.sessionId, infoToSave, {
908
+ // FIXME: figure out how to persist secure storage at reload
909
+ // Otherwise, the client info cannot be retrieved from storage, and
910
+ // the lib tries to re-register the client on each fetch
513
911
  secure: false,
514
912
  });
515
913
  return registeredClient;
@@ -520,9 +918,36 @@ class ClientRegistrar {
520
918
  }
521
919
  }
522
920
 
921
+ //
922
+ // Copyright Inrupt Inc.
923
+ //
924
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
925
+ // of this software and associated documentation files (the "Software"), to deal in
926
+ // the Software without restriction, including without limitation the rights to use,
927
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
928
+ // Software, and to permit persons to whom the Software is furnished to do so,
929
+ // subject to the following conditions:
930
+ //
931
+ // The above copyright notice and this permission notice shall be included in
932
+ // all copies or substantial portions of the Software.
933
+ //
934
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
935
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
936
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
937
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
938
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
939
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
940
+ //
941
+ /**
942
+ * This class handles redirect IRIs without any query params, and returns an unauthenticated
943
+ * session. It serves as a fallback so that consuming libraries don't have to test
944
+ * for the query params themselves, and can always try to use them as a redirect IRI.
945
+ * @hidden
946
+ */
523
947
  class ErrorOidcHandler {
524
948
  async canHandle(redirectUrl) {
525
949
  try {
950
+ // eslint-disable-next-line no-new
526
951
  return new URL(redirectUrl).searchParams.has("error");
527
952
  }
528
953
  catch (e) {
@@ -540,6 +965,32 @@ class ErrorOidcHandler {
540
965
  }
541
966
  }
542
967
 
968
+ //
969
+ // Copyright Inrupt Inc.
970
+ //
971
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
972
+ // of this software and associated documentation files (the "Software"), to deal in
973
+ // the Software without restriction, including without limitation the rights to use,
974
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
975
+ // Software, and to permit persons to whom the Software is furnished to do so,
976
+ // subject to the following conditions:
977
+ //
978
+ // The above copyright notice and this permission notice shall be included in
979
+ // all copies or substantial portions of the Software.
980
+ //
981
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
982
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
983
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
984
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
985
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
986
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
987
+ //
988
+ // Some identifiers are not in camelcase on purpose, as they are named using the
989
+ // official names from the OIDC/OAuth2 specifications.
990
+ /* eslint-disable camelcase */
991
+ /**
992
+ * @hidden
993
+ */
543
994
  class TokenRefresher {
544
995
  constructor(storageUtility, issuerConfigFetcher, clientRegistrar) {
545
996
  this.storageUtility = storageUtility;
@@ -551,8 +1002,10 @@ class TokenRefresher {
551
1002
  }
552
1003
  async refresh(sessionId, refreshToken, dpopKey, eventEmitter) {
553
1004
  const oidcContext = await solidClientAuthnCore.loadOidcContextFromStorage(sessionId, this.storageUtility, this.issuerConfigFetcher);
1005
+ // This should also retrieve the client from storage
554
1006
  const clientInfo = await this.clientRegistrar.getClient({ sessionId }, oidcContext.issuerConfig);
555
1007
  if (refreshToken === undefined) {
1008
+ // TODO: in a next PR, look up storage for a refresh token
556
1009
  throw new Error(`Session [${sessionId}] has no refresh token to allow it to refresh its access token.`);
557
1010
  }
558
1011
  if (oidcContext.dpop && dpopKey === undefined) {
@@ -569,6 +1022,31 @@ class TokenRefresher {
569
1022
  }
570
1023
  }
571
1024
 
1025
+ //
1026
+ // Copyright Inrupt Inc.
1027
+ //
1028
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1029
+ // of this software and associated documentation files (the "Software"), to deal in
1030
+ // the Software without restriction, including without limitation the rights to use,
1031
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
1032
+ // Software, and to permit persons to whom the Software is furnished to do so,
1033
+ // subject to the following conditions:
1034
+ //
1035
+ // The above copyright notice and this permission notice shall be included in
1036
+ // all copies or substantial portions of the Software.
1037
+ //
1038
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
1039
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
1040
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
1041
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1042
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1043
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1044
+ //
1045
+ /**
1046
+ *
1047
+ * @param dependencies
1048
+ * @deprecated This function will be removed from the external API in an upcoming release.
1049
+ */
572
1050
  function getClientAuthenticationWithDependencies(dependencies) {
573
1051
  const inMemoryStorage = new solidClientAuthnCore.InMemoryStorage();
574
1052
  const secureStorage = dependencies.secureStorage || inMemoryStorage;
@@ -579,22 +1057,69 @@ function getClientAuthenticationWithDependencies(dependencies) {
579
1057
  const sessionInfoManager = new SessionInfoManager(storageUtility);
580
1058
  const tokenRefresher = new TokenRefresher(storageUtility, issuerConfigFetcher, clientRegistrar);
581
1059
  const redirector = new Redirector();
1060
+ // make new handler for redirect and login
582
1061
  const loginHandler = new OidcLoginHandler(storageUtility, new AuthorizationCodeWithPkceOidcHandler(storageUtility, redirector), issuerConfigFetcher, clientRegistrar);
583
1062
  const redirectHandler = new AggregateRedirectHandler([
584
1063
  new ErrorOidcHandler(),
585
1064
  new AuthCodeRedirectHandler(storageUtility, sessionInfoManager, issuerConfigFetcher, clientRegistrar, tokenRefresher),
1065
+ // This catch-all class will always be able to handle the
1066
+ // redirect IRI, so it must be registered last.
586
1067
  new FallbackRedirectHandler(),
587
1068
  ]);
588
1069
  return new ClientAuthentication(loginHandler, redirectHandler, new solidClientAuthnCore.IWaterfallLogoutHandler(sessionInfoManager, redirector), sessionInfoManager, issuerConfigFetcher);
589
1070
  }
590
1071
 
1072
+ //
1073
+ // Copyright Inrupt Inc.
1074
+ //
1075
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1076
+ // of this software and associated documentation files (the "Software"), to deal in
1077
+ // the Software without restriction, including without limitation the rights to use,
1078
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
1079
+ // Software, and to permit persons to whom the Software is furnished to do so,
1080
+ // subject to the following conditions:
1081
+ //
1082
+ // The above copyright notice and this permission notice shall be included in
1083
+ // all copies or substantial portions of the Software.
1084
+ //
1085
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
1086
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
1087
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
1088
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1089
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1090
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1091
+ //
591
1092
  const KEY_CURRENT_SESSION = `${solidClientAuthnCore.SOLID_CLIENT_AUTHN_KEY_PREFIX}currentSession`;
592
1093
  const KEY_CURRENT_URL = `${solidClientAuthnCore.SOLID_CLIENT_AUTHN_KEY_PREFIX}currentUrl`;
593
1094
 
1095
+ //
1096
+ // Copyright Inrupt Inc.
1097
+ //
1098
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1099
+ // of this software and associated documentation files (the "Software"), to deal in
1100
+ // the Software without restriction, including without limitation the rights to use,
1101
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
1102
+ // Software, and to permit persons to whom the Software is furnished to do so,
1103
+ // subject to the following conditions:
1104
+ //
1105
+ // The above copyright notice and this permission notice shall be included in
1106
+ // all copies or substantial portions of the Software.
1107
+ //
1108
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
1109
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
1110
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
1111
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1112
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1113
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1114
+ //
594
1115
  async function silentlyAuthenticate(sessionId, clientAuthn, session) {
595
1116
  var _a;
596
1117
  const storedSessionInfo = await clientAuthn.validateCurrentSession(sessionId);
597
1118
  if (storedSessionInfo !== null) {
1119
+ // It can be really useful to save the user's current browser location,
1120
+ // so that we can restore it after completing the silent authentication
1121
+ // on incoming redirect. This way, the user is eventually redirected back
1122
+ // to the page they were on and not to the app's redirect page.
598
1123
  window.localStorage.setItem(KEY_CURRENT_URL, window.location.href);
599
1124
  await clientAuthn.login({
600
1125
  sessionId,
@@ -612,21 +1137,68 @@ async function silentlyAuthenticate(sessionId, clientAuthn, session) {
612
1137
  function isLoggedIn(sessionInfo) {
613
1138
  return !!(sessionInfo === null || sessionInfo === void 0 ? void 0 : sessionInfo.isLoggedIn);
614
1139
  }
1140
+ /**
1141
+ * A {@link Session} object represents a user's session on an application. The session holds state, as it stores information enabling acces to private resources after login for instance.
1142
+ */
615
1143
  class Session extends EventEmitter {
1144
+ /**
1145
+ * Session object constructor. Typically called as follows:
1146
+ *
1147
+ * ```typescript
1148
+ * const session = new Session();
1149
+ * ```
1150
+ *
1151
+ * See also [getDefaultSession](https://docs.inrupt.com/developer-tools/api/javascript/solid-client-authn-browser/functions.html#getdefaultsession).
1152
+ *
1153
+ * @param sessionOptions The options enabling the correct instantiation of
1154
+ * the session. Either both storages or clientAuthentication are required. For
1155
+ * more information, see {@link ISessionOptions}.
1156
+ * @param sessionId A string uniquely identifying the session.
1157
+ *
1158
+ */
616
1159
  constructor(sessionOptions = {}, sessionId = undefined) {
617
1160
  super();
618
1161
  this.tokenRequestInProgress = false;
1162
+ /**
1163
+ * Triggers the login process. Note that this method will redirect the user away from your app.
1164
+ *
1165
+ * @param options Parameter to customize the login behaviour. In particular, two options are mandatory: `options.oidcIssuer`, the user's identity provider, and `options.redirectUrl`, the URL to which the user will be redirected after logging in their identity provider.
1166
+ * @returns This method should redirect the user away from the app: it does not return anything. The login process is completed by {@linkcode handleIncomingRedirect}.
1167
+ */
1168
+ // Define these functions as properties so that they don't get accidentally re-bound.
1169
+ // Isn't Javascript fun?
619
1170
  this.login = async (options) => {
620
1171
  var _a;
621
1172
  await this.clientAuthentication.login({
622
1173
  sessionId: this.info.sessionId,
623
1174
  ...options,
1175
+ // Defaults the token type to DPoP
624
1176
  tokenType: (_a = options.tokenType) !== null && _a !== void 0 ? _a : "DPoP",
625
1177
  }, this.events);
1178
+ // `login` redirects the user away from the app,
1179
+ // so unless it throws an error, there is no code that should run afterwards
1180
+ // (since there is no "after" in the lifetime of the script).
1181
+ // Hence, this Promise never resolves:
626
1182
  return new Promise(() => { });
627
1183
  };
1184
+ /**
1185
+ * Fetches data using available login information. If the user is not logged in, this will behave as a regular `fetch`. The signature of this method is identical to the [canonical `fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
1186
+ *
1187
+ * @param url The URL from which data should be fetched.
1188
+ * @param init Optional parameters customizing the request, by specifying an HTTP method, headers, a body, etc. Follows the [WHATWG Fetch Standard](https://fetch.spec.whatwg.org/).
1189
+ */
628
1190
  this.fetch = (url, init) => this.clientAuthentication.fetch(url, init);
1191
+ /**
1192
+ * An internal logout function, to control whether or not the logout signal
1193
+ * should be sent, i.e. if the logout was user-initiated or is the result of
1194
+ * an external event.
1195
+ *
1196
+ * @hidden
1197
+ */
629
1198
  this.internalLogout = async (emitSignal, options) => {
1199
+ // Clearing this value means that silent refresh will no longer be attempted.
1200
+ // In particular, in the case of a silent authentication error it prevents
1201
+ // from getting stuck in an authentication retries loop.
630
1202
  window.localStorage.removeItem(KEY_CURRENT_SESSION);
631
1203
  await this.clientAuthentication.logout(this.info.sessionId, options);
632
1204
  this.info.isLoggedIn = false;
@@ -634,7 +1206,50 @@ class Session extends EventEmitter {
634
1206
  this.events.emit(solidClientAuthnCore.EVENTS.LOGOUT);
635
1207
  }
636
1208
  };
1209
+ /**
1210
+ * Logs the user out of the application.
1211
+ *
1212
+ * There are 2 types of logout supported by this library,
1213
+ * `app` logout and `idp` logout.
1214
+ *
1215
+ * App logout will log the user out within the application
1216
+ * by clearing any session data from the browser. It does
1217
+ * not log the user out of their Solid identity provider,
1218
+ * and should not redirect the user away.
1219
+ * App logout can be performed as follows:
1220
+ * ```typescript
1221
+ * await session.logout({ logoutType: 'app' });
1222
+ * ```
1223
+ *
1224
+ * IDP logout will log the user out of their Solid identity provider,
1225
+ * and will redirect the user away from the application to do so. In order
1226
+ * for users to be redirected back to `postLogoutUrl` you MUST include the
1227
+ * `postLogoutUrl` value in the `post_logout_redirect_uris` field in the
1228
+ * [Client ID Document](https://docs.inrupt.com/ess/latest/security/authentication/#client-identifier-client-id).
1229
+ * IDP logout can be performed as follows:
1230
+ * ```typescript
1231
+ * await session.logout({
1232
+ * logoutType: 'idp',
1233
+ * // An optional URL to redirect to after logout has completed;
1234
+ * // this MUST match a logout URL listed in the Client ID Document
1235
+ * // of the application that is logged in.
1236
+ * // If the application is logged in with a Client ID that is not
1237
+ * // a URI dereferencing to a Client ID Document then users will
1238
+ * // not be redirected back to the `postLogoutUrl` after logout.
1239
+ * postLogoutUrl: 'https://example.com/logout',
1240
+ * // An optional value to be included in the query parameters
1241
+ * // when the IDP provider redirects the user to the postLogoutRedirectUrl.
1242
+ * state: "my-state"
1243
+ * });
1244
+ * ```
1245
+ */
637
1246
  this.logout = async (options) => this.internalLogout(true, options);
1247
+ /**
1248
+ * Completes the login process by processing the information provided by the
1249
+ * Solid identity provider through redirect.
1250
+ *
1251
+ * @param options See {@see IHandleIncomingRedirectOptions}.
1252
+ */
638
1253
  this.handleIncomingRedirect = async (inputOptions = {}) => {
639
1254
  var _a;
640
1255
  if (this.info.isLoggedIn) {
@@ -651,17 +1266,33 @@ class Session extends EventEmitter {
651
1266
  this.setSessionInfo(sessionInfo);
652
1267
  const currentUrl = window.localStorage.getItem(KEY_CURRENT_URL);
653
1268
  if (currentUrl === null) {
1269
+ // The login event can only be triggered **after** the user has been
1270
+ // redirected from the IdP with access and ID tokens.
654
1271
  this.events.emit(solidClientAuthnCore.EVENTS.LOGIN);
655
1272
  }
656
1273
  else {
1274
+ // If an URL is stored in local storage, we are being logged in after a
1275
+ // silent authentication, so remove our currently stored URL location
1276
+ // to clean up our state now that we are completing the re-login process.
657
1277
  window.localStorage.removeItem(KEY_CURRENT_URL);
658
1278
  this.events.emit(solidClientAuthnCore.EVENTS.SESSION_RESTORED, currentUrl);
659
1279
  }
660
1280
  }
661
1281
  else if (options.restorePreviousSession === true) {
1282
+ // Silent authentication happens after a refresh, which means there are no
1283
+ // OAuth params in the current location IRI. It can only succeed if a session
1284
+ // was previously logged in, in which case its ID will be present with a known
1285
+ // identifier in local storage.
1286
+ // Check if we have a locally stored session ID...
662
1287
  const storedSessionId = window.localStorage.getItem(KEY_CURRENT_SESSION);
1288
+ // ...if not, then there is no ID token, and so silent authentication cannot happen, but
1289
+ // if we do have a stored session ID, attempt to re-authenticate now silently.
663
1290
  if (storedSessionId !== null) {
664
1291
  const attemptedSilentAuthentication = await silentlyAuthenticate(storedSessionId, this.clientAuthentication, this);
1292
+ // At this point, we know that the main window will imminently be redirected.
1293
+ // However, this redirect is asynchronous and there is no way to halt execution
1294
+ // until it happens precisely. That's why the current Promise simply does not
1295
+ // resolve.
665
1296
  if (attemptedSilentAuthentication) {
666
1297
  return new Promise(() => { });
667
1298
  }
@@ -670,6 +1301,10 @@ class Session extends EventEmitter {
670
1301
  this.tokenRequestInProgress = false;
671
1302
  return sessionInfo;
672
1303
  };
1304
+ // Until Session no longer implements EventEmitter, this.events is just a proxy
1305
+ // to this (with some interface filtering). When we make the breaking change,
1306
+ // this.events will be a regular EventEmitter (implementing ISessionEventEmitter):
1307
+ // this.events = new EventEmitter();
673
1308
  this.events = new Proxy(this, solidClientAuthnCore.buildProxyHandler(Session.prototype, "events only implements ISessionEventListener"));
674
1309
  if (sessionOptions.clientAuthentication) {
675
1310
  this.clientAuthentication = sessionOptions.clientAuthentication;
@@ -696,22 +1331,63 @@ class Session extends EventEmitter {
696
1331
  isLoggedIn: false,
697
1332
  };
698
1333
  }
1334
+ // When a session is logged in, we want to track its ID in local storage to
1335
+ // enable silent refresh. The current session ID specifically stored in 'localStorage'
1336
+ // (as opposed to using our storage abstraction layer) because it is only
1337
+ // used in a browser-specific mechanism.
699
1338
  this.events.on(solidClientAuthnCore.EVENTS.LOGIN, () => window.localStorage.setItem(KEY_CURRENT_SESSION, this.info.sessionId));
700
1339
  this.events.on(solidClientAuthnCore.EVENTS.SESSION_EXPIRED, () => this.internalLogout(false));
701
1340
  this.events.on(solidClientAuthnCore.EVENTS.ERROR, () => this.internalLogout(false));
702
1341
  }
1342
+ /**
1343
+ * Register a callback function to be called when a user completes login.
1344
+ *
1345
+ * The callback is called when {@link handleIncomingRedirect} completes successfully.
1346
+ *
1347
+ * @param callback The function called when a user completes login.
1348
+ * @deprecated Prefer session.events.on(EVENTS.LOGIN, callback)
1349
+ */
703
1350
  onLogin(callback) {
704
1351
  this.events.on(solidClientAuthnCore.EVENTS.LOGIN, callback);
705
1352
  }
1353
+ /**
1354
+ * Register a callback function to be called when a user logs out:
1355
+ *
1356
+ * @param callback The function called when a user completes logout.
1357
+ * @deprecated Prefer session.events.on(EVENTS.LOGOUT, callback)
1358
+ */
706
1359
  onLogout(callback) {
707
1360
  this.events.on(solidClientAuthnCore.EVENTS.LOGOUT, callback);
708
1361
  }
1362
+ /**
1363
+ * Register a callback function to be called when a user logs out:
1364
+ *
1365
+ * @param callback The function called when an error occurs.
1366
+ * @since 1.11.0
1367
+ * @deprecated Prefer session.events.on(EVENTS.ERROR, callback)
1368
+ */
709
1369
  onError(callback) {
710
1370
  this.events.on(solidClientAuthnCore.EVENTS.ERROR, callback);
711
1371
  }
1372
+ /**
1373
+ * Register a callback function to be called when a session is restored.
1374
+ *
1375
+ * Note: the callback will be called with the saved value of the 'current URL'
1376
+ * at the time the session was restored.
1377
+ *
1378
+ * @param callback The function called when a user's already logged-in session is restored, e.g., after a silent authentication is completed after a page refresh.
1379
+ * @deprecated Prefer session.events.on(EVENTS.SESSION_RESTORED, callback)
1380
+ */
712
1381
  onSessionRestore(callback) {
713
1382
  this.events.on(solidClientAuthnCore.EVENTS.SESSION_RESTORED, callback);
714
1383
  }
1384
+ /**
1385
+ * Register a callback that runs when the session expires and can no longer
1386
+ * make authenticated requests, but following a user logout.
1387
+ * @param callback The function that runs on session expiration.
1388
+ * @since 1.11.0
1389
+ * @deprecated Prefer session.events.on(EVENTS.SESSION_EXPIRED, callback)
1390
+ */
715
1391
  onSessionExpiration(callback) {
716
1392
  this.events.on(solidClientAuthnCore.EVENTS.SESSION_EXPIRED, callback);
717
1393
  }
@@ -726,41 +1402,132 @@ class Session extends EventEmitter {
726
1402
  }
727
1403
  }
728
1404
 
1405
+ //
1406
+ // Copyright Inrupt Inc.
1407
+ //
1408
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1409
+ // of this software and associated documentation files (the "Software"), to deal in
1410
+ // the Software without restriction, including without limitation the rights to use,
1411
+ // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
1412
+ // Software, and to permit persons to whom the Software is furnished to do so,
1413
+ // subject to the following conditions:
1414
+ //
1415
+ // The above copyright notice and this permission notice shall be included in
1416
+ // all copies or substantial portions of the Software.
1417
+ //
1418
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
1419
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
1420
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
1421
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1422
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1423
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1424
+ //
729
1425
  let defaultSession;
1426
+ /**
1427
+ * Obtain the {@link Session} used when not explicitly instantiating one yourself.
1428
+ *
1429
+ * When using the top-level exports {@link fetch}, {@link login}, {@link logout},
1430
+ * {@link handleIncomingRedirect}, {@link onLogin} and {@link onLogout}, these apply to an
1431
+ * implicitly-instantiated {@link Session}.
1432
+ * This function returns a reference to that Session in order to obtain e.g. the current user's
1433
+ * WebID.
1434
+ * @since 1.3.0
1435
+ */
730
1436
  function getDefaultSession() {
731
1437
  if (typeof defaultSession === "undefined") {
732
1438
  defaultSession = new Session();
733
1439
  }
734
1440
  return defaultSession;
735
1441
  }
1442
+ /**
1443
+ * This function's signature is equal to `window.fetch`, but if the current user is authenticated
1444
+ * (see [[login]] and [[handleIncomingRedirect]]), requests made using it will include that user's
1445
+ * credentials. If not, this will behave just like the regular `window.fetch`.
1446
+ *
1447
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch}
1448
+ * @since 1.3.0
1449
+ */
1450
+ /* eslint-disable-next-line no-shadow */
736
1451
  const fetch = (...args) => {
737
1452
  const session = getDefaultSession();
738
1453
  return session.fetch(...args);
739
1454
  };
1455
+ /**
1456
+ * Triggers the login process. Note that this method will redirect the user away from your app.
1457
+ *
1458
+ * @param options Parameter to customize the login behaviour. In particular, two options are mandatory: `options.oidcIssuer`, the user's identity provider, and `options.redirectUrl`, the URL to which the user will be redirected after logging in their identity provider.
1459
+ * @returns This method should redirect the user away from the app: it does not return anything. The login process is completed by [[handleIncomingRedirect]].
1460
+ * @since 1.3.0
1461
+ */
740
1462
  const login = (...args) => {
741
1463
  const session = getDefaultSession();
742
1464
  return session.login(...args);
743
1465
  };
1466
+ /**
1467
+ * Logs the user out of the application.
1468
+ *
1469
+ * By default this does not log the user out of their Solid identity provider.
1470
+ * In order to do so, you must set the logoutType to `idp`. For usage details
1471
+ * see {@link Session.logout}.
1472
+ *
1473
+ * @since 1.3.0
1474
+ */
744
1475
  const logout = (...args) => {
745
1476
  const session = getDefaultSession();
746
1477
  return session.logout(...args);
747
1478
  };
1479
+ /**
1480
+ * Completes the login process by processing the information provided by the Solid identity provider through redirect.
1481
+ *
1482
+ * @param url The URL of the page handling the redirect, including the query parameters — these contain the information to process the login.
1483
+ * @since 1.3.0
1484
+ */
748
1485
  const handleIncomingRedirect = (...args) => {
749
1486
  const session = getDefaultSession();
750
1487
  return session.handleIncomingRedirect(...args);
751
1488
  };
1489
+ /**
1490
+ * Register a callback function to be called when a user completes login.
1491
+ *
1492
+ * The callback is called when {@link handleIncomingRedirect} completes successfully.
1493
+ * @since 1.3.0
1494
+ *
1495
+ * @param callback The function called when a user completes login.
1496
+ * @deprecated Prefer events.on(EVENTS.LOGIN, callback)
1497
+
1498
+ */
752
1499
  const onLogin = (...args) => {
753
1500
  const session = getDefaultSession();
754
1501
  return session.onLogin(...args);
755
1502
  };
1503
+ /**
1504
+ * Register a callback function to be called when a user logs out:
1505
+ *
1506
+ * @param callback The function called when a user completes logout.
1507
+ * @since 1.3.0
1508
+ * @deprecated Prefer events.on(EVENTS.LOGOUT, callback)
1509
+ *
1510
+ */
756
1511
  const onLogout = (...args) => {
757
1512
  const session = getDefaultSession();
758
1513
  return session.onLogout(...args);
759
1514
  };
1515
+ /**
1516
+ * Register a callback function to be called when a session is restored:
1517
+ *
1518
+ * @param callback The function called when a session is restored.
1519
+ * @since 1.3.0
1520
+ * @deprecated Prefer events.on(EVENTS.SESSION_RESTORED, callback)
1521
+ */
760
1522
  const onSessionRestore = (...args) => {
761
1523
  const session = getDefaultSession();
762
1524
  return session.onSessionRestore(...args);
763
1525
  };
1526
+ /**
1527
+ * {@link SessionEventEmitter} instance to subscribe to events by the default session.
1528
+ *
1529
+ * @since 1.14.0
1530
+ */
764
1531
  const events = () => {
765
1532
  return getDefaultSession().events;
766
1533
  };