@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.
- package/dist/ClientAuthentication.d.ts +7 -0
- package/dist/Session.d.ts +174 -0
- package/dist/defaultSession.d.ts +70 -0
- package/dist/dependencies.d.ts +12 -0
- package/dist/index.js +783 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +785 -18
- package/dist/index.mjs.map +1 -1
- package/dist/login/oidc/AggregateOidcHandler.d.ts +10 -0
- package/dist/login/oidc/AggregateRedirectHandler.d.ts +10 -0
- package/dist/login/oidc/ClientRegistrar.d.ts +7 -0
- package/dist/login/oidc/IssuerConfigFetcher.d.ts +10 -0
- package/dist/login/oidc/OidcLoginHandler.d.ts +10 -0
- package/dist/login/oidc/Redirector.d.ts +7 -0
- package/dist/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.d.ts +3 -0
- package/dist/login/oidc/incomingRedirectHandler/ErrorOidcHandler.d.ts +10 -0
- package/dist/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.d.ts +10 -0
- package/dist/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.d.ts +12 -0
- package/dist/login/oidc/refresh/TokenRefresher.d.ts +7 -0
- package/dist/logout/buildRpInitiatedLogout.d.ts +7 -0
- package/dist/sessionInfo/SessionInfoManager.d.ts +18 -0
- package/dist/solid-client-authn.bundle.js +7 -7
- package/dist/solid-client-authn.bundle.js.map +1 -1
- package/dist/storage/BrowserStorage.d.ts +7 -0
- package/dist/storage/StorageUtility.d.ts +14 -0
- package/dist/util/UuidGenerator.d.ts +6 -0
- package/package.json +7 -7
- package/src/ClientAuthentication.spec.ts +33 -31
- package/src/ClientAuthentication.ts +19 -17
- 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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inrupt/solid-client-authn-browser",
|
|
3
|
-
"version": "1.17.
|
|
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.
|
|
29
|
+
"@types/uuid": "^9.0.4"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@inrupt/oidc-client-ext": "^1.17.
|
|
33
|
-
"@inrupt/solid-client-authn-core": "^1.17.
|
|
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.
|
|
37
|
-
"uuid": "^9.0.
|
|
36
|
+
"jose": "^4.15.4",
|
|
37
|
+
"uuid": "^9.0.1"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
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
|
-
|
|
340
|
-
|
|
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
|
-
|
|
406
|
-
|
|
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://
|
|
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
|
-
|
|
440
|
-
|
|
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
|
-
|
|
456
|
-
|
|
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
|
-
|
|
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
|
-
|
|
499
|
-
|
|
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
|
-
|
|
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 {
|
|
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 ??
|
|
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 =
|
|
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
|
|
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
|
}
|
package/src/Session.spec.ts
CHANGED
|
@@ -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
|
|
250
|
-
clientAuthentication,
|
|
251
|
-
|
|
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
|
|
261
|
+
it("wraps ClientAuthentication handleIncomingRedirect", async () => {
|
|
263
262
|
mockLocation("https://some.url");
|
|
264
263
|
const clientAuthentication = mockClientAuthentication();
|
|
265
|
-
const incomingRedirectHandler = jest
|
|
266
|
-
clientAuthentication,
|
|
267
|
-
|
|
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
|
|
405
|
-
clientAuthentication,
|
|
406
|
-
|
|
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,
|