@inrupt/solid-client-authn-browser 1.11.7 → 1.11.8-alpha.2
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 +18 -17
- package/dist/Session.d.ts +35 -36
- package/dist/Session.js +2 -45
- package/dist/Session.js.map +1 -1
- package/dist/constant.d.ts +2 -2
- package/dist/defaultSession.d.ts +9 -9
- package/dist/dependencies.d.ts +6 -6
- package/dist/iframe.d.ts +3 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.es.js +1693 -0
- package/dist/index.js +1717 -27
- package/dist/login/AggregateLoginHandler.d.ts +4 -4
- package/dist/login/oidc/AggregateOidcHandler.d.ts +4 -4
- package/dist/login/oidc/ClientRegistrar.d.ts +6 -6
- package/dist/login/oidc/IssuerConfigFetcher.d.ts +8 -8
- package/dist/login/oidc/OidcLoginHandler.d.ts +10 -10
- package/dist/login/oidc/Redirector.d.ts +4 -4
- package/dist/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.d.ts +8 -8
- package/dist/login/oidc/oidcHandlers/OidcHandlerCanHandleTest.d.ts +9 -9
- package/dist/login/oidc/redirectHandler/AggregateRedirectHandler.d.ts +4 -4
- package/dist/login/oidc/redirectHandler/AuthCodeRedirectHandler.d.ts +15 -16
- package/dist/login/oidc/redirectHandler/AuthCodeRedirectHandler.js +1 -23
- package/dist/login/oidc/redirectHandler/AuthCodeRedirectHandler.js.map +1 -1
- package/dist/login/oidc/redirectHandler/ErrorOidcHandler.d.ts +9 -9
- package/dist/login/oidc/redirectHandler/FallbackRedirectHandler.d.ts +7 -7
- package/dist/login/oidc/refresh/TokenRefresher.d.ts +10 -10
- package/dist/login/webid/WebIdLoginHandler.d.ts +5 -5
- package/dist/logout/GeneralLogoutHandler.d.ts +7 -7
- package/dist/sessionInfo/SessionInfoManager.d.ts +16 -16
- package/dist/sessionInfo/SessionInfoManager.js +0 -8
- package/dist/sessionInfo/SessionInfoManager.js.map +1 -1
- package/dist/solid-client-authn.bundle.js +1720 -2
- package/dist/storage/BrowserStorage.d.ts +7 -7
- package/dist/storage/StorageUtility.d.ts +4 -4
- package/dist/util/UuidGenerator.d.ts +6 -6
- package/dist/util/urlPath.d.ts +1 -1
- package/examples/demoClientApp/package-lock.json +85 -18
- package/examples/demoClientApp/package.json +2 -1
- package/examples/demoClientApp/src/DemoClientApp.js +9 -1
- package/examples/single/bundle/package-lock.json +18 -18
- package/jest.config.js +6 -1
- package/package.json +17 -29
- package/rollup.config.js +57 -0
- package/src/ClientAuthentication.spec.ts +69 -1
- package/src/ClientAuthentication.ts +32 -14
- package/src/Session.spec.ts +0 -224
- package/src/Session.ts +8 -88
- package/src/login/oidc/__mocks__/Redirector.ts +9 -9
- package/src/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.spec.ts +34 -18
- package/src/login/oidc/redirectHandler/AuthCodeRedirectHandler.spec.ts +3 -171
- package/src/login/oidc/redirectHandler/AuthCodeRedirectHandler.ts +0 -68
- package/src/login/oidc/redirectHandler/__mocks__/RedirectHandler.ts +13 -0
- package/src/sessionInfo/SessionInfoManager.spec.ts +0 -27
- package/src/sessionInfo/SessionInfoManager.ts +0 -10
- package/src/index.browser.spec.ts +0 -27
- package/src/index.browser.ts +0 -24
- package/webpack.browser.js +0 -19
- package/webpack.common.js +0 -22
- package/webpack.config.js +0 -8
- package/webpack.dev.js +0 -17
- package/webpack.prod.js +0 -3
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
IIssuerConfigFetcher,
|
|
34
34
|
ISessionInternalInfo,
|
|
35
35
|
ILoginOptions,
|
|
36
|
+
EVENTS,
|
|
36
37
|
} from "@inrupt/solid-client-authn-core";
|
|
37
38
|
import { removeOidcQueryParam } from "@inrupt/oidc-client-ext";
|
|
38
39
|
import { EventEmitter } from "events";
|
|
@@ -129,13 +130,37 @@ export default class ClientAuthentication {
|
|
|
129
130
|
url: string,
|
|
130
131
|
eventEmitter: EventEmitter
|
|
131
132
|
): Promise<ISessionInfo | undefined> => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
try {
|
|
134
|
+
const redirectInfo = await this.redirectHandler.handle(url, eventEmitter);
|
|
135
|
+
// The `FallbackRedirectHandler` directly returns the global `fetch` for
|
|
136
|
+
// his value, so we should ensure it's bound to `window` rather than to
|
|
137
|
+
// ClientAuthentication, to avoid the following error:
|
|
138
|
+
// > 'fetch' called on an object that does not implement interface Window.
|
|
139
|
+
this.fetch = redirectInfo.fetch.bind(window);
|
|
140
|
+
|
|
141
|
+
// Strip the oauth params:
|
|
142
|
+
this.cleanUrlAfterRedirect(url);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
isLoggedIn: redirectInfo.isLoggedIn,
|
|
146
|
+
webId: redirectInfo.webId,
|
|
147
|
+
sessionId: redirectInfo.sessionId,
|
|
148
|
+
expirationDate: redirectInfo.expirationDate,
|
|
149
|
+
};
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// Strip the oauth params:
|
|
152
|
+
this.cleanUrlAfterRedirect(url);
|
|
153
|
+
|
|
154
|
+
// FIXME: EVENTS.ERROR should be errorCode, errorDescription
|
|
155
|
+
//
|
|
156
|
+
// I'm not sure if "redirect" is a good error code, and in theory `err`
|
|
157
|
+
// maybe an Error object and not a string; Maybe we want to just hardcode
|
|
158
|
+
// a description instead?
|
|
159
|
+
eventEmitter.emit(EVENTS.ERROR, "redirect", err);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
138
162
|
|
|
163
|
+
private cleanUrlAfterRedirect(url: string): void {
|
|
139
164
|
const cleanedUpUrl = new URL(url);
|
|
140
165
|
cleanedUpUrl.searchParams.delete("state");
|
|
141
166
|
// For auth code flow
|
|
@@ -153,12 +178,5 @@ export default class ClientAuthentication {
|
|
|
153
178
|
// authentication library will be called again with what are now invalid
|
|
154
179
|
// query parameters!).
|
|
155
180
|
window.history.replaceState(null, "", cleanedUpUrl.toString());
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
isLoggedIn: redirectInfo.isLoggedIn,
|
|
159
|
-
webId: redirectInfo.webId,
|
|
160
|
-
sessionId: redirectInfo.sessionId,
|
|
161
|
-
expirationDate: redirectInfo.expirationDate,
|
|
162
|
-
};
|
|
163
|
-
};
|
|
181
|
+
}
|
|
164
182
|
}
|
package/src/Session.spec.ts
CHANGED
|
@@ -395,230 +395,6 @@ describe("Session", () => {
|
|
|
395
395
|
expect(incomingRedirectHandler).toHaveBeenCalled();
|
|
396
396
|
});
|
|
397
397
|
|
|
398
|
-
// This workaround will be removed after we've settled on an API that allows us to silently
|
|
399
|
-
// re-activate the user's session after a refresh:
|
|
400
|
-
describe("(using the temporary workaround for losing a session after refresh)", () => {
|
|
401
|
-
it("does not report the user to be logged in when the workaround cookie is not present, and just executes the redirect", async () => {
|
|
402
|
-
const clientAuthentication = mockClientAuthentication();
|
|
403
|
-
clientAuthentication.handleIncomingRedirect = jest.fn(
|
|
404
|
-
async (_url: string) => undefined
|
|
405
|
-
);
|
|
406
|
-
const mySession = new Session({ clientAuthentication });
|
|
407
|
-
await mySession.handleIncomingRedirect({
|
|
408
|
-
url: "https://some.url",
|
|
409
|
-
useEssSession: true,
|
|
410
|
-
});
|
|
411
|
-
expect(mySession.info.isLoggedIn).toBe(false);
|
|
412
|
-
expect(mySession.info.webId).toBeUndefined();
|
|
413
|
-
expect(
|
|
414
|
-
clientAuthentication.handleIncomingRedirect
|
|
415
|
-
).toHaveBeenCalledTimes(1);
|
|
416
|
-
// The workaround should be enabled by default
|
|
417
|
-
expect(
|
|
418
|
-
window.localStorage.getItem("tmp-resource-server-session-enabled")
|
|
419
|
-
).toBe("true");
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it("re-initialises the user's WebID without redirection if the proper cookie is set", async () => {
|
|
423
|
-
mockLocalStorage({
|
|
424
|
-
"tmp-resource-server-session-info": JSON.stringify({
|
|
425
|
-
webId: "https://my.pod/profile#me",
|
|
426
|
-
sessions: {
|
|
427
|
-
"https://my.pod/": { expiration: 9000000000000 },
|
|
428
|
-
},
|
|
429
|
-
}),
|
|
430
|
-
});
|
|
431
|
-
const clientAuthentication = mockClientAuthentication();
|
|
432
|
-
clientAuthentication.handleIncomingRedirect = jest.fn();
|
|
433
|
-
const mySession = new Session({ clientAuthentication });
|
|
434
|
-
await mySession.handleIncomingRedirect({
|
|
435
|
-
url: "https://some.url",
|
|
436
|
-
useEssSession: true,
|
|
437
|
-
});
|
|
438
|
-
expect(mySession.info.isLoggedIn).toBe(true);
|
|
439
|
-
expect(mySession.info.webId).toBe("https://my.pod/profile#me");
|
|
440
|
-
expect(
|
|
441
|
-
clientAuthentication.handleIncomingRedirect
|
|
442
|
-
).toHaveBeenCalledTimes(0);
|
|
443
|
-
// The workaround should be enabled by default
|
|
444
|
-
expect(
|
|
445
|
-
window.localStorage.getItem("tmp-resource-server-session-enabled")
|
|
446
|
-
).toBe("true");
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
it("does not attempt to use the workaround if the long-term solution (silent refresh) is enabled", async () => {
|
|
450
|
-
mockLocalStorage({
|
|
451
|
-
"tmp-resource-server-session-info": JSON.stringify({
|
|
452
|
-
webId: "https://my.pod/profile#me",
|
|
453
|
-
sessions: {
|
|
454
|
-
"https://my.pod/": { expiration: 9000000000000 },
|
|
455
|
-
},
|
|
456
|
-
}),
|
|
457
|
-
});
|
|
458
|
-
const clientAuthentication = mockClientAuthentication();
|
|
459
|
-
clientAuthentication.handleIncomingRedirect = jest.fn();
|
|
460
|
-
const mySession = new Session({ clientAuthentication });
|
|
461
|
-
await mySession.handleIncomingRedirect({
|
|
462
|
-
url: "https://some.url",
|
|
463
|
-
restorePreviousSession: true,
|
|
464
|
-
});
|
|
465
|
-
expect(mySession.info.isLoggedIn).toBe(false);
|
|
466
|
-
expect(mySession.info.webId).toBeUndefined();
|
|
467
|
-
expect(
|
|
468
|
-
clientAuthentication.handleIncomingRedirect
|
|
469
|
-
).toHaveBeenCalledTimes(1);
|
|
470
|
-
expect(
|
|
471
|
-
window.localStorage.getItem("tmp-resource-server-session-enabled")
|
|
472
|
-
).toBe("false");
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
it("overrides the workaround if both silent refresh and the ESS session are enabled", async () => {
|
|
476
|
-
mockLocalStorage({
|
|
477
|
-
"tmp-resource-server-session-info": JSON.stringify({
|
|
478
|
-
webId: "https://my.pod/profile#me",
|
|
479
|
-
sessions: {
|
|
480
|
-
"https://my.pod/": { expiration: 9000000000000 },
|
|
481
|
-
},
|
|
482
|
-
}),
|
|
483
|
-
});
|
|
484
|
-
const clientAuthentication = mockClientAuthentication();
|
|
485
|
-
clientAuthentication.handleIncomingRedirect = jest.fn();
|
|
486
|
-
const mySession = new Session({ clientAuthentication });
|
|
487
|
-
await mySession.handleIncomingRedirect({
|
|
488
|
-
url: "https://some.url",
|
|
489
|
-
restorePreviousSession: true,
|
|
490
|
-
useEssSession: true,
|
|
491
|
-
});
|
|
492
|
-
expect(mySession.info.isLoggedIn).toBe(false);
|
|
493
|
-
expect(mySession.info.webId).toBeUndefined();
|
|
494
|
-
expect(
|
|
495
|
-
clientAuthentication.handleIncomingRedirect
|
|
496
|
-
).toHaveBeenCalledTimes(1);
|
|
497
|
-
expect(
|
|
498
|
-
window.localStorage.getItem("tmp-resource-server-session-enabled")
|
|
499
|
-
).toBe("false");
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
it("does not attempt to use the workaround if it is not explicitly enabled", async () => {
|
|
503
|
-
mockLocalStorage({
|
|
504
|
-
"tmp-resource-server-session-info": JSON.stringify({
|
|
505
|
-
webId: "https://my.pod/profile#me",
|
|
506
|
-
sessions: {
|
|
507
|
-
"https://my.pod/": { expiration: 9000000000000 },
|
|
508
|
-
},
|
|
509
|
-
}),
|
|
510
|
-
});
|
|
511
|
-
const clientAuthentication = mockClientAuthentication();
|
|
512
|
-
clientAuthentication.handleIncomingRedirect = jest.fn();
|
|
513
|
-
const mySession = new Session({ clientAuthentication });
|
|
514
|
-
await mySession.handleIncomingRedirect({
|
|
515
|
-
url: "https://some.url",
|
|
516
|
-
});
|
|
517
|
-
expect(mySession.info.isLoggedIn).toBe(false);
|
|
518
|
-
expect(mySession.info.webId).toBeUndefined();
|
|
519
|
-
expect(
|
|
520
|
-
clientAuthentication.handleIncomingRedirect
|
|
521
|
-
).toHaveBeenCalledTimes(1);
|
|
522
|
-
expect(
|
|
523
|
-
window.localStorage.getItem("tmp-resource-server-session-enabled")
|
|
524
|
-
).toBe("false");
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
it("does not attempt to use the workaround if it is explicitly disabled", async () => {
|
|
528
|
-
mockLocalStorage({
|
|
529
|
-
"tmp-resource-server-session-info": JSON.stringify({
|
|
530
|
-
webId: "https://my.pod/profile#me",
|
|
531
|
-
sessions: {
|
|
532
|
-
"https://my.pod/": { expiration: 9000000000000 },
|
|
533
|
-
},
|
|
534
|
-
}),
|
|
535
|
-
});
|
|
536
|
-
const clientAuthentication = mockClientAuthentication();
|
|
537
|
-
clientAuthentication.handleIncomingRedirect = jest.fn();
|
|
538
|
-
const mySession = new Session({ clientAuthentication });
|
|
539
|
-
await mySession.handleIncomingRedirect({
|
|
540
|
-
url: "https://some.url",
|
|
541
|
-
useEssSession: false,
|
|
542
|
-
});
|
|
543
|
-
expect(mySession.info.isLoggedIn).toBe(false);
|
|
544
|
-
expect(mySession.info.webId).toBeUndefined();
|
|
545
|
-
expect(
|
|
546
|
-
clientAuthentication.handleIncomingRedirect
|
|
547
|
-
).toHaveBeenCalledTimes(1);
|
|
548
|
-
expect(
|
|
549
|
-
window.localStorage.getItem("tmp-resource-server-session-enabled")
|
|
550
|
-
).toBe("false");
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
it("does not mark an almost-expired session as logged in", async () => {
|
|
554
|
-
mockLocalStorage({
|
|
555
|
-
"tmp-resource-server-session-info": JSON.stringify({
|
|
556
|
-
webId: "https://my.pod/profile#me",
|
|
557
|
-
sessions: {
|
|
558
|
-
"https://my.pod/": { expiration: Date.now() + 10000 },
|
|
559
|
-
},
|
|
560
|
-
}),
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
const clientAuthentication = mockClientAuthentication();
|
|
564
|
-
clientAuthentication.handleIncomingRedirect = jest.fn();
|
|
565
|
-
const mySession = new Session({ clientAuthentication });
|
|
566
|
-
await mySession.handleIncomingRedirect({
|
|
567
|
-
url: "https://some.url",
|
|
568
|
-
useEssSession: true,
|
|
569
|
-
});
|
|
570
|
-
expect(mySession.info.isLoggedIn).toBe(false);
|
|
571
|
-
expect(mySession.info.webId).toBeUndefined();
|
|
572
|
-
expect(
|
|
573
|
-
clientAuthentication.handleIncomingRedirect
|
|
574
|
-
).toHaveBeenCalledTimes(1);
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
it("logs you in even if your Solid Identity Provider is not your Resource server", async () => {
|
|
578
|
-
mockLocalStorage({
|
|
579
|
-
"tmp-resource-server-session-info": JSON.stringify({
|
|
580
|
-
webId: "https://my.pod/profile#me",
|
|
581
|
-
sessions: {
|
|
582
|
-
"https://not.my.pod/": { expiration: 9000000000000 },
|
|
583
|
-
},
|
|
584
|
-
}),
|
|
585
|
-
});
|
|
586
|
-
const clientAuthentication = mockClientAuthentication();
|
|
587
|
-
clientAuthentication.handleIncomingRedirect = jest.fn();
|
|
588
|
-
const mySession = new Session({ clientAuthentication });
|
|
589
|
-
await mySession.handleIncomingRedirect({
|
|
590
|
-
url: "https://some.url",
|
|
591
|
-
useEssSession: true,
|
|
592
|
-
});
|
|
593
|
-
expect(mySession.info.isLoggedIn).toBe(true);
|
|
594
|
-
expect(mySession.info.webId).toBe("https://my.pod/profile#me");
|
|
595
|
-
expect(
|
|
596
|
-
clientAuthentication.handleIncomingRedirect
|
|
597
|
-
).toHaveBeenCalledTimes(0);
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
it("does not log the user in if the data in the storage was invalid", async () => {
|
|
601
|
-
mockLocalStorage({
|
|
602
|
-
"tmp-resource-server-session-info": JSON.stringify({
|
|
603
|
-
webId: "https://my.pod/profile#me",
|
|
604
|
-
// `sessions` key is intentionally missing
|
|
605
|
-
}),
|
|
606
|
-
});
|
|
607
|
-
const clientAuthentication = mockClientAuthentication();
|
|
608
|
-
clientAuthentication.handleIncomingRedirect = jest.fn();
|
|
609
|
-
const mySession = new Session({ clientAuthentication });
|
|
610
|
-
await mySession.handleIncomingRedirect({
|
|
611
|
-
url: "https://some.url",
|
|
612
|
-
useEssSession: true,
|
|
613
|
-
});
|
|
614
|
-
expect(mySession.info.isLoggedIn).toBe(false);
|
|
615
|
-
expect(mySession.info.webId).toBeUndefined();
|
|
616
|
-
expect(
|
|
617
|
-
clientAuthentication.handleIncomingRedirect
|
|
618
|
-
).toHaveBeenCalledTimes(1);
|
|
619
|
-
});
|
|
620
|
-
});
|
|
621
|
-
|
|
622
398
|
it("posts the redirect IRI to the parent if in an iframe", async () => {
|
|
623
399
|
// Pretend we are in an iframe.
|
|
624
400
|
const frameElement = jest.spyOn(window, "frameElement", "get");
|
package/src/Session.ts
CHANGED
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
ILoginInputOptions,
|
|
29
29
|
ISessionInfo,
|
|
30
30
|
IStorage,
|
|
31
|
-
ResourceServerSession,
|
|
32
31
|
} from "@inrupt/solid-client-authn-core";
|
|
33
32
|
import { v4 } from "uuid";
|
|
34
33
|
import ClientAuthentication from "./ClientAuthentication";
|
|
@@ -83,6 +82,8 @@ export interface IHandleIncomingRedirectOptions {
|
|
|
83
82
|
* `useEssSession` defaults to false and will be removed in the future; to
|
|
84
83
|
* preserve sessions across page reloads, use of `restorePreviousSession` is
|
|
85
84
|
* recommended.
|
|
85
|
+
*
|
|
86
|
+
* @deprecated unreleased
|
|
86
87
|
*/
|
|
87
88
|
useEssSession?: boolean;
|
|
88
89
|
/**
|
|
@@ -148,9 +149,6 @@ export class Session extends EventEmitter {
|
|
|
148
149
|
|
|
149
150
|
private tokenRequestInProgress = false;
|
|
150
151
|
|
|
151
|
-
// Remove this when removing the `useEssSession` workaround:
|
|
152
|
-
private tmpFetchWithCookies = false;
|
|
153
|
-
|
|
154
152
|
/**
|
|
155
153
|
* Session object constructor. Typically called as follows:
|
|
156
154
|
*
|
|
@@ -237,6 +235,11 @@ export class Session extends EventEmitter {
|
|
|
237
235
|
this.on(EVENTS.SESSION_EXPIRED, () => this.internalLogout(false));
|
|
238
236
|
|
|
239
237
|
this.on(EVENTS.ERROR, () => this.internalLogout(false));
|
|
238
|
+
|
|
239
|
+
// @deprecated: Remove this when removing all support for the useEssSession parameter.
|
|
240
|
+
// This intends at cleaning up local storage of applications from data related
|
|
241
|
+
// to the resource server session workaround.
|
|
242
|
+
window.localStorage.removeItem("tmp-resource-server-session-enabled");
|
|
240
243
|
}
|
|
241
244
|
|
|
242
245
|
/**
|
|
@@ -271,14 +274,7 @@ export class Session extends EventEmitter {
|
|
|
271
274
|
* @param init Optional parameters customizing the request, by specifying an HTTP method, headers, a body, etc. Follows the [WHATWG Fetch Standard](https://fetch.spec.whatwg.org/).
|
|
272
275
|
*/
|
|
273
276
|
fetch = async (url: RequestInfo, init?: RequestInit): Promise<Response> => {
|
|
274
|
-
return this.clientAuthentication.fetch(url,
|
|
275
|
-
...init,
|
|
276
|
-
credentials: this.tmpFetchWithCookies
|
|
277
|
-
? /* istanbul ignore next Simulating a cookie-enabled session in tests is too much work for a temporary, disabled-by-default workaround: */
|
|
278
|
-
"include"
|
|
279
|
-
: /* istanbul ignore next Simulating a cookie-enabled session in tests is too much work for a temporary, disabled-by-default workaround: */
|
|
280
|
-
init?.credentials,
|
|
281
|
-
});
|
|
277
|
+
return this.clientAuthentication.fetch(url, init);
|
|
282
278
|
};
|
|
283
279
|
|
|
284
280
|
/**
|
|
@@ -295,7 +291,6 @@ export class Session extends EventEmitter {
|
|
|
295
291
|
window.localStorage.removeItem(KEY_CURRENT_SESSION);
|
|
296
292
|
await this.clientAuthentication.logout(this.info.sessionId);
|
|
297
293
|
this.info.isLoggedIn = false;
|
|
298
|
-
this.tmpFetchWithCookies = false;
|
|
299
294
|
if (emitSignal) {
|
|
300
295
|
this.emit(EVENTS.LOGOUT);
|
|
301
296
|
}
|
|
@@ -333,81 +328,6 @@ export class Session extends EventEmitter {
|
|
|
333
328
|
return undefined;
|
|
334
329
|
}
|
|
335
330
|
|
|
336
|
-
// Unfortunately, regular sessions are lost when the user refreshes the page or opens a new tab.
|
|
337
|
-
// While we're figuring out the API for a longer-term solution, as a temporary workaround some
|
|
338
|
-
// *resource* servers set a cookie that keeps the user logged in after authenticated requests,
|
|
339
|
-
// and expose the fact that they set it on a special endpoint.
|
|
340
|
-
// After login, we store that fact in LocalStorage. This means that we can now look for that
|
|
341
|
-
// data, and if present, indicate that the user is already logged in.
|
|
342
|
-
// Note that there are a lot of edge cases that won't work well with this approach, so it willl
|
|
343
|
-
// be removed in due time, and is disabled by default.
|
|
344
|
-
if (
|
|
345
|
-
options.useEssSession !== true ||
|
|
346
|
-
options.restorePreviousSession === true
|
|
347
|
-
) {
|
|
348
|
-
window.localStorage.setItem(
|
|
349
|
-
"tmp-resource-server-session-enabled",
|
|
350
|
-
"false"
|
|
351
|
-
);
|
|
352
|
-
} else {
|
|
353
|
-
window.localStorage.setItem(
|
|
354
|
-
"tmp-resource-server-session-enabled",
|
|
355
|
-
"true"
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
const storedSessionCookieReference = window.localStorage.getItem(
|
|
359
|
-
"tmp-resource-server-session-info"
|
|
360
|
-
);
|
|
361
|
-
if (
|
|
362
|
-
typeof storedSessionCookieReference === "string" &&
|
|
363
|
-
options.restorePreviousSession !== true &&
|
|
364
|
-
options.useEssSession === true
|
|
365
|
-
) {
|
|
366
|
-
// TOOD: Re-use the type used when writing this data:
|
|
367
|
-
// https://github.com/inrupt/solid-client-authn-js/pull/920/files#diff-659ac87dfd3711f4cfcea3c7bf6970980f4740fd59df45f04c7977bffaa23e98R118
|
|
368
|
-
// To keep temporary code together
|
|
369
|
-
// eslint-disable-next-line no-inner-declarations
|
|
370
|
-
function isValidSessionCookieReference(
|
|
371
|
-
reference: Record<string, unknown>
|
|
372
|
-
): reference is ResourceServerSession {
|
|
373
|
-
const resourceServers = Object.keys(
|
|
374
|
-
(reference as ResourceServerSession).sessions ?? {}
|
|
375
|
-
);
|
|
376
|
-
return (
|
|
377
|
-
typeof (reference as ResourceServerSession).webId === "string" &&
|
|
378
|
-
resourceServers.length > 0 &&
|
|
379
|
-
typeof (reference as ResourceServerSession).sessions[
|
|
380
|
-
resourceServers[0]
|
|
381
|
-
].expiration === "number"
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
const reference = JSON.parse(storedSessionCookieReference);
|
|
385
|
-
if (isValidSessionCookieReference(reference)) {
|
|
386
|
-
const resourceServers = Object.keys(reference.sessions);
|
|
387
|
-
const webIdOrigin = new URL(reference.webId).hostname;
|
|
388
|
-
const ownResourceServer = resourceServers.find((resourceServer) => {
|
|
389
|
-
return new URL(resourceServer).hostname === webIdOrigin;
|
|
390
|
-
});
|
|
391
|
-
// Usually the user's WebID is also a Resource server for them,
|
|
392
|
-
// so we pick the expiration time for that. If it doesn't exist,
|
|
393
|
-
// we just pick the first (and probably only) one:
|
|
394
|
-
const relevantServer = ownResourceServer ?? resourceServers[0];
|
|
395
|
-
// If the cookie is valid for fewer than five minutes,
|
|
396
|
-
// pretend it's not valid anymore already, to avoid small misalignments
|
|
397
|
-
// resulting in invalid states:
|
|
398
|
-
if (
|
|
399
|
-
reference.sessions[relevantServer].expiration - Date.now() >
|
|
400
|
-
5 * 60 * 1000
|
|
401
|
-
) {
|
|
402
|
-
this.info.isLoggedIn = true;
|
|
403
|
-
this.info.webId = reference.webId;
|
|
404
|
-
this.tmpFetchWithCookies = true;
|
|
405
|
-
return this.info;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
// end of temporary workaround.
|
|
410
|
-
|
|
411
331
|
this.tokenRequestInProgress = true;
|
|
412
332
|
const sessionInfo = await this.clientAuthentication.handleIncomingRedirect(
|
|
413
333
|
url,
|
|
@@ -25,12 +25,12 @@ import {
|
|
|
25
25
|
} from "@inrupt/solid-client-authn-core";
|
|
26
26
|
import { jest } from "@jest/globals";
|
|
27
27
|
|
|
28
|
-
export const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
28
|
+
export const mockedRedirect = jest.fn<
|
|
29
|
+
void,
|
|
30
|
+
[redirectUrl: string, redirectOptions: IRedirectorOptions]
|
|
31
|
+
>();
|
|
32
|
+
export const mockRedirector = (): IRedirector => {
|
|
33
|
+
return {
|
|
34
|
+
redirect: mockedRedirect,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -19,23 +19,25 @@
|
|
|
19
19
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { jest, it, describe, expect, afterEach } from "@jest/globals";
|
|
23
|
+
import mockConsole from "jest-mock-console";
|
|
24
|
+
|
|
22
25
|
/**
|
|
23
26
|
* Test for AuthorizationCodeWithPkceOidcHandler
|
|
24
27
|
*/
|
|
25
28
|
import {
|
|
26
29
|
IOidcOptions,
|
|
27
|
-
IRedirector,
|
|
28
30
|
IRedirectorOptions,
|
|
29
31
|
mockStorage,
|
|
30
32
|
StorageUtility,
|
|
31
33
|
StorageUtilityMock,
|
|
32
34
|
} from "@inrupt/solid-client-authn-core";
|
|
33
|
-
|
|
35
|
+
|
|
34
36
|
import AuthorizationCodeWithPkceOidcHandler from "./AuthorizationCodeWithPkceOidcHandler";
|
|
35
37
|
import canHandleTests from "./OidcHandlerCanHandleTest";
|
|
36
38
|
import { SessionInfoManagerMock } from "../../../sessionInfo/__mocks__/SessionInfoManager";
|
|
37
39
|
import { standardOidcOptions } from "../__mocks__/IOidcOptions";
|
|
38
|
-
import {
|
|
40
|
+
import { mockedRedirect, mockRedirector } from "../__mocks__/Redirector";
|
|
39
41
|
|
|
40
42
|
jest.mock("@inrupt/oidc-client-ext");
|
|
41
43
|
|
|
@@ -57,10 +59,14 @@ const mockOidcModule = (
|
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
describe("AuthorizationCodeWithPkceOidcHandler", () => {
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
jest.resetAllMocks();
|
|
64
|
+
});
|
|
65
|
+
|
|
60
66
|
const defaultMocks = {
|
|
61
67
|
sessionCreator: SessionInfoManagerMock,
|
|
62
68
|
storageUtility: StorageUtilityMock,
|
|
63
|
-
redirector:
|
|
69
|
+
redirector: mockRedirector(),
|
|
64
70
|
};
|
|
65
71
|
|
|
66
72
|
function getAuthorizationCodeWithPkceOidcHandler(
|
|
@@ -91,20 +97,18 @@ describe("AuthorizationCodeWithPkceOidcHandler", () => {
|
|
|
91
97
|
describe("handle", () => {
|
|
92
98
|
it("swallows any redirector exceptions", async () => {
|
|
93
99
|
mockOidcModule();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
mockConsole("error");
|
|
101
|
+
|
|
102
|
+
mockedRedirect.mockImplementation(
|
|
103
|
+
(redirectUrl: string, redirectOptions: IRedirectorOptions) => {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Redirecting to [${redirectUrl}] with options [${redirectOptions}] throws...`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
103
109
|
|
|
104
110
|
const authorizationCodeWithPkceOidcHandler =
|
|
105
|
-
getAuthorizationCodeWithPkceOidcHandler(
|
|
106
|
-
redirector: redirectorThatThrowsMock,
|
|
107
|
-
});
|
|
111
|
+
getAuthorizationCodeWithPkceOidcHandler();
|
|
108
112
|
|
|
109
113
|
const oidcOptions: IOidcOptions = {
|
|
110
114
|
...standardOidcOptions,
|
|
@@ -115,8 +119,20 @@ describe("AuthorizationCodeWithPkceOidcHandler", () => {
|
|
|
115
119
|
grantTypesSupported: ["authorization_code"],
|
|
116
120
|
},
|
|
117
121
|
};
|
|
118
|
-
|
|
119
|
-
expect(
|
|
122
|
+
|
|
123
|
+
await expect(
|
|
124
|
+
authorizationCodeWithPkceOidcHandler.handle(oidcOptions)
|
|
125
|
+
).resolves.toBeUndefined();
|
|
126
|
+
|
|
127
|
+
expect(mockedRedirect).toHaveBeenCalledTimes(1);
|
|
128
|
+
|
|
129
|
+
// Test the error was even printed to the console Note: this matcher is
|
|
130
|
+
// pretty nasty due to an Error instance being logged without being
|
|
131
|
+
// converted to a string:
|
|
132
|
+
expect(console.error).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect(
|
|
134
|
+
(console as jest.Mocked<Console>).error.mock.calls[0][0].toString()
|
|
135
|
+
).toMatch(/Error: Redirecting to \[[^\]]+\] with options/);
|
|
120
136
|
});
|
|
121
137
|
|
|
122
138
|
it("handles login properly with PKCE", async () => {
|