@inrupt/solid-client-authn-browser 1.17.5 → 2.1.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/dist/ClientAuthentication.d.ts +1 -0
- package/dist/Session.d.ts +4 -67
- package/dist/defaultSession.d.ts +0 -28
- package/dist/dependencies.d.ts +1 -2
- package/dist/index.d.ts +2 -3
- package/dist/index.js +40 -153
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34 -143
- package/dist/index.mjs.map +1 -1
- package/dist/login/oidc/__mocks__/IOidcOptions.d.ts +2 -0
- package/dist/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.d.ts +4 -0
- package/dist/solid-client-authn.bundle.js +19 -19
- package/dist/solid-client-authn.bundle.js.map +1 -1
- package/package.json +7 -8
- package/rollup.config.mjs +28 -14
- package/src/ClientAuthentication.spec.ts +1 -13
- package/src/Session.spec.ts +11 -178
- package/src/Session.ts +6 -98
- package/src/defaultSession.spec.ts +0 -18
- package/src/defaultSession.ts +0 -40
- package/src/dependencies.ts +1 -2
- package/src/index.spec.ts +1 -2
- package/src/index.ts +9 -3
- package/src/login/oidc/ClientRegistrar.spec.ts +9 -9
- package/src/login/oidc/ClientRegistrar.ts +15 -8
- package/src/login/oidc/IssuerConfigFetcher.spec.ts +17 -32
- package/src/login/oidc/IssuerConfigFetcher.ts +1 -5
- package/src/login/oidc/OidcLoginHandler.spec.ts +6 -3
- package/src/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.spec.ts +28 -42
- package/src/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.ts +18 -34
- package/src/login/oidc/incomingRedirectHandler/ErrorOidcHandler.spec.ts +0 -11
- package/src/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.spec.ts +1 -12
- package/src/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.spec.ts +1 -0
- package/src/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.ts +1 -2
- package/.prettierignore +0 -3
- package/sampleFlows/SolidAuthClientArchitecture.png +0 -0
- package/sampleFlows/authorization-code-with-pkce.png +0 -0
- package/sampleFlows/authorization-code-with-pkce.seqdiag +0 -46
- package/sampleFlows/authorization-code.png +0 -0
- package/sampleFlows/authorization-code.seqdiag +0 -50
- package/sampleFlows/client-credentials.png +0 -0
- package/sampleFlows/client-credentials.seqdiag +0 -38
- package/sampleFlows/legacy-implicit-flow.png +0 -0
- package/sampleFlows/legacy-implicit-flow.seqdiag +0 -43
- package/sampleFlows/self-signed.png +0 -0
- package/sampleFlows/self-signed.seqdiag +0 -49
- package/sampleFlows/signatory-access-control.seqdiag +0 -36
- package/sampleFlows/signatory-access-control.svg +0 -1
- package/sonar-project.properties +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inrupt/solid-client-authn-browser",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"browser": "dist/index.js",
|
|
@@ -25,19 +25,18 @@
|
|
|
25
25
|
"build-docs-preview-site": "npm run build-api-docs; cd docs/api; make html"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@types/node": "^20.
|
|
29
|
-
"@types/uuid": "^9.0.
|
|
28
|
+
"@types/node": "^20.11.27",
|
|
29
|
+
"@types/uuid": "^9.0.8"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@inrupt/oidc-client-ext": "^1.
|
|
33
|
-
"@inrupt/solid-client-authn-core": "^1.
|
|
34
|
-
"@inrupt/universal-fetch": "^1.0.2",
|
|
32
|
+
"@inrupt/oidc-client-ext": "^2.1.0",
|
|
33
|
+
"@inrupt/solid-client-authn-core": "^2.1.0",
|
|
35
34
|
"events": "^3.3.0",
|
|
36
|
-
"jose": "^
|
|
35
|
+
"jose": "^5.1.3",
|
|
37
36
|
"uuid": "^9.0.1"
|
|
38
37
|
},
|
|
39
38
|
"publishConfig": {
|
|
40
39
|
"access": "public"
|
|
41
40
|
},
|
|
42
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "259a56701e0e1f6d602055efcf8eb3ab26c792fd"
|
|
43
42
|
}
|
package/rollup.config.mjs
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
//
|
|
2
|
+
// Copyright Inrupt Inc.
|
|
3
|
+
//
|
|
4
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
6
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
7
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
8
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
9
|
+
// subject to the following conditions:
|
|
10
|
+
//
|
|
11
|
+
// The above copyright notice and this permission notice shall be included in
|
|
12
|
+
// all copies or substantial portions of the Software.
|
|
13
|
+
//
|
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
15
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
16
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
17
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
18
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
19
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
|
+
//
|
|
3
21
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import createConfig, { createSharedConfig } from '@inrupt/base-rollup-config';
|
|
11
|
-
|
|
12
|
-
const require = createRequire(import.meta.url);
|
|
13
|
-
const pkg = require("./package.json");
|
|
22
|
+
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
|
23
|
+
import commonjs from "@rollup/plugin-commonjs";
|
|
24
|
+
import terser from "@rollup/plugin-terser";
|
|
25
|
+
import createConfig, { createSharedConfig } from "@inrupt/base-rollup-config";
|
|
26
|
+
// eslint-disable-next-line import/extensions
|
|
27
|
+
import pkg from "./package.json" assert { type: "json" };
|
|
14
28
|
|
|
15
29
|
export default [
|
|
16
30
|
...createConfig(pkg),
|
|
@@ -35,12 +49,12 @@ export default [
|
|
|
35
49
|
browser: true,
|
|
36
50
|
// Bundles in the events library from the node_modules
|
|
37
51
|
// rather than trying to use the node events library
|
|
38
|
-
preferBuiltins: false
|
|
52
|
+
preferBuiltins: false,
|
|
39
53
|
}),
|
|
40
54
|
// Adds support for importing cjs libraries such as the 'events' library
|
|
41
55
|
commonjs(),
|
|
42
56
|
// Minifies the browser bundle
|
|
43
57
|
terser(),
|
|
44
|
-
]
|
|
58
|
+
],
|
|
45
59
|
},
|
|
46
60
|
];
|
|
@@ -37,9 +37,6 @@ import {
|
|
|
37
37
|
mockLogoutHandler,
|
|
38
38
|
} from "@inrupt/solid-client-authn-core/mocks";
|
|
39
39
|
|
|
40
|
-
import { fetch } from "@inrupt/universal-fetch";
|
|
41
|
-
import type * as UniversalFetch from "@inrupt/universal-fetch";
|
|
42
|
-
|
|
43
40
|
import { mockLoginHandler } from "./login/__mocks__/LoginHandler";
|
|
44
41
|
import {
|
|
45
42
|
mockSessionInfoManager,
|
|
@@ -48,15 +45,7 @@ import {
|
|
|
48
45
|
import ClientAuthentication from "./ClientAuthentication";
|
|
49
46
|
import { mockDefaultIssuerConfigFetcher } from "./login/oidc/__mocks__/IssuerConfigFetcher";
|
|
50
47
|
|
|
51
|
-
jest.
|
|
52
|
-
const fetchModule = jest.requireActual(
|
|
53
|
-
"@inrupt/universal-fetch",
|
|
54
|
-
) as typeof UniversalFetch;
|
|
55
|
-
return {
|
|
56
|
-
...fetchModule,
|
|
57
|
-
fetch: jest.fn<typeof fetch>(),
|
|
58
|
-
};
|
|
59
|
-
});
|
|
48
|
+
jest.spyOn(globalThis, "fetch").mockResolvedValue(new Response());
|
|
60
49
|
|
|
61
50
|
jest.mock("@inrupt/solid-client-authn-core", () => {
|
|
62
51
|
const actualCoreModule = jest.requireActual(
|
|
@@ -64,7 +53,6 @@ jest.mock("@inrupt/solid-client-authn-core", () => {
|
|
|
64
53
|
) as typeof SolidClientAuthnCore;
|
|
65
54
|
return {
|
|
66
55
|
...actualCoreModule,
|
|
67
|
-
fetchJwks: jest.fn(),
|
|
68
56
|
};
|
|
69
57
|
});
|
|
70
58
|
|
package/src/Session.spec.ts
CHANGED
|
@@ -27,8 +27,7 @@ import {
|
|
|
27
27
|
USER_SESSION_PREFIX,
|
|
28
28
|
} from "@inrupt/solid-client-authn-core";
|
|
29
29
|
import { mockStorage } from "@inrupt/solid-client-authn-core/mocks";
|
|
30
|
-
import {
|
|
31
|
-
import type * as UniversalFetch from "@inrupt/universal-fetch";
|
|
30
|
+
import { EventEmitter } from "stream";
|
|
32
31
|
import { mockClientAuthentication } from "./__mocks__/ClientAuthentication";
|
|
33
32
|
import { Session } from "./Session";
|
|
34
33
|
import { LocalStorageMock } from "./storage/__mocks__/LocalStorage";
|
|
@@ -38,15 +37,7 @@ import type ClientAuthentication from "./ClientAuthentication";
|
|
|
38
37
|
|
|
39
38
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
40
39
|
|
|
41
|
-
jest.
|
|
42
|
-
const fetchModule = jest.requireActual(
|
|
43
|
-
"@inrupt/universal-fetch",
|
|
44
|
-
) as typeof UniversalFetch;
|
|
45
|
-
return {
|
|
46
|
-
...fetchModule,
|
|
47
|
-
fetch: jest.fn<typeof fetch>(),
|
|
48
|
-
};
|
|
49
|
-
});
|
|
40
|
+
jest.spyOn(globalThis, "fetch").mockResolvedValue(new Response());
|
|
50
41
|
|
|
51
42
|
const mockLocalStorage = (stored: Record<string, string>) => {
|
|
52
43
|
Object.defineProperty(window, "localStorage", {
|
|
@@ -164,7 +155,7 @@ describe("Session", () => {
|
|
|
164
155
|
expect.objectContaining({
|
|
165
156
|
tokenType: "Bearer",
|
|
166
157
|
}),
|
|
167
|
-
mySession,
|
|
158
|
+
mySession.events,
|
|
168
159
|
);
|
|
169
160
|
});
|
|
170
161
|
|
|
@@ -254,7 +245,7 @@ describe("Session", () => {
|
|
|
254
245
|
await mySession.handleIncomingRedirect();
|
|
255
246
|
expect(incomingRedirectHandler).toHaveBeenCalledWith(
|
|
256
247
|
"https://some.url",
|
|
257
|
-
mySession,
|
|
248
|
+
mySession.events,
|
|
258
249
|
);
|
|
259
250
|
});
|
|
260
251
|
|
|
@@ -344,7 +335,10 @@ describe("Session", () => {
|
|
|
344
335
|
const mySession = new Session({ clientAuthentication });
|
|
345
336
|
await mySession.handleIncomingRedirect("https://some.url");
|
|
346
337
|
expect(mySession.info.expirationDate).toBe(MOCK_TIMESTAMP + 1337);
|
|
347
|
-
mySession.
|
|
338
|
+
(mySession.events as EventEmitter).emit(
|
|
339
|
+
EVENTS.SESSION_EXTENDED,
|
|
340
|
+
1337 * 2,
|
|
341
|
+
);
|
|
348
342
|
expect(mySession.info.expirationDate).toBe(
|
|
349
343
|
MOCK_TIMESTAMP + 1337 * 2 * 1000,
|
|
350
344
|
);
|
|
@@ -578,10 +572,10 @@ describe("Session", () => {
|
|
|
578
572
|
},
|
|
579
573
|
expect.anything(),
|
|
580
574
|
);
|
|
581
|
-
// Check that second parameter is
|
|
575
|
+
// Check that second parameter is an event emitter
|
|
582
576
|
expect(
|
|
583
577
|
(clientAuthentication.login as any).mock.calls[0][1],
|
|
584
|
-
).toBeInstanceOf(
|
|
578
|
+
).toBeInstanceOf(EventEmitter);
|
|
585
579
|
});
|
|
586
580
|
|
|
587
581
|
it("resolves handleIncomingRedirect if silent authentication could not be started", async () => {
|
|
@@ -763,154 +757,12 @@ describe("Session", () => {
|
|
|
763
757
|
});
|
|
764
758
|
|
|
765
759
|
const mySession = new Session({ clientAuthentication });
|
|
766
|
-
mySession.emit(EVENTS.SESSION_EXPIRED);
|
|
760
|
+
(mySession.events as EventEmitter).emit(EVENTS.SESSION_EXPIRED);
|
|
767
761
|
// The local storage should have been cleared by the auth error
|
|
768
762
|
expect(window.localStorage.getItem(KEY_CURRENT_SESSION)).toBeNull();
|
|
769
763
|
});
|
|
770
764
|
});
|
|
771
765
|
|
|
772
|
-
describe("legacy events handlers", () => {
|
|
773
|
-
describe("onError", () => {
|
|
774
|
-
it("calls the registered callback on error", async () => {
|
|
775
|
-
const onErrorCallback = jest.fn();
|
|
776
|
-
const mySession = new Session();
|
|
777
|
-
mySession.onError(onErrorCallback);
|
|
778
|
-
mySession.emit(EVENTS.ERROR, "error", "error_description");
|
|
779
|
-
expect(onErrorCallback).toHaveBeenCalledWith(
|
|
780
|
-
"error",
|
|
781
|
-
"error_description",
|
|
782
|
-
);
|
|
783
|
-
});
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
describe("onLogin", () => {
|
|
787
|
-
it("calls the registered callback on login", async () => {
|
|
788
|
-
const myCallback = jest.fn();
|
|
789
|
-
const clientAuthentication = mockClientAuthentication();
|
|
790
|
-
clientAuthentication.handleIncomingRedirect = (
|
|
791
|
-
jest.fn() as any
|
|
792
|
-
).mockResolvedValue({
|
|
793
|
-
isLoggedIn: true,
|
|
794
|
-
sessionId: "a session ID",
|
|
795
|
-
webId: "https://some.webid#them",
|
|
796
|
-
});
|
|
797
|
-
mockLocalStorage({});
|
|
798
|
-
const mySession = new Session({ clientAuthentication });
|
|
799
|
-
mySession.onLogin(myCallback);
|
|
800
|
-
await mySession.handleIncomingRedirect("https://some.url");
|
|
801
|
-
expect(myCallback).toHaveBeenCalled();
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
it("does not call the registered callback if login isn't successful", async () => {
|
|
805
|
-
const failCallback = (): void => {
|
|
806
|
-
throw new Error(
|
|
807
|
-
"Should *NOT* call callback - this means test has failed!",
|
|
808
|
-
);
|
|
809
|
-
};
|
|
810
|
-
const clientAuthentication = mockClientAuthentication();
|
|
811
|
-
clientAuthentication.handleIncomingRedirect = jest.fn(
|
|
812
|
-
async (_url: string) => {
|
|
813
|
-
return {
|
|
814
|
-
isLoggedIn: false,
|
|
815
|
-
sessionId: "a session ID",
|
|
816
|
-
webId: "https://some.webid#them",
|
|
817
|
-
};
|
|
818
|
-
},
|
|
819
|
-
);
|
|
820
|
-
const mySession = new Session({ clientAuthentication });
|
|
821
|
-
mySession.onLogin(failCallback);
|
|
822
|
-
await expect(
|
|
823
|
-
mySession.handleIncomingRedirect("https://some.url"),
|
|
824
|
-
).resolves.not.toThrow();
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
it("sets the appropriate information before calling the callback", async () => {
|
|
828
|
-
const clientAuthentication = mockClientAuthentication();
|
|
829
|
-
clientAuthentication.handleIncomingRedirect = jest.fn(
|
|
830
|
-
async (_url: string) => {
|
|
831
|
-
return {
|
|
832
|
-
isLoggedIn: true,
|
|
833
|
-
sessionId: "a session ID",
|
|
834
|
-
webId: "https://some.webid#them",
|
|
835
|
-
};
|
|
836
|
-
},
|
|
837
|
-
);
|
|
838
|
-
const mySession = new Session({ clientAuthentication });
|
|
839
|
-
const myCallback = jest.fn((): void => {
|
|
840
|
-
expect(mySession.info.webId).toBe("https://some.webid#them");
|
|
841
|
-
});
|
|
842
|
-
mySession.onLogin(myCallback);
|
|
843
|
-
await mySession.handleIncomingRedirect("https://some.url");
|
|
844
|
-
expect(myCallback).toHaveBeenCalled();
|
|
845
|
-
// Verify that the conditional assertion has been called
|
|
846
|
-
expect.assertions(2);
|
|
847
|
-
});
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
describe("onLogout", () => {
|
|
851
|
-
it("calls the registered callback on logout", async () => {
|
|
852
|
-
const myCallback = jest.fn();
|
|
853
|
-
const mySession = new Session({
|
|
854
|
-
clientAuthentication: mockClientAuthentication(),
|
|
855
|
-
});
|
|
856
|
-
|
|
857
|
-
mySession.onLogout(myCallback);
|
|
858
|
-
await mySession.logout();
|
|
859
|
-
expect(myCallback).toHaveBeenCalled();
|
|
860
|
-
});
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
describe("onSessionRestore", () => {
|
|
864
|
-
it("calls the registered callback on session restore", async () => {
|
|
865
|
-
// Set our window's location to our test value.
|
|
866
|
-
const defaultLocation = "https://coolSite.com/resource";
|
|
867
|
-
const currentLocation = "https://coolSite.com/redirect";
|
|
868
|
-
|
|
869
|
-
// This pretends we have previously triggered silent authentication and stored
|
|
870
|
-
// the location.
|
|
871
|
-
mockLocalStorage({
|
|
872
|
-
[KEY_CURRENT_URL]: defaultLocation,
|
|
873
|
-
});
|
|
874
|
-
// This acts as the URL the user has been redirected to.
|
|
875
|
-
mockLocation(currentLocation);
|
|
876
|
-
// This pretends the login is successful.
|
|
877
|
-
const clientAuthentication = mockClientAuthentication();
|
|
878
|
-
clientAuthentication.handleIncomingRedirect = (
|
|
879
|
-
jest.fn() as any
|
|
880
|
-
).mockResolvedValue({
|
|
881
|
-
isLoggedIn: true,
|
|
882
|
-
sessionId: "a session ID",
|
|
883
|
-
webId: "https://some.webid#them",
|
|
884
|
-
});
|
|
885
|
-
|
|
886
|
-
const mySession = new Session({
|
|
887
|
-
clientAuthentication,
|
|
888
|
-
});
|
|
889
|
-
const myCallback = (urlBeforeRestore: string): void => {
|
|
890
|
-
expect(urlBeforeRestore).toEqual(defaultLocation);
|
|
891
|
-
};
|
|
892
|
-
|
|
893
|
-
mySession.onSessionRestore(myCallback);
|
|
894
|
-
await mySession.handleIncomingRedirect(currentLocation);
|
|
895
|
-
|
|
896
|
-
// This verifies that the callback has been called
|
|
897
|
-
expect.assertions(1);
|
|
898
|
-
});
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
describe("onSessionExpiration", () => {
|
|
902
|
-
it("calls the provided callback when receiving the appropriate event", async () => {
|
|
903
|
-
const myCallback = jest.fn();
|
|
904
|
-
const mySession = new Session({
|
|
905
|
-
clientAuthentication: mockClientAuthentication(),
|
|
906
|
-
});
|
|
907
|
-
mySession.onSessionExpiration(myCallback);
|
|
908
|
-
mySession.emit(EVENTS.SESSION_EXPIRED);
|
|
909
|
-
expect(myCallback).toHaveBeenCalled();
|
|
910
|
-
});
|
|
911
|
-
});
|
|
912
|
-
});
|
|
913
|
-
|
|
914
766
|
describe("events.on", () => {
|
|
915
767
|
describe("login", () => {
|
|
916
768
|
it("calls the registered callback on login", async () => {
|
|
@@ -1017,23 +869,4 @@ describe("Session", () => {
|
|
|
1017
869
|
});
|
|
1018
870
|
});
|
|
1019
871
|
});
|
|
1020
|
-
|
|
1021
|
-
describe("proxies events to the session", () => {
|
|
1022
|
-
// This describe block is only required as long as Session extends the EventEmitter
|
|
1023
|
-
// class.
|
|
1024
|
-
it("proxies the EventEmitter calls from events to the session object", () => {
|
|
1025
|
-
const mySession = new Session();
|
|
1026
|
-
const spiedOn = jest.spyOn(mySession, "on");
|
|
1027
|
-
mySession.events.on("login", jest.fn());
|
|
1028
|
-
expect(spiedOn).toHaveBeenCalled();
|
|
1029
|
-
});
|
|
1030
|
-
|
|
1031
|
-
it("throws on calls from events which aren't part of the EventEmitter interface", () => {
|
|
1032
|
-
const mySession = new Session();
|
|
1033
|
-
// @ts-expect-error onLogin is a function on Session, and not SessionEventEmitter
|
|
1034
|
-
expect(() => mySession.events.onLogin(jest.fn())).toThrow(
|
|
1035
|
-
"[onLogin] is not supported",
|
|
1036
|
-
);
|
|
1037
|
-
});
|
|
1038
|
-
});
|
|
1039
872
|
});
|
package/src/Session.ts
CHANGED
|
@@ -30,8 +30,7 @@ import type {
|
|
|
30
30
|
ISessionEventListener,
|
|
31
31
|
ILogoutOptions,
|
|
32
32
|
} from "@inrupt/solid-client-authn-core";
|
|
33
|
-
import { EVENTS
|
|
34
|
-
import type { fetch } from "@inrupt/universal-fetch";
|
|
33
|
+
import { EVENTS } from "@inrupt/solid-client-authn-core";
|
|
35
34
|
import { v4 } from "uuid";
|
|
36
35
|
import EventEmitter from "events";
|
|
37
36
|
import type ClientAuthentication from "./ClientAuthentication";
|
|
@@ -66,29 +65,11 @@ export interface IHandleIncomingRedirectOptions {
|
|
|
66
65
|
* If your app's access has not expired yet and re-activation completed
|
|
67
66
|
* successfully, a `sessionRestore` event will be fired with the URL the user
|
|
68
67
|
* was at before they were redirected to their Solid Identity Provider.
|
|
69
|
-
* {@
|
|
68
|
+
* See {@link ISessionEventListener}: a callback can be registered to
|
|
69
|
+
* `session.events.on(EVENTS.SESSION_RESTORED, callback)`.
|
|
70
70
|
*/
|
|
71
71
|
restorePreviousSession?: boolean;
|
|
72
72
|
|
|
73
|
-
/**
|
|
74
|
-
* Inrupt's Enterprise Solid Server can set a cookie to allow the browser to
|
|
75
|
-
* access private resources on a Pod. In order to mitigate the logout-on-refresh
|
|
76
|
-
* issue on the short term, the server also implemented a session endpoint
|
|
77
|
-
* enabling the client app to know whether the cookie is set. When a user
|
|
78
|
-
* logs in to a server that has that capability enabled, applications that set
|
|
79
|
-
* this option to `true` will be able to make use of it.
|
|
80
|
-
*
|
|
81
|
-
* If your app supports the newest session restore approach, and `restorePreviousSession`
|
|
82
|
-
* is set to true, this option is automatically set to false, but your app will
|
|
83
|
-
* not be logged out when reloaded.
|
|
84
|
-
*
|
|
85
|
-
* `useEssSession` defaults to false and will be removed in the future; to
|
|
86
|
-
* preserve sessions across page reloads, use of `restorePreviousSession` is
|
|
87
|
-
* recommended.
|
|
88
|
-
*
|
|
89
|
-
* @deprecated unreleased
|
|
90
|
-
*/
|
|
91
|
-
useEssSession?: boolean;
|
|
92
73
|
/**
|
|
93
74
|
* The URL of the page handling the redirect, including the query
|
|
94
75
|
* parameters — these contain the information to process the login.
|
|
@@ -136,7 +117,7 @@ function isLoggedIn(
|
|
|
136
117
|
/**
|
|
137
118
|
* 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.
|
|
138
119
|
*/
|
|
139
|
-
export class Session
|
|
120
|
+
export class Session implements IHasSessionEventListener {
|
|
140
121
|
/**
|
|
141
122
|
* Information regarding the current session.
|
|
142
123
|
*/
|
|
@@ -172,18 +153,7 @@ export class Session extends EventEmitter implements IHasSessionEventListener {
|
|
|
172
153
|
sessionOptions: Partial<ISessionOptions> = {},
|
|
173
154
|
sessionId: string | undefined = undefined,
|
|
174
155
|
) {
|
|
175
|
-
|
|
176
|
-
// Until Session no longer implements EventEmitter, this.events is just a proxy
|
|
177
|
-
// to this (with some interface filtering). When we make the breaking change,
|
|
178
|
-
// this.events will be a regular EventEmitter (implementing ISessionEventEmitter):
|
|
179
|
-
// this.events = new EventEmitter();
|
|
180
|
-
this.events = new Proxy(
|
|
181
|
-
this,
|
|
182
|
-
buildProxyHandler(
|
|
183
|
-
Session.prototype,
|
|
184
|
-
"events only implements ISessionEventListener",
|
|
185
|
-
),
|
|
186
|
-
);
|
|
156
|
+
this.events = new EventEmitter();
|
|
187
157
|
if (sessionOptions.clientAuthentication) {
|
|
188
158
|
this.clientAuthentication = sessionOptions.clientAuthentication;
|
|
189
159
|
} else if (sessionOptions.secureStorage && sessionOptions.insecureStorage) {
|
|
@@ -321,7 +291,7 @@ export class Session extends EventEmitter implements IHasSessionEventListener {
|
|
|
321
291
|
* Completes the login process by processing the information provided by the
|
|
322
292
|
* Solid identity provider through redirect.
|
|
323
293
|
*
|
|
324
|
-
* @param options See {@
|
|
294
|
+
* @param options See {@link IHandleIncomingRedirectOptions}.
|
|
325
295
|
*/
|
|
326
296
|
handleIncomingRedirect = async (
|
|
327
297
|
inputOptions: string | IHandleIncomingRedirectOptions = {},
|
|
@@ -384,68 +354,6 @@ export class Session extends EventEmitter implements IHasSessionEventListener {
|
|
|
384
354
|
return sessionInfo;
|
|
385
355
|
};
|
|
386
356
|
|
|
387
|
-
/**
|
|
388
|
-
* Register a callback function to be called when a user completes login.
|
|
389
|
-
*
|
|
390
|
-
* The callback is called when {@link handleIncomingRedirect} completes successfully.
|
|
391
|
-
*
|
|
392
|
-
* @param callback The function called when a user completes login.
|
|
393
|
-
* @deprecated Prefer session.events.on(EVENTS.LOGIN, callback)
|
|
394
|
-
*/
|
|
395
|
-
onLogin(callback: () => unknown): void {
|
|
396
|
-
this.events.on(EVENTS.LOGIN, callback);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Register a callback function to be called when a user logs out:
|
|
401
|
-
*
|
|
402
|
-
* @param callback The function called when a user completes logout.
|
|
403
|
-
* @deprecated Prefer session.events.on(EVENTS.LOGOUT, callback)
|
|
404
|
-
*/
|
|
405
|
-
onLogout(callback: () => unknown): void {
|
|
406
|
-
this.events.on(EVENTS.LOGOUT, callback);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Register a callback function to be called when a user logs out:
|
|
411
|
-
*
|
|
412
|
-
* @param callback The function called when an error occurs.
|
|
413
|
-
* @since 1.11.0
|
|
414
|
-
* @deprecated Prefer session.events.on(EVENTS.ERROR, callback)
|
|
415
|
-
*/
|
|
416
|
-
onError(
|
|
417
|
-
callback: (
|
|
418
|
-
error: string | null,
|
|
419
|
-
errorDescription?: string | null,
|
|
420
|
-
) => unknown,
|
|
421
|
-
): void {
|
|
422
|
-
this.events.on(EVENTS.ERROR, callback);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Register a callback function to be called when a session is restored.
|
|
427
|
-
*
|
|
428
|
-
* Note: the callback will be called with the saved value of the 'current URL'
|
|
429
|
-
* at the time the session was restored.
|
|
430
|
-
*
|
|
431
|
-
* @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.
|
|
432
|
-
* @deprecated Prefer session.events.on(EVENTS.SESSION_RESTORED, callback)
|
|
433
|
-
*/
|
|
434
|
-
onSessionRestore(callback: (currentUrl: string) => unknown): void {
|
|
435
|
-
this.events.on(EVENTS.SESSION_RESTORED, callback);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Register a callback that runs when the session expires and can no longer
|
|
440
|
-
* make authenticated requests, but following a user logout.
|
|
441
|
-
* @param callback The function that runs on session expiration.
|
|
442
|
-
* @since 1.11.0
|
|
443
|
-
* @deprecated Prefer session.events.on(EVENTS.SESSION_EXPIRED, callback)
|
|
444
|
-
*/
|
|
445
|
-
onSessionExpiration(callback: () => unknown): void {
|
|
446
|
-
this.events.on(EVENTS.SESSION_EXPIRED, callback);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
357
|
private setSessionInfo(
|
|
450
358
|
sessionInfo: ISessionInfo & { isLoggedIn: true },
|
|
451
359
|
): void {
|
|
@@ -27,9 +27,6 @@ import {
|
|
|
27
27
|
handleIncomingRedirect,
|
|
28
28
|
login,
|
|
29
29
|
logout,
|
|
30
|
-
onLogin,
|
|
31
|
-
onSessionRestore,
|
|
32
|
-
onLogout,
|
|
33
30
|
events,
|
|
34
31
|
} from "./defaultSession";
|
|
35
32
|
import * as SessionModule from "./Session";
|
|
@@ -72,9 +69,6 @@ it("all functions pass on their arguments to the default session", () => {
|
|
|
72
69
|
const handleIncomingRedirectSpy = jest.fn();
|
|
73
70
|
defaultSession.handleIncomingRedirect =
|
|
74
71
|
handleIncomingRedirectSpy as typeof defaultSession.handleIncomingRedirect;
|
|
75
|
-
const onLoginSpy = jest.spyOn(defaultSession, "onLogin");
|
|
76
|
-
const onLogoutSpy = jest.spyOn(defaultSession, "onLogout");
|
|
77
|
-
const onSessionRestoreSpy = jest.spyOn(defaultSession, "onSessionRestore");
|
|
78
72
|
const eventsOnSpy = jest.spyOn(defaultSession.events, "on");
|
|
79
73
|
|
|
80
74
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
@@ -102,16 +96,4 @@ it("all functions pass on their arguments to the default session", () => {
|
|
|
102
96
|
expect(eventsOnSpy).not.toHaveBeenCalled();
|
|
103
97
|
events().on(EVENTS.LOGIN, jest.fn());
|
|
104
98
|
expect(eventsOnSpy).toHaveBeenCalledTimes(1);
|
|
105
|
-
|
|
106
|
-
expect(onLoginSpy).not.toHaveBeenCalled();
|
|
107
|
-
onLogin(jest.fn());
|
|
108
|
-
expect(onLoginSpy).toHaveBeenCalledTimes(1);
|
|
109
|
-
|
|
110
|
-
expect(onSessionRestoreSpy).not.toHaveBeenCalled();
|
|
111
|
-
onSessionRestore(jest.fn());
|
|
112
|
-
expect(onSessionRestoreSpy).toHaveBeenCalledTimes(1);
|
|
113
|
-
|
|
114
|
-
expect(onLogoutSpy).not.toHaveBeenCalled();
|
|
115
|
-
onLogout(jest.fn());
|
|
116
|
-
expect(onLogoutSpy).toHaveBeenCalledTimes(1);
|
|
117
99
|
});
|
package/src/defaultSession.ts
CHANGED
|
@@ -93,46 +93,6 @@ export const handleIncomingRedirect: Session["handleIncomingRedirect"] = (
|
|
|
93
93
|
return session.handleIncomingRedirect(...args);
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
-
/**
|
|
97
|
-
* Register a callback function to be called when a user completes login.
|
|
98
|
-
*
|
|
99
|
-
* The callback is called when {@link handleIncomingRedirect} completes successfully.
|
|
100
|
-
* @since 1.3.0
|
|
101
|
-
*
|
|
102
|
-
* @param callback The function called when a user completes login.
|
|
103
|
-
* @deprecated Prefer events.on(EVENTS.LOGIN, callback)
|
|
104
|
-
|
|
105
|
-
*/
|
|
106
|
-
export const onLogin: Session["onLogin"] = (...args) => {
|
|
107
|
-
const session = getDefaultSession();
|
|
108
|
-
return session.onLogin(...args);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Register a callback function to be called when a user logs out:
|
|
113
|
-
*
|
|
114
|
-
* @param callback The function called when a user completes logout.
|
|
115
|
-
* @since 1.3.0
|
|
116
|
-
* @deprecated Prefer events.on(EVENTS.LOGOUT, callback)
|
|
117
|
-
*
|
|
118
|
-
*/
|
|
119
|
-
export const onLogout: Session["onLogout"] = (...args) => {
|
|
120
|
-
const session = getDefaultSession();
|
|
121
|
-
return session.onLogout(...args);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Register a callback function to be called when a session is restored:
|
|
126
|
-
*
|
|
127
|
-
* @param callback The function called when a session is restored.
|
|
128
|
-
* @since 1.3.0
|
|
129
|
-
* @deprecated Prefer events.on(EVENTS.SESSION_RESTORED, callback)
|
|
130
|
-
*/
|
|
131
|
-
export const onSessionRestore: Session["onSessionRestore"] = (...args) => {
|
|
132
|
-
const session = getDefaultSession();
|
|
133
|
-
return session.onSessionRestore(...args);
|
|
134
|
-
};
|
|
135
|
-
|
|
136
96
|
/**
|
|
137
97
|
* {@link SessionEventEmitter} instance to subscribe to events by the default session.
|
|
138
98
|
*
|
package/src/dependencies.ts
CHANGED
|
@@ -48,9 +48,8 @@ import { ErrorOidcHandler } from "./login/oidc/incomingRedirectHandler/ErrorOidc
|
|
|
48
48
|
import TokenRefresher from "./login/oidc/refresh/TokenRefresher";
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
52
51
|
* @param dependencies
|
|
53
|
-
* @
|
|
52
|
+
* @hidden
|
|
54
53
|
*/
|
|
55
54
|
export function getClientAuthenticationWithDependencies(dependencies: {
|
|
56
55
|
secureStorage?: IStorage;
|
package/src/index.spec.ts
CHANGED
|
@@ -21,9 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
import { it, expect } from "@jest/globals";
|
|
23
23
|
|
|
24
|
-
import { Session
|
|
24
|
+
import { Session } from "./index";
|
|
25
25
|
|
|
26
26
|
it("exports the public API from the entrypoint", () => {
|
|
27
27
|
expect(Session).toBeDefined();
|
|
28
|
-
expect(getClientAuthenticationWithDependencies).toBeDefined();
|
|
29
28
|
});
|
package/src/index.ts
CHANGED
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
20
|
//
|
|
21
21
|
|
|
22
|
-
export {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
export {
|
|
23
|
+
Session,
|
|
24
|
+
ISessionOptions,
|
|
25
|
+
IHandleIncomingRedirectOptions,
|
|
26
|
+
} from "./Session";
|
|
25
27
|
|
|
26
28
|
export * from "./defaultSession";
|
|
27
29
|
|
|
@@ -34,5 +36,9 @@ export {
|
|
|
34
36
|
NotImplementedError,
|
|
35
37
|
ConfigurationError,
|
|
36
38
|
InMemoryStorage,
|
|
39
|
+
IHasSessionEventListener,
|
|
40
|
+
ISessionEventListener,
|
|
41
|
+
IEndSessionOptions,
|
|
42
|
+
ILogoutOptions,
|
|
37
43
|
EVENTS,
|
|
38
44
|
} from "@inrupt/solid-client-authn-core";
|