@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
@@ -1,4 +1,11 @@
1
+ /**
2
+ * @hidden
3
+ * @packageDocumentation
4
+ */
1
5
  import type { IStorage } from "@inrupt/solid-client-authn-core";
6
+ /**
7
+ * @hidden
8
+ */
2
9
  export default class BrowserStorage implements IStorage {
3
10
  get storage(): typeof window.localStorage;
4
11
  get(key: string): Promise<string | undefined>;
@@ -1,5 +1,19 @@
1
+ /**
2
+ * @hidden
3
+ * @packageDocumentation
4
+ */
5
+ /**
6
+ * A helper class that will validate items taken from local storage
7
+ */
1
8
  import type { IStorage } from "@inrupt/solid-client-authn-core";
2
9
  import { StorageUtility } from "@inrupt/solid-client-authn-core";
10
+ /**
11
+ * This class in a no-value-added extension of StorageUtility from the core module.
12
+ * The reason it has to be declared is for TSyringe to find the decorators in the
13
+ * same modules as where the dependency container is declared (in this case,
14
+ * the browser module, with the dependancy container in dependencies.ts).
15
+ * @hidden
16
+ */
3
17
  export default class StorageUtilityBrowser extends StorageUtility {
4
18
  constructor(secureStorage: IStorage, insecureStorage: IStorage);
5
19
  }
@@ -1,6 +1,12 @@
1
+ /**
2
+ * @hidden
3
+ */
1
4
  export interface IUuidGenerator {
2
5
  v4(): string;
3
6
  }
7
+ /**
8
+ * @hidden
9
+ */
4
10
  export default class UuidGenerator {
5
11
  v4(): string;
6
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inrupt/solid-client-authn-browser",
3
- "version": "1.17.2",
3
+ "version": "1.17.4",
4
4
  "license": "MIT",
5
5
  "types": "dist/index.d.ts",
6
6
  "browser": "dist/index.js",
@@ -26,18 +26,18 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^20.6.0",
29
- "@types/uuid": "^9.0.1"
29
+ "@types/uuid": "^9.0.4"
30
30
  },
31
31
  "dependencies": {
32
- "@inrupt/oidc-client-ext": "^1.17.2",
33
- "@inrupt/solid-client-authn-core": "^1.17.2",
32
+ "@inrupt/oidc-client-ext": "^1.17.4",
33
+ "@inrupt/solid-client-authn-core": "^1.17.4",
34
34
  "@inrupt/universal-fetch": "^1.0.2",
35
35
  "events": "^3.3.0",
36
- "jose": "^4.14.6",
37
- "uuid": "^9.0.0"
36
+ "jose": "^4.15.4",
37
+ "uuid": "^9.0.1"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"
41
41
  },
42
- "gitHead": "5818a79b875812ca93235fca64c65d2ba5b59fc3"
42
+ "gitHead": "88355026c275933dc54ab078c4fd9b257cb7c50e"
43
43
  }
