@inrupt/solid-client-authn-browser 3.1.1 → 4.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 +4 -4
- package/rollup.config.mjs +0 -2
- package/src/ClientAuthentication.spec.ts +128 -2
- package/src/ClientAuthentication.ts +18 -4
- package/src/Session.spec.ts +69 -18
- 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 +0 -1
- 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 +1 -1
- 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 +1 -1
- 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": "4.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"browser": "dist/index.js",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"build-docs-preview-site": "npm run build-api-docs; cd docs/api; make html"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@types/node": "^
|
|
29
|
+
"@types/node": "^25.5.0",
|
|
30
30
|
"@types/uuid": "^10.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@inrupt/oidc-client-ext": "^
|
|
34
|
-
"@inrupt/solid-client-authn-core": "^
|
|
33
|
+
"@inrupt/oidc-client-ext": "^4.0.0",
|
|
34
|
+
"@inrupt/solid-client-authn-core": "^4.0.0",
|
|
35
35
|
"events": "^3.3.0",
|
|
36
36
|
"jose": "^5.1.3",
|
|
37
37
|
"uuid": "^11.1.0"
|
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
|
|
@@ -35,6 +34,7 @@ import {
|
|
|
35
34
|
mockIncomingRedirectHandler,
|
|
36
35
|
mockHandleIncomingRedirect,
|
|
37
36
|
mockLogoutHandler,
|
|
37
|
+
// eslint-disable-next-line import/no-unresolved
|
|
38
38
|
} from "@inrupt/solid-client-authn-core/mocks";
|
|
39
39
|
|
|
40
40
|
import { mockLoginHandler } from "./login/__mocks__/LoginHandler";
|
|
@@ -472,7 +472,7 @@ describe("ClientAuthentication", () => {
|
|
|
472
472
|
const url =
|
|
473
473
|
"https://coolapp.com/redirect?state=someState&code=someAuthCode&iss=someIssuer";
|
|
474
474
|
await clientAuthn.handleIncomingRedirect(url, mockEmitter);
|
|
475
|
-
|
|
475
|
+
|
|
476
476
|
expect(history.replaceState).toHaveBeenCalledWith(
|
|
477
477
|
null,
|
|
478
478
|
"",
|
|
@@ -601,4 +601,130 @@ describe("ClientAuthentication", () => {
|
|
|
601
601
|
);
|
|
602
602
|
});
|
|
603
603
|
});
|
|
604
|
+
|
|
605
|
+
describe("validateCurrentSession", () => {
|
|
606
|
+
it("returns clientExpiresAt when expiresAt is in storage", async () => {
|
|
607
|
+
const sessionId = "mySession";
|
|
608
|
+
const expiresAt = Math.floor(Date.now() / 1000) + 10000;
|
|
609
|
+
const mockedStorage = new StorageUtility(
|
|
610
|
+
mockStorage({
|
|
611
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
612
|
+
isLoggedIn: "true",
|
|
613
|
+
webId: "https://my.pod/profile#me",
|
|
614
|
+
},
|
|
615
|
+
}),
|
|
616
|
+
mockStorage({
|
|
617
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
618
|
+
clientId: "https://some.app/registration",
|
|
619
|
+
clientSecret: "some-secret",
|
|
620
|
+
issuer: "https://some.issuer",
|
|
621
|
+
expiresAt: String(expiresAt),
|
|
622
|
+
},
|
|
623
|
+
}),
|
|
624
|
+
);
|
|
625
|
+
const clientAuthn = getClientAuthentication({
|
|
626
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const result = await clientAuthn.validateCurrentSession(sessionId);
|
|
630
|
+
expect(result).toStrictEqual(
|
|
631
|
+
expect.objectContaining({
|
|
632
|
+
clientExpiresAt: expiresAt,
|
|
633
|
+
}),
|
|
634
|
+
);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it("returns null when client has expired", async () => {
|
|
638
|
+
const sessionId = "mySession";
|
|
639
|
+
const expiredAt = Math.floor(Date.now() / 1000) - 1000;
|
|
640
|
+
const mockedStorage = new StorageUtility(
|
|
641
|
+
mockStorage({
|
|
642
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
643
|
+
isLoggedIn: "true",
|
|
644
|
+
webId: "https://my.pod/profile#me",
|
|
645
|
+
},
|
|
646
|
+
}),
|
|
647
|
+
mockStorage({
|
|
648
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
649
|
+
clientId: "https://some.app/registration",
|
|
650
|
+
clientSecret: "some-secret",
|
|
651
|
+
issuer: "https://some.issuer",
|
|
652
|
+
expiresAt: String(expiredAt),
|
|
653
|
+
},
|
|
654
|
+
}),
|
|
655
|
+
);
|
|
656
|
+
const clientAuthn = getClientAuthentication({
|
|
657
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
await expect(
|
|
661
|
+
clientAuthn.validateCurrentSession(sessionId),
|
|
662
|
+
).resolves.toBeNull();
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("returns session when client has not expired", async () => {
|
|
666
|
+
const sessionId = "mySession";
|
|
667
|
+
const futureExpiry = Math.floor(Date.now() / 1000) + 10000;
|
|
668
|
+
const mockedStorage = new StorageUtility(
|
|
669
|
+
mockStorage({
|
|
670
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
671
|
+
isLoggedIn: "true",
|
|
672
|
+
webId: "https://my.pod/profile#me",
|
|
673
|
+
},
|
|
674
|
+
}),
|
|
675
|
+
mockStorage({
|
|
676
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
677
|
+
clientId: "https://some.app/registration",
|
|
678
|
+
clientSecret: "some-secret",
|
|
679
|
+
issuer: "https://some.issuer",
|
|
680
|
+
expiresAt: String(futureExpiry),
|
|
681
|
+
},
|
|
682
|
+
}),
|
|
683
|
+
);
|
|
684
|
+
const clientAuthn = getClientAuthentication({
|
|
685
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
const result = await clientAuthn.validateCurrentSession(sessionId);
|
|
689
|
+
expect(result).not.toBeNull();
|
|
690
|
+
expect(result).toStrictEqual(
|
|
691
|
+
expect.objectContaining({
|
|
692
|
+
clientAppId: "https://some.app/registration",
|
|
693
|
+
issuer: "https://some.issuer",
|
|
694
|
+
}),
|
|
695
|
+
);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it("returns session when clientExpiresAt is 0 (never expires per OIDC DCR spec)", async () => {
|
|
699
|
+
const sessionId = "mySession";
|
|
700
|
+
const mockedStorage = new StorageUtility(
|
|
701
|
+
mockStorage({
|
|
702
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
703
|
+
isLoggedIn: "true",
|
|
704
|
+
webId: "https://my.pod/profile#me",
|
|
705
|
+
},
|
|
706
|
+
}),
|
|
707
|
+
mockStorage({
|
|
708
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
709
|
+
clientId: "https://some.app/registration",
|
|
710
|
+
clientSecret: "some-secret",
|
|
711
|
+
issuer: "https://some.issuer",
|
|
712
|
+
expiresAt: "0",
|
|
713
|
+
},
|
|
714
|
+
}),
|
|
715
|
+
);
|
|
716
|
+
const clientAuthn = getClientAuthentication({
|
|
717
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
const result = await clientAuthn.validateCurrentSession(sessionId);
|
|
721
|
+
expect(result).not.toBeNull();
|
|
722
|
+
expect(result).toStrictEqual(
|
|
723
|
+
expect.objectContaining({
|
|
724
|
+
clientAppId: "https://some.app/registration",
|
|
725
|
+
clientExpiresAt: 0,
|
|
726
|
+
}),
|
|
727
|
+
);
|
|
728
|
+
});
|
|
729
|
+
});
|
|
604
730
|
});
|
|
@@ -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";
|
|
@@ -57,9 +57,9 @@ const mockLocation = (mockedLocation: string) => {
|
|
|
57
57
|
|
|
58
58
|
// (window as any) is used to override the window type definition and
|
|
59
59
|
// allow location to be written.
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
delete (window as any).location;
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
delete (window as any).history.replaceState;
|
|
64
64
|
|
|
65
65
|
// Set our window's location to our test value.
|
|
@@ -119,11 +119,10 @@ describe("Session", () => {
|
|
|
119
119
|
// Let's make TypeScript and eslint angry! We'll set our window mock to
|
|
120
120
|
// undefined so that any references to its properties or methods explode.
|
|
121
121
|
// @ts-ignore-start
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
window = undefined;
|
|
124
124
|
// @ts-ignore-end
|
|
125
125
|
expect(() => {
|
|
126
|
-
// eslint-disable-next-line no-new
|
|
127
126
|
new Session({});
|
|
128
127
|
}).not.toThrow();
|
|
129
128
|
});
|
|
@@ -136,7 +135,7 @@ describe("Session", () => {
|
|
|
136
135
|
const mySession = new Session({ clientAuthentication });
|
|
137
136
|
// login never resolves if there are no errors,
|
|
138
137
|
// because a login redirects the user away from the page:
|
|
139
|
-
|
|
138
|
+
|
|
140
139
|
void mySession.login({});
|
|
141
140
|
expect(clientAuthnLogin).toHaveBeenCalled();
|
|
142
141
|
});
|
|
@@ -147,7 +146,7 @@ describe("Session", () => {
|
|
|
147
146
|
const mySession = new Session({ clientAuthentication });
|
|
148
147
|
// login never resolves if there are no errors,
|
|
149
148
|
// because a login redirects the user away from the page:
|
|
150
|
-
|
|
149
|
+
|
|
151
150
|
void mySession.login({
|
|
152
151
|
tokenType: "Bearer",
|
|
153
152
|
});
|
|
@@ -168,7 +167,7 @@ describe("Session", () => {
|
|
|
168
167
|
};
|
|
169
168
|
// login never resolves if there are no errors,
|
|
170
169
|
// because a login redirects the user away from the page:
|
|
171
|
-
|
|
170
|
+
|
|
172
171
|
void objectWithLogin.login({});
|
|
173
172
|
expect(clientAuthnLogin).toHaveBeenCalled();
|
|
174
173
|
});
|
|
@@ -380,6 +379,7 @@ describe("Session", () => {
|
|
|
380
379
|
"https://my.app/?code=someCode&state=arizona",
|
|
381
380
|
);
|
|
382
381
|
// We know that it has been set by the call to `blockingRequest`.
|
|
382
|
+
// FIXME: This can be fixed when we drop support for Node 20 by using Promise.withResolvers.
|
|
383
383
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
384
384
|
continueAfterSecondRequest!();
|
|
385
385
|
const tokenRequests = await Promise.all([
|
|
@@ -425,7 +425,7 @@ describe("Session", () => {
|
|
|
425
425
|
incomingRedirectPromise,
|
|
426
426
|
) as typeof clientAuthentication.handleIncomingRedirect;
|
|
427
427
|
const mySession = new Session({ clientAuthentication });
|
|
428
|
-
|
|
428
|
+
|
|
429
429
|
void mySession.handleIncomingRedirect({
|
|
430
430
|
url: "https://some.redirect/url",
|
|
431
431
|
restorePreviousSession: true,
|
|
@@ -458,9 +458,12 @@ describe("Session", () => {
|
|
|
458
458
|
.mockReturnValueOnce(
|
|
459
459
|
incomingRedirectPromise,
|
|
460
460
|
) as typeof clientAuthentication.handleIncomingRedirect;
|
|
461
|
-
const validateCurrentSessionPromise = Promise.resolve(
|
|
462
|
-
"https://some.issuer/",
|
|
463
|
-
|
|
461
|
+
const validateCurrentSessionPromise = Promise.resolve({
|
|
462
|
+
issuer: "https://some.issuer/",
|
|
463
|
+
clientAppId: "some client ID",
|
|
464
|
+
redirectUrl: "https://some.redirect/url",
|
|
465
|
+
tokenType: "DPoP",
|
|
466
|
+
});
|
|
464
467
|
clientAuthentication.validateCurrentSession = jest
|
|
465
468
|
.fn()
|
|
466
469
|
.mockReturnValue(
|
|
@@ -468,7 +471,7 @@ describe("Session", () => {
|
|
|
468
471
|
) as typeof clientAuthentication.validateCurrentSession;
|
|
469
472
|
|
|
470
473
|
const mySession = new Session({ clientAuthentication });
|
|
471
|
-
|
|
474
|
+
|
|
472
475
|
void mySession.handleIncomingRedirect({
|
|
473
476
|
url: "https://some.redirect/url",
|
|
474
477
|
restorePreviousSession: true,
|
|
@@ -507,7 +510,7 @@ describe("Session", () => {
|
|
|
507
510
|
undefined,
|
|
508
511
|
) as typeof clientAuthentication.handleIncomingRedirect;
|
|
509
512
|
const mySession = new Session({ clientAuthentication });
|
|
510
|
-
|
|
513
|
+
|
|
511
514
|
void mySession.handleIncomingRedirect({
|
|
512
515
|
url: "https://some.redirect/url",
|
|
513
516
|
restorePreviousSession: true,
|
|
@@ -536,6 +539,7 @@ describe("Session", () => {
|
|
|
536
539
|
issuer: "https://some.issuer",
|
|
537
540
|
clientAppId: "some client ID",
|
|
538
541
|
clientAppSecret: "some client secret",
|
|
542
|
+
clientExpiresAt: Math.floor(Date.now() / 1000) + 10000,
|
|
539
543
|
redirectUrl: "https://some.redirect/url",
|
|
540
544
|
tokenType: "DPoP",
|
|
541
545
|
});
|
|
@@ -553,7 +557,7 @@ describe("Session", () => {
|
|
|
553
557
|
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
554
558
|
|
|
555
559
|
const mySession = new Session({ clientAuthentication });
|
|
556
|
-
|
|
560
|
+
|
|
557
561
|
void mySession.handleIncomingRedirect({
|
|
558
562
|
url: "https://some.redirect/url",
|
|
559
563
|
restorePreviousSession: true,
|
|
@@ -652,7 +656,7 @@ describe("Session", () => {
|
|
|
652
656
|
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
653
657
|
|
|
654
658
|
const mySession = new Session({ clientAuthentication });
|
|
655
|
-
|
|
659
|
+
|
|
656
660
|
void mySession.handleIncomingRedirect("https://some.redirect/url");
|
|
657
661
|
expect(window.localStorage.getItem(KEY_CURRENT_URL)).toBeNull();
|
|
658
662
|
expect(clientAuthentication.login).not.toHaveBeenCalled();
|
|
@@ -689,7 +693,7 @@ describe("Session", () => {
|
|
|
689
693
|
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
690
694
|
|
|
691
695
|
const mySession = new Session({ clientAuthentication });
|
|
692
|
-
|
|
696
|
+
|
|
693
697
|
void mySession.handleIncomingRedirect({
|
|
694
698
|
url: "https://some.redirect/url",
|
|
695
699
|
restorePreviousSession: false,
|
|
@@ -728,7 +732,7 @@ describe("Session", () => {
|
|
|
728
732
|
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
729
733
|
|
|
730
734
|
const mySession = new Session({ clientAuthentication });
|
|
731
|
-
|
|
735
|
+
|
|
732
736
|
void mySession.handleIncomingRedirect("https://some.redirect/url");
|
|
733
737
|
expect(mySession.info.isLoggedIn).toBe(false);
|
|
734
738
|
// The local storage should have been cleared by the auth error
|
|
@@ -761,6 +765,53 @@ describe("Session", () => {
|
|
|
761
765
|
// The local storage should have been cleared by the auth error
|
|
762
766
|
expect(window.localStorage.getItem(KEY_CURRENT_SESSION)).toBeNull();
|
|
763
767
|
});
|
|
768
|
+
|
|
769
|
+
it("does not attempt silent authentication if the stored client has expired", async () => {
|
|
770
|
+
const sessionId = "mySession";
|
|
771
|
+
mockLocalStorage({
|
|
772
|
+
[KEY_CURRENT_SESSION]: sessionId,
|
|
773
|
+
});
|
|
774
|
+
mockLocation("https://mock.current/location");
|
|
775
|
+
|
|
776
|
+
// Set up storage with an expired client (expiresAt in the past)
|
|
777
|
+
const expiredAt = Math.floor(Date.now() / 1000) - 1000;
|
|
778
|
+
const mockedStorage = new StorageUtility(
|
|
779
|
+
mockStorage({
|
|
780
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
781
|
+
isLoggedIn: "true",
|
|
782
|
+
webId: "https://my.pod/profile#me",
|
|
783
|
+
},
|
|
784
|
+
}),
|
|
785
|
+
mockStorage({
|
|
786
|
+
[`${USER_SESSION_PREFIX}:${sessionId}`]: {
|
|
787
|
+
clientId: "https://some.app/registration",
|
|
788
|
+
clientSecret: "some-secret",
|
|
789
|
+
issuer: "https://some.issuer",
|
|
790
|
+
expiresAt: String(expiredAt),
|
|
791
|
+
},
|
|
792
|
+
}),
|
|
793
|
+
);
|
|
794
|
+
const clientAuthentication = mockClientAuthentication({
|
|
795
|
+
sessionInfoManager: mockSessionInfoManager(mockedStorage),
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// Mock handleIncomingRedirect to return undefined (no OAuth params in URL)
|
|
799
|
+
clientAuthentication.handleIncomingRedirect = jest
|
|
800
|
+
.fn<typeof clientAuthentication.handleIncomingRedirect>()
|
|
801
|
+
.mockResolvedValue(undefined);
|
|
802
|
+
clientAuthentication.login = jest.fn<typeof clientAuthentication.login>();
|
|
803
|
+
|
|
804
|
+
const mySession = new Session({ clientAuthentication });
|
|
805
|
+
const result = await mySession.handleIncomingRedirect({
|
|
806
|
+
url: "https://some.redirect/url",
|
|
807
|
+
restorePreviousSession: true,
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// Silent auth should NOT have been attempted because client is expired
|
|
811
|
+
expect(clientAuthentication.login).not.toHaveBeenCalled();
|
|
812
|
+
// The function should resolve (not hang)
|
|
813
|
+
expect(result).toBeUndefined();
|
|
814
|
+
});
|
|
764
815
|
});
|
|
765
816
|
|
|
766
817
|
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
package/src/index.browser.ts
CHANGED
package/src/index.spec.ts
CHANGED
package/src/index.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
|
|
@@ -29,7 +28,7 @@ jest.mock("@inrupt/solid-client-authn-core");
|
|
|
29
28
|
describe("AggregateOidcHandler", () => {
|
|
30
29
|
it("should pass injected handlers to its superclass", () => {
|
|
31
30
|
// We just test if the parent is called.
|
|
32
|
-
|
|
31
|
+
|
|
33
32
|
new AggregateOidcHandler(["Some handler"] as unknown as IOidcHandler[]);
|
|
34
33
|
|
|
35
34
|
expect((AggregateHandler as jest.Mock).mock.calls).toEqual([
|
|
@@ -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
|
|
@@ -22,6 +21,7 @@
|
|
|
22
21
|
import {
|
|
23
22
|
StorageUtilityMock,
|
|
24
23
|
mockStorageUtility,
|
|
24
|
+
// eslint-disable-next-line import/no-unresolved
|
|
25
25
|
} from "@inrupt/solid-client-authn-core/mocks";
|
|
26
26
|
import { jest, it, describe, expect } from "@jest/globals";
|
|
27
27
|
import { randomUUID } from "crypto";
|
|
@@ -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,9 @@ import { jest, it, describe, expect } from "@jest/globals";
|
|
|
23
22
|
import { mockStorageUtility } from "@inrupt/solid-client-authn-core";
|
|
24
23
|
import IssuerConfigFetcher from "./IssuerConfigFetcher";
|
|
25
24
|
|
|
25
|
+
// Camelcase identifiers are required in the OIDC specification.
|
|
26
|
+
/* eslint-disable camelcase*/
|
|
27
|
+
|
|
26
28
|
/**
|
|
27
29
|
* Test for IssuerConfigFetcher
|
|
28
30
|
*/
|
|
@@ -58,7 +60,7 @@ describe("IssuerConfigFetcher", () => {
|
|
|
58
60
|
new Response(
|
|
59
61
|
JSON.stringify({
|
|
60
62
|
issuer: "https://example.com",
|
|
61
|
-
|
|
63
|
+
|
|
62
64
|
claim_types_supported: "oidc",
|
|
63
65
|
bleepBloop: "Meep Moop",
|
|
64
66
|
end_session_endpoint: "https://example.com/endSessionEndpoint",
|
|
@@ -92,7 +94,7 @@ describe("IssuerConfigFetcher", () => {
|
|
|
92
94
|
new Response(
|
|
93
95
|
JSON.stringify({
|
|
94
96
|
issuer: "https://example.com",
|
|
95
|
-
|
|
97
|
+
|
|
96
98
|
claim_types_supported: "oidc",
|
|
97
99
|
scopes_supported: ["openid", "offline_access", "webid"],
|
|
98
100
|
}),
|
|
@@ -114,7 +116,7 @@ describe("IssuerConfigFetcher", () => {
|
|
|
114
116
|
new Response(
|
|
115
117
|
JSON.stringify({
|
|
116
118
|
issuer: "https://example.com",
|
|
117
|
-
|
|
119
|
+
|
|
118
120
|
claim_types_supported: "oidc",
|
|
119
121
|
}),
|
|
120
122
|
),
|
|
@@ -136,7 +138,7 @@ describe("IssuerConfigFetcher", () => {
|
|
|
136
138
|
new Response(
|
|
137
139
|
JSON.stringify({
|
|
138
140
|
issuer: "https://example.com",
|
|
139
|
-
|
|
141
|
+
|
|
140
142
|
claim_types_supported: "oidc",
|
|
141
143
|
scopes_supported: ["openid", "offline_access", "webid"],
|
|
142
144
|
}),
|
|
@@ -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
|
|
@@ -34,9 +33,11 @@ import type {
|
|
|
34
33
|
} from "@inrupt/solid-client-authn-core";
|
|
35
34
|
import { ConfigurationError } from "@inrupt/solid-client-authn-core";
|
|
36
35
|
|
|
36
|
+
// Camelcase identifiers are required in the OIDC specification.
|
|
37
|
+
/* eslint-disable camelcase*/
|
|
38
|
+
|
|
37
39
|
export const WELL_KNOWN_OPENID_CONFIG = ".well-known/openid-configuration";
|
|
38
40
|
|
|
39
|
-
/* eslint-disable camelcase */
|
|
40
41
|
const issuerConfigKeyMap: Record<
|
|
41
42
|
string,
|
|
42
43
|
{ toKey: string; convertToUrl?: boolean }
|
|
@@ -127,7 +128,6 @@ const issuerConfigKeyMap: Record<
|
|
|
127
128
|
convertToUrl: true,
|
|
128
129
|
},
|
|
129
130
|
};
|
|
130
|
-
/* eslint-enable camelcase */
|
|
131
131
|
|
|
132
132
|
function processConfig(
|
|
133
133
|
config: Record<string, string | string[]>,
|