@inrupt/solid-client-authn-browser 3.1.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -13
- package/dist/index.js +28 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +28 -38
- package/dist/index.mjs.map +1 -1
- package/dist/solid-client-authn.bundle.js +1 -31
- package/dist/solid-client-authn.bundle.js.map +1 -1
- package/package.json +7 -7
- package/rollup.config.mjs +0 -2
- package/src/ClientAuthentication.spec.ts +147 -23
- package/src/ClientAuthentication.ts +18 -4
- package/src/Session.spec.ts +74 -39
- package/src/Session.ts +0 -1
- package/src/__mocks__/ClientAuthentication.ts +1 -1
- package/src/constant.ts +0 -1
- package/src/defaultSession.spec.ts +0 -1
- package/src/defaultSession.ts +1 -2
- package/src/dependencies.node.spec.ts +0 -1
- package/src/dependencies.spec.ts +0 -1
- package/src/dependencies.ts +0 -1
- package/src/index.browser.spec.ts +0 -1
- package/src/index.browser.ts +0 -1
- package/src/index.spec.ts +0 -1
- package/src/index.ts +0 -1
- package/src/login/__mocks__/LoginHandler.ts +0 -1
- package/src/login/oidc/AggregateOidcHandler.spec.ts +1 -2
- package/src/login/oidc/AggregateOidcHandler.ts +0 -1
- package/src/login/oidc/AggregateRedirectHandler.ts +0 -1
- package/src/login/oidc/ClientRegistrar.spec.ts +1 -1
- package/src/login/oidc/ClientRegistrar.ts +0 -1
- package/src/login/oidc/IssuerConfigFetcher.spec.ts +7 -5
- package/src/login/oidc/IssuerConfigFetcher.ts +3 -3
- package/src/login/oidc/OidcLoginHandler.spec.ts +1 -1
- package/src/login/oidc/OidcLoginHandler.ts +0 -1
- package/src/login/oidc/Redirector.spec.ts +5 -23
- package/src/login/oidc/Redirector.ts +0 -1
- package/src/login/oidc/__mocks__/ClientRegistrar.ts +0 -1
- package/src/login/oidc/__mocks__/IOidcHandler.ts +1 -3
- package/src/login/oidc/__mocks__/IOidcOptions.ts +0 -1
- package/src/login/oidc/__mocks__/IssuerConfigFetcher.ts +0 -2
- package/src/login/oidc/__mocks__/Redirector.ts +0 -1
- package/src/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.spec.ts +3 -4
- package/src/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.ts +0 -1
- package/src/login/oidc/incomingRedirectHandler/ErrorOidcHandler.spec.ts +0 -1
- package/src/login/oidc/incomingRedirectHandler/ErrorOidcHandler.ts +0 -2
- package/src/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.spec.ts +0 -1
- package/src/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.ts +1 -2
- package/src/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.spec.ts +1 -4
- package/src/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.ts +10 -11
- package/src/login/oidc/oidcHandlers/OidcHandlerCanHandleTest.ts +0 -1
- package/src/login/oidc/refresh/TokenRefresher.spec.ts +3 -4
- package/src/login/oidc/refresh/TokenRefresher.ts +0 -2
- package/src/login/oidc/refresh/__mocks__/TokenRefresher.ts +0 -2
- package/src/logout/buildRpInitiatedLogout.ts +1 -1
- package/src/sessionInfo/SessionInfoManager.spec.ts +62 -1
- package/src/sessionInfo/SessionInfoManager.ts +6 -1
- package/src/sessionInfo/__mocks__/SessionInfoManager.ts +1 -2
- package/src/storage/BrowserStorage.spec.ts +0 -1
- package/src/storage/BrowserStorage.ts +0 -1
- package/src/storage/StorageUtility.ts +0 -1
- package/src/storage/__mocks__/LocalStorage.ts +0 -1
- package/src/util/UUIDGenerator.spec.ts +0 -1
- package/src/util/UuidGenerator.ts +0 -2
- package/src/util/__mocks__/UuidGenerator.ts +0 -1
- package/typedoc.config.mjs +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inrupt/solid-client-authn-browser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"browser": "dist/index.js",
|
|
@@ -26,15 +26,15 @@
|
|
|
26
26
|
"build-docs-preview-site": "npm run build-api-docs; cd docs/api; make html"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@types/node": "^
|
|
30
|
-
"@types/uuid": "^
|
|
29
|
+
"@types/node": "^25.9.1",
|
|
30
|
+
"@types/uuid": "^11.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@inrupt/oidc-client-ext": "^
|
|
34
|
-
"@inrupt/solid-client-authn-core": "^
|
|
33
|
+
"@inrupt/oidc-client-ext": "^5.0.0",
|
|
34
|
+
"@inrupt/solid-client-authn-core": "^5.0.0",
|
|
35
35
|
"events": "^3.3.0",
|
|
36
|
-
"jose": "^
|
|
37
|
-
"uuid": "^
|
|
36
|
+
"jose": "^6.2.3",
|
|
37
|
+
"uuid": "^14.0.0"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
package/rollup.config.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//
|
|
2
1
|
// Copyright Inrupt Inc.
|
|
3
2
|
//
|
|
4
3
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
@@ -23,7 +22,6 @@ import { nodeResolve } from "@rollup/plugin-node-resolve";
|
|
|
23
22
|
import commonjs from "@rollup/plugin-commonjs";
|
|
24
23
|
import terser from "@rollup/plugin-terser";
|
|
25
24
|
import createConfig, { createSharedConfig } from "@inrupt/base-rollup-config";
|
|
26
|
-
// eslint-disable-next-line import/extensions
|
|
27
25
|
import pkg from "./package.json" with { type: "json" };
|
|
28
26
|
|
|
29
27
|
export default [
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//
|
|
2
1
|
// Copyright Inrupt Inc.
|
|
3
2
|
//
|
|
4
3
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
@@ -23,6 +22,8 @@ import { jest, it, describe, expect, beforeEach } from "@jest/globals";
|
|
|
23
22
|
import { EventEmitter } from "events";
|
|
24
23
|
import type * as SolidClientAuthnCore from "@inrupt/solid-client-authn-core";
|
|
25
24
|
|
|
25
|
+
declare const __setTestUrl: (url: string) => void;
|
|
26
|
+
|
|
26
27
|
import {
|
|
27
28
|
StorageUtility,
|
|
28
29
|
USER_SESSION_PREFIX,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
mockIncomingRedirectHandler,
|
|
36
37
|
mockHandleIncomingRedirect,
|
|
37
38
|
mockLogoutHandler,
|
|
39
|
+
// eslint-disable-next-line import/no-unresolved
|
|
38
40
|
} from "@inrupt/solid-client-authn-core/mocks";
|
|
39
41
|
|
|
40
42
|
import { mockLoginHandler } from "./login/__mocks__/LoginHandler";
|
|
@@ -108,11 +110,7 @@ describe("ClientAuthentication", () => {
|
|
|
108
110
|
|
|
109
111
|
beforeEach(() => {
|
|
110
112
|
jest.clearAllMocks();
|
|
111
|
-
|
|
112
|
-
Object.defineProperty(window, "location", {
|
|
113
|
-
writable: true,
|
|
114
|
-
value: { assign: jest.fn() },
|
|
115
|
-
});
|
|
113
|
+
__setTestUrl("http://localhost/");
|
|
116
114
|
});
|
|
117
115
|
|
|
118
116
|
describe("login", () => {
|
|
@@ -122,7 +120,7 @@ describe("ClientAuthentication", () => {
|
|
|
122
120
|
it("calls login, and uses the window.location.href for the redirect if no redirectUrl is set", async () => {
|
|
123
121
|
// Set a current window location which will be the expected URL to be
|
|
124
122
|
// redirected back to, since we don't pass redirectUrl:
|
|
125
|
-
|
|
123
|
+
__setTestUrl("https://coolapp.test/some/redirect");
|
|
126
124
|
|
|
127
125
|
const clientAuthn = getClientAuthentication();
|
|
128
126
|
await clientAuthn.login(
|
|
@@ -353,11 +351,11 @@ describe("ClientAuthentication", () => {
|
|
|
353
351
|
// TODO: add tests for events & errors
|
|
354
352
|
|
|
355
353
|
it("reverts back to un-authenticated fetch on logout", async () => {
|
|
356
|
-
|
|
357
|
-
.
|
|
354
|
+
jest
|
|
355
|
+
.spyOn(window.history, "replaceState")
|
|
358
356
|
.mockImplementationOnce((_data, _unused, url) => {
|
|
359
357
|
// Pretend the current location is updated
|
|
360
|
-
|
|
358
|
+
__setTestUrl(url as string);
|
|
361
359
|
});
|
|
362
360
|
const clientAuthn = getClientAuthentication();
|
|
363
361
|
|
|
@@ -423,11 +421,11 @@ describe("ClientAuthentication", () => {
|
|
|
423
421
|
mockEmitter.emit = jest.fn<typeof mockEmitter.emit>();
|
|
424
422
|
|
|
425
423
|
it("calls handle redirect", async () => {
|
|
426
|
-
|
|
427
|
-
.
|
|
424
|
+
jest
|
|
425
|
+
.spyOn(window.history, "replaceState")
|
|
428
426
|
.mockImplementationOnce((_data, _unused, url) => {
|
|
429
427
|
// Pretend the current location is updated
|
|
430
|
-
|
|
428
|
+
__setTestUrl(url as string);
|
|
431
429
|
});
|
|
432
430
|
const expectedResult = SessionCreatorCreateResponse;
|
|
433
431
|
const clientAuthn = getClientAuthentication();
|
|
@@ -462,17 +460,17 @@ describe("ClientAuthentication", () => {
|
|
|
462
460
|
});
|
|
463
461
|
|
|
464
462
|
it("clears the current IRI from OAuth query parameters in the auth code flow", async () => {
|
|
465
|
-
|
|
466
|
-
.
|
|
463
|
+
jest
|
|
464
|
+
.spyOn(window.history, "replaceState")
|
|
467
465
|
.mockImplementationOnce((_data, _unused, url) => {
|
|
468
466
|
// Pretend the current location is updated
|
|
469
|
-
|
|
467
|
+
__setTestUrl(url as string);
|
|
470
468
|
});
|
|
471
469
|
const clientAuthn = getClientAuthentication();
|
|
472
470
|
const url =
|
|
473
471
|
"https://coolapp.com/redirect?state=someState&code=someAuthCode&iss=someIssuer";
|
|
474
472
|
await clientAuthn.handleIncomingRedirect(url, mockEmitter);
|
|
475
|
-
|
|
473
|
+
|
|
476
474
|
expect(history.replaceState).toHaveBeenCalledWith(
|
|
477
475
|
null,
|
|
478
476
|
"",
|
|
@@ -482,11 +480,11 @@ describe("ClientAuthentication", () => {
|
|
|
482
480
|
});
|
|
483
481
|
|
|
484
482
|
it("clears the current IRI from OAuth query parameters even if auth flow fails", async () => {
|
|
485
|
-
|
|
486
|
-
.
|
|
483
|
+
jest
|
|
484
|
+
.spyOn(window.history, "replaceState")
|
|
487
485
|
.mockImplementationOnce((_data, _unused, url) => {
|
|
488
486
|
// Pretend the current location is updated
|
|
489
|
-
|
|
487
|
+
__setTestUrl(url as string);
|
|
490
488
|
});
|
|
491
489
|
|
|
492
490
|
mockHandleIncomingRedirect.mockImplementationOnce(() =>
|
|
@@ -512,11 +510,11 @@ describe("ClientAuthentication", () => {
|
|
|
512
510
|
});
|
|
513
511
|
|
|
514
512
|
it("preserves non-OAuth query strings", async () => {
|
|
515
|
-
|
|
516
|
-
.
|
|
513
|
+
jest
|
|
514
|
+
.spyOn(window.history, "replaceState")
|
|
517
515
|
.mockImplementationOnce((_data, _unused, url) => {
|
|
518
516
|
// Pretend the current location is updated
|
|
519
|
-
|
|
517
|
+
__setTestUrl(url as string);
|
|
520
518
|
});
|
|
521
519
|
const clientAuthn = getClientAuthentication();
|
|
522
520
|
const url =
|
|
@@ -601,4 +599,130 @@ describe("ClientAuthentication", () => {
|
|
|
601
599
|
);
|
|
602
600
|
});
|
|
603
601
|
});
|
|
602
|
+
|
|
603
|
+
describe("validateCurrentSession", () => {
|
|
604
|
+
it("returns clientExpiresAt when expiresAt is in storage", async () => {
|
|
605
|
+
const sessionId = "mySession";
|
|
606
|
+
const expiresAt = Math.floor(Date.now() / 1000) + 10000;
|
|
607
|
+
const mockedStorage = new StorageUtility(
|
|
608
|
+
mockStorage({
|
|
609
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
610
|
+
isLoggedIn: "true",
|
|
611
|
+
webId: "https://my.pod/profile#me",
|
|
612
|
+
},
|
|
613
|
+
}),
|
|
614
|
+
mockStorage({
|
|
615
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
616
|
+
clientId: "https://some.app/registration",
|
|
617
|
+
clientSecret: "some-secret",
|
|
618
|
+
issuer: "https://some.issuer",
|
|
619
|
+
expiresAt: String(expiresAt),
|
|
620
|
+
},
|
|
621
|
+
}),
|
|
622
|
+
);
|
|
623
|
+
const clientAuthn = getClientAuthentication({
|
|
624
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const result = await clientAuthn.validateCurrentSession(sessionId);
|
|
628
|
+
expect(result).toStrictEqual(
|
|
629
|
+
expect.objectContaining({
|
|
630
|
+
clientExpiresAt: expiresAt,
|
|
631
|
+
}),
|
|
632
|
+
);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("returns null when client has expired", async () => {
|
|
636
|
+
const sessionId = "mySession";
|
|
637
|
+
const expiredAt = Math.floor(Date.now() / 1000) - 1000;
|
|
638
|
+
const mockedStorage = new StorageUtility(
|
|
639
|
+
mockStorage({
|
|
640
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
641
|
+
isLoggedIn: "true",
|
|
642
|
+
webId: "https://my.pod/profile#me",
|
|
643
|
+
},
|
|
644
|
+
}),
|
|
645
|
+
mockStorage({
|
|
646
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
647
|
+
clientId: "https://some.app/registration",
|
|
648
|
+
clientSecret: "some-secret",
|
|
649
|
+
issuer: "https://some.issuer",
|
|
650
|
+
expiresAt: String(expiredAt),
|
|
651
|
+
},
|
|
652
|
+
}),
|
|
653
|
+
);
|
|
654
|
+
const clientAuthn = getClientAuthentication({
|
|
655
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
await expect(
|
|
659
|
+
clientAuthn.validateCurrentSession(sessionId),
|
|
660
|
+
).resolves.toBeNull();
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it("returns session when client has not expired", async () => {
|
|
664
|
+
const sessionId = "mySession";
|
|
665
|
+
const futureExpiry = Math.floor(Date.now() / 1000) + 10000;
|
|
666
|
+
const mockedStorage = new StorageUtility(
|
|
667
|
+
mockStorage({
|
|
668
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
669
|
+
isLoggedIn: "true",
|
|
670
|
+
webId: "https://my.pod/profile#me",
|
|
671
|
+
},
|
|
672
|
+
}),
|
|
673
|
+
mockStorage({
|
|
674
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
675
|
+
clientId: "https://some.app/registration",
|
|
676
|
+
clientSecret: "some-secret",
|
|
677
|
+
issuer: "https://some.issuer",
|
|
678
|
+
expiresAt: String(futureExpiry),
|
|
679
|
+
},
|
|
680
|
+
}),
|
|
681
|
+
);
|
|
682
|
+
const clientAuthn = getClientAuthentication({
|
|
683
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
const result = await clientAuthn.validateCurrentSession(sessionId);
|
|
687
|
+
expect(result).not.toBeNull();
|
|
688
|
+
expect(result).toStrictEqual(
|
|
689
|
+
expect.objectContaining({
|
|
690
|
+
clientAppId: "https://some.app/registration",
|
|
691
|
+
issuer: "https://some.issuer",
|
|
692
|
+
}),
|
|
693
|
+
);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it("returns session when clientExpiresAt is 0 (never expires per OIDC DCR spec)", async () => {
|
|
697
|
+
const sessionId = "mySession";
|
|
698
|
+
const mockedStorage = new StorageUtility(
|
|
699
|
+
mockStorage({
|
|
700
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
701
|
+
isLoggedIn: "true",
|
|
702
|
+
webId: "https://my.pod/profile#me",
|
|
703
|
+
},
|
|
704
|
+
}),
|
|
705
|
+
mockStorage({
|
|
706
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
707
|
+
clientId: "https://some.app/registration",
|
|
708
|
+
clientSecret: "some-secret",
|
|
709
|
+
issuer: "https://some.issuer",
|
|
710
|
+
expiresAt: "0",
|
|
711
|
+
},
|
|
712
|
+
}),
|
|
713
|
+
);
|
|
714
|
+
const clientAuthn = getClientAuthentication({
|
|
715
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
const result = await clientAuthn.validateCurrentSession(sessionId);
|
|
719
|
+
expect(result).not.toBeNull();
|
|
720
|
+
expect(result).toStrictEqual(
|
|
721
|
+
expect.objectContaining({
|
|
722
|
+
clientAppId: "https://some.app/registration",
|
|
723
|
+
clientExpiresAt: 0,
|
|
724
|
+
}),
|
|
725
|
+
);
|
|
726
|
+
});
|
|
727
|
+
});
|
|
604
728
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//
|
|
2
1
|
// Copyright Inrupt Inc.
|
|
3
2
|
//
|
|
4
3
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
@@ -38,6 +37,20 @@ import {
|
|
|
38
37
|
import { normalizeCallbackUrl } from "@inrupt/oidc-client-ext";
|
|
39
38
|
import type { EventEmitter } from "events";
|
|
40
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Checks if a client's registration has expired.
|
|
42
|
+
*/
|
|
43
|
+
function isClientExpired(sessionInfo: { clientExpiresAt?: number }): boolean {
|
|
44
|
+
// clientExpiresAt === 0 means the client never expires (per OIDC DCR spec)
|
|
45
|
+
if (
|
|
46
|
+
sessionInfo.clientExpiresAt === undefined ||
|
|
47
|
+
sessionInfo.clientExpiresAt === 0
|
|
48
|
+
) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return sessionInfo.clientExpiresAt < Math.floor(Date.now() / 1000);
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
/**
|
|
42
55
|
* @hidden
|
|
43
56
|
*/
|
|
@@ -78,7 +91,7 @@ export default class ClientAuthentication extends ClientAuthenticationBase {
|
|
|
78
91
|
};
|
|
79
92
|
|
|
80
93
|
// Collects session information from storage, and returns them. Returns null
|
|
81
|
-
// if the expected information cannot be found.
|
|
94
|
+
// if the expected information cannot be found or if the client has expired.
|
|
82
95
|
// Note that the ID token is not stored, which means the session information
|
|
83
96
|
// cannot be validated at this point.
|
|
84
97
|
validateCurrentSession = async (
|
|
@@ -88,7 +101,8 @@ export default class ClientAuthentication extends ClientAuthenticationBase {
|
|
|
88
101
|
if (
|
|
89
102
|
sessionInfo === undefined ||
|
|
90
103
|
sessionInfo.clientAppId === undefined ||
|
|
91
|
-
sessionInfo.issuer === undefined
|
|
104
|
+
sessionInfo.issuer === undefined ||
|
|
105
|
+
isClientExpired(sessionInfo)
|
|
92
106
|
) {
|
|
93
107
|
return null;
|
|
94
108
|
}
|
|
@@ -152,7 +166,7 @@ export default class ClientAuthentication extends ClientAuthenticationBase {
|
|
|
152
166
|
// 'popstate' event which should be listened to is only sent on active
|
|
153
167
|
// navigation, which we will not have here.
|
|
154
168
|
// See https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event#when_popstate_is_sent
|
|
155
|
-
|
|
169
|
+
|
|
156
170
|
await new Promise<void>((resolve) => {
|
|
157
171
|
setTimeout(() => resolve(), 1);
|
|
158
172
|
});
|
package/src/Session.spec.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//
|
|
2
1
|
// Copyright Inrupt Inc.
|
|
3
2
|
//
|
|
4
3
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
@@ -26,6 +25,7 @@ import {
|
|
|
26
25
|
StorageUtility,
|
|
27
26
|
USER_SESSION_PREFIX,
|
|
28
27
|
} from "@inrupt/solid-client-authn-core";
|
|
28
|
+
// eslint-disable-next-line import/no-unresolved
|
|
29
29
|
import { mockStorage } from "@inrupt/solid-client-authn-core/mocks";
|
|
30
30
|
import { EventEmitter } from "stream";
|
|
31
31
|
import { mockClientAuthentication } from "./__mocks__/ClientAuthentication";
|
|
@@ -46,27 +46,11 @@ const mockLocalStorage = (stored: Record<string, string>) => {
|
|
|
46
46
|
});
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
declare const __setTestUrl: (url: string) => void;
|
|
50
|
+
|
|
49
51
|
const mockLocation = (mockedLocation: string) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// a browser would do). So instead we need to reset 'window.location' as a
|
|
53
|
-
// whole, and reset it's value after our test. We also need to
|
|
54
|
-
// replace the 'history' object, otherwise this error happens: "SecurityError:
|
|
55
|
-
// replaceState cannot update history to a URL which differs in components other
|
|
56
|
-
// than in path, query, or fragment.""
|
|
57
|
-
|
|
58
|
-
// (window as any) is used to override the window type definition and
|
|
59
|
-
// allow location to be written.
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
-
delete (window as any).location;
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
-
delete (window as any).history.replaceState;
|
|
64
|
-
|
|
65
|
-
// Set our window's location to our test value.
|
|
66
|
-
(window as any).location = {
|
|
67
|
-
href: mockedLocation,
|
|
68
|
-
} as Location;
|
|
69
|
-
window.history.replaceState = jest.fn();
|
|
52
|
+
__setTestUrl(mockedLocation);
|
|
53
|
+
jest.spyOn(window.history, "replaceState").mockImplementation(() => {});
|
|
70
54
|
};
|
|
71
55
|
|
|
72
56
|
describe("Session", () => {
|
|
@@ -119,11 +103,10 @@ describe("Session", () => {
|
|
|
119
103
|
// Let's make TypeScript and eslint angry! We'll set our window mock to
|
|
120
104
|
// undefined so that any references to its properties or methods explode.
|
|
121
105
|
// @ts-ignore-start
|
|
122
|
-
|
|
106
|
+
|
|
123
107
|
window = undefined;
|
|
124
108
|
// @ts-ignore-end
|
|
125
109
|
expect(() => {
|
|
126
|
-
// eslint-disable-next-line no-new
|
|
127
110
|
new Session({});
|
|
128
111
|
}).not.toThrow();
|
|
129
112
|
});
|
|
@@ -136,7 +119,7 @@ describe("Session", () => {
|
|
|
136
119
|
const mySession = new Session({ clientAuthentication });
|
|
137
120
|
// login never resolves if there are no errors,
|
|
138
121
|
// because a login redirects the user away from the page:
|
|
139
|
-
|
|
122
|
+
|
|
140
123
|
void mySession.login({});
|
|
141
124
|
expect(clientAuthnLogin).toHaveBeenCalled();
|
|
142
125
|
});
|
|
@@ -147,7 +130,7 @@ describe("Session", () => {
|
|
|
147
130
|
const mySession = new Session({ clientAuthentication });
|
|
148
131
|
// login never resolves if there are no errors,
|
|
149
132
|
// because a login redirects the user away from the page:
|
|
150
|
-
|
|
133
|
+
|
|
151
134
|
void mySession.login({
|
|
152
135
|
tokenType: "Bearer",
|
|
153
136
|
});
|
|
@@ -168,7 +151,7 @@ describe("Session", () => {
|
|
|
168
151
|
};
|
|
169
152
|
// login never resolves if there are no errors,
|
|
170
153
|
// because a login redirects the user away from the page:
|
|
171
|
-
|
|
154
|
+
|
|
172
155
|
void objectWithLogin.login({});
|
|
173
156
|
expect(clientAuthnLogin).toHaveBeenCalled();
|
|
174
157
|
});
|
|
@@ -235,7 +218,7 @@ describe("Session", () => {
|
|
|
235
218
|
|
|
236
219
|
describe("handleIncomingRedirect", () => {
|
|
237
220
|
it("uses current window location as default redirect URL", async () => {
|
|
238
|
-
mockLocation("https://some.url");
|
|
221
|
+
mockLocation("https://some.url/");
|
|
239
222
|
const clientAuthentication = mockClientAuthentication();
|
|
240
223
|
const incomingRedirectHandler = jest
|
|
241
224
|
.spyOn(clientAuthentication, "handleIncomingRedirect")
|
|
@@ -244,13 +227,13 @@ describe("Session", () => {
|
|
|
244
227
|
const mySession = new Session({ clientAuthentication });
|
|
245
228
|
await mySession.handleIncomingRedirect();
|
|
246
229
|
expect(incomingRedirectHandler).toHaveBeenCalledWith(
|
|
247
|
-
"https://some.url",
|
|
230
|
+
"https://some.url/",
|
|
248
231
|
mySession.events,
|
|
249
232
|
);
|
|
250
233
|
});
|
|
251
234
|
|
|
252
235
|
it("wraps ClientAuthentication handleIncomingRedirect", async () => {
|
|
253
|
-
mockLocation("https://some.url");
|
|
236
|
+
mockLocation("https://some.url/");
|
|
254
237
|
const clientAuthentication = mockClientAuthentication();
|
|
255
238
|
const incomingRedirectHandler = jest
|
|
256
239
|
.spyOn(clientAuthentication, "handleIncomingRedirect")
|
|
@@ -380,6 +363,7 @@ describe("Session", () => {
|
|
|
380
363
|
"https://my.app/?code=someCode&state=arizona",
|
|
381
364
|
);
|
|
382
365
|
// We know that it has been set by the call to `blockingRequest`.
|
|
366
|
+
// FIXME: This can be fixed when we drop support for Node 20 by using Promise.withResolvers.
|
|
383
367
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
384
368
|
continueAfterSecondRequest!();
|
|
385
369
|
const tokenRequests = await Promise.all([
|
|
@@ -425,7 +409,7 @@ describe("Session", () => {
|
|
|
425
409
|
incomingRedirectPromise,
|
|
426
410
|
) as typeof clientAuthentication.handleIncomingRedirect;
|
|
427
411
|
const mySession = new Session({ clientAuthentication });
|
|
428
|
-
|
|
412
|
+
|
|
429
413
|
void mySession.handleIncomingRedirect({
|
|
430
414
|
url: "https://some.redirect/url",
|
|
431
415
|
restorePreviousSession: true,
|
|
@@ -458,9 +442,12 @@ describe("Session", () => {
|
|
|
458
442
|
.mockReturnValueOnce(
|
|
459
443
|
incomingRedirectPromise,
|
|
460
444
|
) as typeof clientAuthentication.handleIncomingRedirect;
|
|
461
|
-
const validateCurrentSessionPromise = Promise.resolve(
|
|
462
|
-
"https://some.issuer/",
|
|
463
|
-
|
|
445
|
+
const validateCurrentSessionPromise = Promise.resolve({
|
|
446
|
+
issuer: "https://some.issuer/",
|
|
447
|
+
clientAppId: "some client ID",
|
|
448
|
+
redirectUrl: "https://some.redirect/url",
|
|
449
|
+
tokenType: "DPoP",
|
|
450
|
+
});
|
|
464
451
|
clientAuthentication.validateCurrentSession = jest
|
|
465
452
|
.fn()
|
|
466
453
|
.mockReturnValue(
|
|
@@ -468,7 +455,7 @@ describe("Session", () => {
|
|
|
468
455
|
) as typeof clientAuthentication.validateCurrentSession;
|
|
469
456
|
|
|
470
457
|
const mySession = new Session({ clientAuthentication });
|
|
471
|
-
|
|
458
|
+
|
|
472
459
|
void mySession.handleIncomingRedirect({
|
|
473
460
|
url: "https://some.redirect/url",
|
|
474
461
|
restorePreviousSession: true,
|
|
@@ -507,7 +494,7 @@ describe("Session", () => {
|
|
|
507
494
|
undefined,
|
|
508
495
|
) as typeof clientAuthentication.handleIncomingRedirect;
|
|
509
496
|
const mySession = new Session({ clientAuthentication });
|
|
510
|
-
|
|
497
|
+
|
|
511
498
|
void mySession.handleIncomingRedirect({
|
|
512
499
|
url: "https://some.redirect/url",
|
|
513
500
|
restorePreviousSession: true,
|
|
@@ -536,6 +523,7 @@ describe("Session", () => {
|
|
|
536
523
|
issuer: "https://some.issuer",
|
|
537
524
|
clientAppId: "some client ID",
|
|
538
525
|
clientAppSecret: "some client secret",
|
|
526
|
+
clientExpiresAt: Math.floor(Date.now() / 1000) + 10000,
|
|
539
527
|
redirectUrl: "https://some.redirect/url",
|
|
540
528
|
tokenType: "DPoP",
|
|
541
529
|
});
|
|
@@ -553,7 +541,7 @@ describe("Session", () => {
|
|
|
553
541
|
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
554
542
|
|
|
555
543
|
const mySession = new Session({ clientAuthentication });
|
|
556
|
-
|
|
544
|
+
|
|
557
545
|
void mySession.handleIncomingRedirect({
|
|
558
546
|
url: "https://some.redirect/url",
|
|
559
547
|
restorePreviousSession: true,
|
|
@@ -652,7 +640,7 @@ describe("Session", () => {
|
|
|
652
640
|
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
653
641
|
|
|
654
642
|
const mySession = new Session({ clientAuthentication });
|
|
655
|
-
|
|
643
|
+
|
|
656
644
|
void mySession.handleIncomingRedirect("https://some.redirect/url");
|
|
657
645
|
expect(window.localStorage.getItem(KEY_CURRENT_URL)).toBeNull();
|
|
658
646
|
expect(clientAuthentication.login).not.toHaveBeenCalled();
|
|
@@ -689,7 +677,7 @@ describe("Session", () => {
|
|
|
689
677
|
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
690
678
|
|
|
691
679
|
const mySession = new Session({ clientAuthentication });
|
|
692
|
-
|
|
680
|
+
|
|
693
681
|
void mySession.handleIncomingRedirect({
|
|
694
682
|
url: "https://some.redirect/url",
|
|
695
683
|
restorePreviousSession: false,
|
|
@@ -728,7 +716,7 @@ describe("Session", () => {
|
|
|
728
716
|
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
729
717
|
|
|
730
718
|
const mySession = new Session({ clientAuthentication });
|
|
731
|
-
|
|
719
|
+
|
|
732
720
|
void mySession.handleIncomingRedirect("https://some.redirect/url");
|
|
733
721
|
expect(mySession.info.isLoggedIn).toBe(false);
|
|
734
722
|
// The local storage should have been cleared by the auth error
|
|
@@ -761,6 +749,53 @@ describe("Session", () => {
|
|
|
761
749
|
// The local storage should have been cleared by the auth error
|
|
762
750
|
expect(window.localStorage.getItem(KEY_CURRENT_SESSION)).toBeNull();
|
|
763
751
|
});
|
|
752
|
+
|
|
753
|
+
it("does not attempt silent authentication if the stored client has expired", async () => {
|
|
754
|
+
const sessionId = "mySession";
|
|
755
|
+
mockLocalStorage({
|
|
756
|
+
[KEY_CURRENT_SESSION]: sessionId,
|
|
757
|
+
});
|
|
758
|
+
mockLocation("https://mock.current/location");
|
|
759
|
+
|
|
760
|
+
// Set up storage with an expired client (expiresAt in the past)
|
|
761
|
+
const expiredAt = Math.floor(Date.now() / 1000) - 1000;
|
|
762
|
+
const mockedStorage = new StorageUtility(
|
|
763
|
+
mockStorage({
|
|
764
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
765
|
+
isLoggedIn: "true",
|
|
766
|
+
webId: "https://my.pod/profile#me",
|
|
767
|
+
},
|
|
768
|
+
}),
|
|
769
|
+
mockStorage({
|
|
770
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
771
|
+
clientId: "https://some.app/registration",
|
|
772
|
+
clientSecret: "some-secret",
|
|
773
|
+
issuer: "https://some.issuer",
|
|
774
|
+
expiresAt: String(expiredAt),
|
|
775
|
+
},
|
|
776
|
+
}),
|
|
777
|
+
);
|
|
778
|
+
const clientAuthentication = mockClientAuthentication({
|
|
779
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// Mock handleIncomingRedirect to return undefined (no OAuth params in URL)
|
|
783
|
+
clientAuthentication.handleIncomingRedirect = jest
|
|
784
|
+
.fn<typeof clientAuthentication.handleIncomingRedirect>()
|
|
785
|
+
.mockResolvedValue(undefined);
|
|
786
|
+
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
787
|
+
|
|
788
|
+
const mySession = new Session({ clientAuthentication });
|
|
789
|
+
const result = await mySession.handleIncomingRedirect({
|
|
790
|
+
url: "https://some.redirect/url",
|
|
791
|
+
restorePreviousSession: true,
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
// Silent auth should NOT have been attempted because client is expired
|
|
795
|
+
expect(clientAuthentication.login).not.toHaveBeenCalled();
|
|
796
|
+
// The function should resolve (not hang)
|
|
797
|
+
expect(result).toBeUndefined();
|
|
798
|
+
});
|
|
764
799
|
});
|
|
765
800
|
|
|
766
801
|
describe("events.on", () => {
|
package/src/Session.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//
|
|
2
1
|
// Copyright Inrupt Inc.
|
|
3
2
|
//
|
|
4
3
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
@@ -31,6 +30,7 @@ import {
|
|
|
31
30
|
mockStorageUtility,
|
|
32
31
|
mockIncomingRedirectHandler,
|
|
33
32
|
mockLogoutHandler,
|
|
33
|
+
// eslint-disable-next-line import/no-unresolved
|
|
34
34
|
} from "@inrupt/solid-client-authn-core/mocks";
|
|
35
35
|
|
|
36
36
|
import ClientAuthentication from "../ClientAuthentication";
|
package/src/constant.ts
CHANGED
package/src/defaultSession.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//
|
|
2
1
|
// Copyright Inrupt Inc.
|
|
3
2
|
//
|
|
4
3
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
@@ -48,7 +47,7 @@ export function getDefaultSession(): Session {
|
|
|
48
47
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch}
|
|
49
48
|
* @since 1.3.0
|
|
50
49
|
*/
|
|
51
|
-
|
|
50
|
+
|
|
52
51
|
export function fetch(
|
|
53
52
|
...args: Parameters<Session["fetch"]>
|
|
54
53
|
): ReturnType<Session["fetch"]> {
|
package/src/dependencies.spec.ts
CHANGED
package/src/dependencies.ts
CHANGED