@@ -336,8 +336,12 @@ describe("ClientAuthentication", () => {
336
336
  // TODO: add tests for events & errors
337
337
 
338
338
  it("reverts back to un-authenticated fetch on logout", async () => {
339
- // eslint-disable-next-line no-restricted-globals
340
- history.replaceState = jest.fn();
339
+ window.history.replaceState = jest
340
+ .fn<typeof window.history.replaceState>()
341
+ .mockImplementationOnce((_data, _unused, url) => {
342
+ // Pretend the current location is updated
343
+ window.location.href = url as string;
344
+ });
341
345
  const clientAuthn = getClientAuthentication();
342
346
 
343
347
  const unauthFetch = clientAuthn.fetch;
@@ -402,13 +406,17 @@ describe("ClientAuthentication", () => {
402
406
  mockEmitter.emit = jest.fn<typeof mockEmitter.emit>();
403
407
 
404
408
  it("calls handle redirect", async () => {
405
- // eslint-disable-next-line no-restricted-globals
406
- history.replaceState = jest.fn();
409
+ window.history.replaceState = jest
410
+ .fn<typeof window.history.replaceState>()
411
+ .mockImplementationOnce((_data, _unused, url) => {
412
+ // Pretend the current location is updated
413
+ window.location.href = url as string;
414
+ });
407
415
  const expectedResult = SessionCreatorCreateResponse;
408
416
  const clientAuthn = getClientAuthentication();
409
417
  const unauthFetch = clientAuthn.fetch;
410
418
  const url =
411
- "https://coolapp.com/redirect?state=userId&id_token=idToken&access_token=accessToken";
419
+ "https://example.org/redirect?state=userId&id_token=idToken&access_token=accessToken";
412
420
  const redirectInfo = await clientAuthn.handleIncomingRedirect(
413
421
  url,
414
422
  mockEmitter,
@@ -436,8 +444,12 @@ describe("ClientAuthentication", () => {
436
444
  });
437
445
 
438
446
  it("clears the current IRI from OAuth query parameters in the auth code flow", async () => {
439
- // eslint-disable-next-line no-restricted-globals
440
- history.replaceState = jest.fn();
447
+ window.history.replaceState = jest
448
+ .fn<typeof window.history.replaceState>()
449
+ .mockImplementationOnce((_data, _unused, url) => {
450
+ // Pretend the current location is updated
451
+ window.location.href = url as string;
452
+ });
441
453
  const clientAuthn = getClientAuthentication();
442
454
  const url =
443
455
  "https://coolapp.com/redirect?state=someState&code=someAuthCode&iss=someIssuer";
@@ -452,8 +464,12 @@ describe("ClientAuthentication", () => {
452
464
  });
453
465
 
454
466
  it("clears the current IRI from OAuth query parameters even if auth flow fails", async () => {
455
- // eslint-disable-next-line no-restricted-globals
456
- history.replaceState = jest.fn();
467
+ window.history.replaceState = jest
468
+ .fn<typeof window.history.replaceState>()
469
+ .mockImplementationOnce((_data, _unused, url) => {
470
+ // Pretend the current location is updated
471
+ window.location.href = url as string;
472
+ });
457
473
 
458
474
  mockHandleIncomingRedirect.mockImplementationOnce(() =>
459
475
  Promise.reject(new Error("Something went wrong")),
@@ -464,8 +480,7 @@ describe("ClientAuthentication", () => {
464
480
  const url =
465
481
  "https://coolapp.com/redirect?state=someState&code=someAuthCode";
466
482
  await clientAuthn.handleIncomingRedirect(url, mockEmitter);
467
- // eslint-disable-next-line no-restricted-globals
468
- expect(history.replaceState).toHaveBeenCalledWith(
483
+ expect(window.history.replaceState).toHaveBeenCalledWith(
469
484
  null,
470
485
  "",
471
486
  "https://coolapp.com/redirect",
@@ -478,31 +493,18 @@ describe("ClientAuthentication", () => {
478
493
  );
479
494
  });
480
495
 
481
- it("clears the current IRI from OAuth query parameters in the implicit flow", async () => {
482
- // eslint-disable-next-line no-restricted-globals
483
- history.replaceState = jest.fn();
484
- const clientAuthn = getClientAuthentication();
485
- const url =
486
- "https://coolapp.com/redirect?state=someState&id_token=idToken&access_token=accessToken";
487
- await clientAuthn.handleIncomingRedirect(url, mockEmitter);
488
- // eslint-disable-next-line no-restricted-globals
489
- expect(history.replaceState).toHaveBeenCalledWith(
490
- null,
491
- "",
492
- "https://coolapp.com/redirect",
493
- );
494
- expect(mockEmitter.emit).not.toHaveBeenCalled();
495
- });
496
-
497
496
  it("preserves non-OAuth query strings", async () => {
498
- // eslint-disable-next-line no-restricted-globals
499
- history.replaceState = jest.fn();
497
+ window.history.replaceState = jest
498
+ .fn<typeof window.history.replaceState>()
499
+ .mockImplementationOnce((_data, _unused, url) => {
500
+ // Pretend the current location is updated
501
+ window.location.href = url as string;
502
+ });
500
503
  const clientAuthn = getClientAuthentication();
501
504
  const url =
502
505
  "https://coolapp.com/redirect?state=someState&code=someAuthCode&someQuery=someValue";
503
506
  await clientAuthn.handleIncomingRedirect(url, mockEmitter);
504
- // eslint-disable-next-line no-restricted-globals
505
- expect(history.replaceState).toHaveBeenCalledWith(
507
+ expect(window.history.replaceState).toHaveBeenCalledWith(
506
508
  null,
507
509
  "",
508
510
  "https://coolapp.com/redirect?someQuery=someValue",
@@ -33,8 +33,9 @@ import {
33
33
  EVENTS,
34
34
  isValidRedirectUrl,
35
35
  ClientAuthentication as ClientAuthenticationBase,
36
+ removeOpenIdParams,
36
37
  } from "@inrupt/solid-client-authn-core";
37
- import { removeOidcQueryParam } from "@inrupt/oidc-client-ext";
38
+ import { normalizeCallbackUrl } from "@inrupt/oidc-client-ext";
38
39
  import type { EventEmitter } from "events";
39
40
 
40
41
  /**
@@ -62,7 +63,7 @@ export default class ClientAuthentication extends ClientAuthenticationBase {
62
63
  // normalization only applies if we default to the current location (which is
63
64
  // a bad practice and should be discouraged).
64
65
  const redirectUrl =
65
- options.redirectUrl ?? removeOidcQueryParam(window.location.href);
66
+ options.redirectUrl ?? normalizeCallbackUrl(window.location.href);
66
67
  if (!isValidRedirectUrl(redirectUrl)) {
67
68
  throw new Error(
68
69
  `${redirectUrl} is not a valid redirect URL, it is either a malformed IRI, includes a hash fragment, or reserved query parameters ('code' or 'state').`,
@@ -109,7 +110,7 @@ export default class ClientAuthentication extends ClientAuthenticationBase {
109
110
  this.boundLogout = redirectInfo.getLogoutUrl;
110
111
 
111
112
  // Strip the oauth params:
112
- this.cleanUrlAfterRedirect(url);
113
+ await this.cleanUrlAfterRedirect(url);
113
114
 
114
115
  return {
115
116
  isLoggedIn: redirectInfo.isLoggedIn,
@@ -119,7 +120,7 @@ export default class ClientAuthentication extends ClientAuthenticationBase {
119
120
  };
120
121
  } catch (err) {
121
122
  // Strip the oauth params:
122
- this.cleanUrlAfterRedirect(url);
123
+ await this.cleanUrlAfterRedirect(url);
123
124
 
124
125
  // FIXME: EVENTS.ERROR should be errorCode, errorDescription
125
126
  //
@@ -132,24 +133,25 @@ export default class ClientAuthentication extends ClientAuthenticationBase {
132
133
  }
133
134
  };
134
135
 
135
- private cleanUrlAfterRedirect(url: string): void {
136
- const cleanedUpUrl = new URL(url);
137
- cleanedUpUrl.searchParams.delete("state");
138
- // For auth code flow
139
- cleanedUpUrl.searchParams.delete("code");
140
- // For implicit flow
141
- cleanedUpUrl.searchParams.delete("id_token");
142
- cleanedUpUrl.searchParams.delete("access_token");
143
- // For login error
144
- cleanedUpUrl.searchParams.delete("error");
145
- cleanedUpUrl.searchParams.delete("error_description");
146
- cleanedUpUrl.searchParams.delete("iss");
136
+ private async cleanUrlAfterRedirect(url: string): Promise<void> {
137
+ const cleanedUpUrl = removeOpenIdParams(url).href;
147
138
 
148
139
  // Remove OAuth-specific query params (since the login flow finishes with
149
140
  // the browser being redirected back with OAuth2 query params (e.g. for
150
141
  // 'code' and 'state'), and so if the user simply refreshes this page our
151
142
  // authentication library will be called again with what are now invalid
152
143
  // query parameters!).
153
- window.history.replaceState(null, "", cleanedUpUrl.toString());
144
+ window.history.replaceState(null, "", cleanedUpUrl);
145
+ while (window.location.href !== cleanedUpUrl) {
146
+ // Poll the current URL every ms. Active polling is required because
147
+ // window.history.replaceState is asynchronous, but the associated
148
+ // 'popstate' event which should be listened to is only sent on active
149
+ // navigation, which we will not have here.
150
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event#when_popstate_is_sent
151
+ // eslint-disable-next-line no-await-in-loop
152
+ await new Promise<void>((resolve) => {
153
+ setTimeout(() => resolve(), 1);
154
+ });
155
+ }
154
156
  }
155
157
  }
@@ -246,10 +246,9 @@ describe("Session", () => {
246
246
  it("uses current window location as default redirect URL", async () => {
247
247
  mockLocation("https://some.url");
248
248
  const clientAuthentication = mockClientAuthentication();
249
- const incomingRedirectHandler = jest.spyOn(
250
- clientAuthentication,
251
- "handleIncomingRedirect",
252
- );
249
+ const incomingRedirectHandler = jest
250
+ .spyOn(clientAuthentication, "handleIncomingRedirect")
251
+ .mockResolvedValue(undefined);
253
252
 
254
253
  const mySession = new Session({ clientAuthentication });
255
254
  await mySession.handleIncomingRedirect();
@@ -259,13 +258,12 @@ describe("Session", () => {
259
258
  );
260
259
  });
261
260
 
262
- it("wraps up ClientAuthentication handleIncomingRedirect", async () => {
261
+ it("wraps ClientAuthentication handleIncomingRedirect", async () => {
263
262
  mockLocation("https://some.url");
264
263
  const clientAuthentication = mockClientAuthentication();
265
- const incomingRedirectHandler = jest.spyOn(
266
- clientAuthentication,
267
- "handleIncomingRedirect",
268
- );
264
+ const incomingRedirectHandler = jest
265
+ .spyOn(clientAuthentication, "handleIncomingRedirect")
266
+ .mockResolvedValue(undefined);
269
267
  const mySession = new Session({ clientAuthentication });
270
268
  await mySession.handleIncomingRedirect("https://some.url");
271
269
  expect(incomingRedirectHandler).toHaveBeenCalled();
@@ -401,10 +399,9 @@ describe("Session", () => {
401
399
 
402
400
  it("preserves a binding to its Session instance", async () => {
403
401
  const clientAuthentication = mockClientAuthentication();
404
- const incomingRedirectHandler = jest.spyOn(
405
- clientAuthentication,
406
- "handleIncomingRedirect",
407
- );
402
+ const incomingRedirectHandler = jest
403
+ .spyOn(clientAuthentication, "handleIncomingRedirect")
404
+ .mockResolvedValue(undefined);
408
405
  const mySession = new Session({ clientAuthentication });
409
406
  const objectWithHandleIncomingRedirect = {
410
407
  handleIncomingRedirect: mySession.handleIncomingRedirect,