@inrupt/solid-client-authn-browser 1.17.2 → 1.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ClientAuthentication.d.ts +7 -0
- package/dist/Session.d.ts +174 -0
- package/dist/defaultSession.d.ts +70 -0
- package/dist/dependencies.d.ts +12 -0
- package/dist/index.js +783 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +785 -18
- package/dist/index.mjs.map +1 -1
- package/dist/login/oidc/AggregateOidcHandler.d.ts +10 -0
- package/dist/login/oidc/AggregateRedirectHandler.d.ts +10 -0
- package/dist/login/oidc/ClientRegistrar.d.ts +7 -0
- package/dist/login/oidc/IssuerConfigFetcher.d.ts +10 -0
- package/dist/login/oidc/OidcLoginHandler.d.ts +10 -0
- package/dist/login/oidc/Redirector.d.ts +7 -0
- package/dist/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.d.ts +3 -0
- package/dist/login/oidc/incomingRedirectHandler/ErrorOidcHandler.d.ts +10 -0
- package/dist/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.d.ts +10 -0
- package/dist/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.d.ts +12 -0
- package/dist/login/oidc/refresh/TokenRefresher.d.ts +7 -0
- package/dist/logout/buildRpInitiatedLogout.d.ts +7 -0
- package/dist/sessionInfo/SessionInfoManager.d.ts +18 -0
- package/dist/solid-client-authn.bundle.js +7 -7
- package/dist/solid-client-authn.bundle.js.map +1 -1
- package/dist/storage/BrowserStorage.d.ts +7 -0
- package/dist/storage/StorageUtility.d.ts +14 -0
- package/dist/util/UuidGenerator.d.ts +6 -0
- package/package.json +7 -7
- package/src/ClientAuthentication.spec.ts +33 -31
- package/src/ClientAuthentication.ts +19 -17
- package/src/Session.spec.ts +10 -13
package/dist/index.mjs
CHANGED
|
@@ -1,33 +1,102 @@
|
|
|
1
|
-
import { StorageUtility, ClientAuthentication as ClientAuthentication$1, isValidRedirectUrl, EVENTS, ConfigurationError, handleRegistration, AuthorizationCodeWithPkceOidcHandlerBase, DEFAULT_SCOPES, SessionInfoManagerBase, isSupportedTokenType, clear as clear$1, getUnauthenticatedSession, loadOidcContextFromStorage, buildAuthenticatedFetch, maybeBuildRpInitiatedLogout, AggregateHandler, InMemoryStorage, IWaterfallLogoutHandler, SOLID_CLIENT_AUTHN_KEY_PREFIX, buildProxyHandler } from '@inrupt/solid-client-authn-core';
|
|
1
|
+
import { StorageUtility, ClientAuthentication as ClientAuthentication$1, isValidRedirectUrl, EVENTS, removeOpenIdParams, ConfigurationError, handleRegistration, AuthorizationCodeWithPkceOidcHandlerBase, DEFAULT_SCOPES, SessionInfoManagerBase, isSupportedTokenType, clear as clear$1, getUnauthenticatedSession, loadOidcContextFromStorage, buildAuthenticatedFetch, maybeBuildRpInitiatedLogout, AggregateHandler, InMemoryStorage, IWaterfallLogoutHandler, SOLID_CLIENT_AUTHN_KEY_PREFIX, buildProxyHandler } from '@inrupt/solid-client-authn-core';
|
|
2
2
|
export { ConfigurationError, EVENTS, InMemoryStorage, NotImplementedError } from '@inrupt/solid-client-authn-core';
|
|
3
3
|
import { v4 } from 'uuid';
|
|
4
4
|
import EventEmitter from 'events';
|
|
5
|
-
import {
|
|
5
|
+
import { normalizeCallbackUrl, OidcClient, clearOidcPersistentStorage, getDpopToken, getBearerToken, registerClient, refresh } from '@inrupt/oidc-client-ext';
|
|
6
6
|
import { fetch as fetch$1 } from '@inrupt/universal-fetch';
|
|
7
7
|
|
|
8
|
+
//
|
|
9
|
+
// Copyright Inrupt Inc.
|
|
10
|
+
//
|
|
11
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
13
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
14
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
15
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
16
|
+
// subject to the following conditions:
|
|
17
|
+
//
|
|
18
|
+
// The above copyright notice and this permission notice shall be included in
|
|
19
|
+
// all copies or substantial portions of the Software.
|
|
20
|
+
//
|
|
21
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
22
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
23
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
24
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
25
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
26
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
27
|
+
//
|
|
28
|
+
/**
|
|
29
|
+
* This class in a no-value-added extension of StorageUtility from the core module.
|
|
30
|
+
* The reason it has to be declared is for TSyringe to find the decorators in the
|
|
31
|
+
* same modules as where the dependency container is declared (in this case,
|
|
32
|
+
* the browser module, with the dependancy container in dependencies.ts).
|
|
33
|
+
* @hidden
|
|
34
|
+
*/
|
|
8
35
|
class StorageUtilityBrowser extends StorageUtility {
|
|
9
36
|
constructor(secureStorage, insecureStorage) {
|
|
10
37
|
super(secureStorage, insecureStorage);
|
|
11
38
|
}
|
|
12
39
|
}
|
|
13
40
|
|
|
41
|
+
//
|
|
42
|
+
// Copyright Inrupt Inc.
|
|
43
|
+
//
|
|
44
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
45
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
46
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
47
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
48
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
49
|
+
// subject to the following conditions:
|
|
50
|
+
//
|
|
51
|
+
// The above copyright notice and this permission notice shall be included in
|
|
52
|
+
// all copies or substantial portions of the Software.
|
|
53
|
+
//
|
|
54
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
55
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
56
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
57
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
58
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
59
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
60
|
+
//
|
|
61
|
+
/**
|
|
62
|
+
* @hidden
|
|
63
|
+
*/
|
|
14
64
|
class ClientAuthentication extends ClientAuthentication$1 {
|
|
15
65
|
constructor() {
|
|
16
66
|
super(...arguments);
|
|
67
|
+
// Define these functions as properties so that they don't get accidentally re-bound.
|
|
68
|
+
// Isn't Javascript fun?
|
|
17
69
|
this.login = async (options, eventEmitter) => {
|
|
18
70
|
var _a, _b;
|
|
71
|
+
// In order to get a clean start, make sure that the session is logged out
|
|
72
|
+
// on login.
|
|
73
|
+
// But we may want to preserve our client application info, particularly if
|
|
74
|
+
// we used Dynamic Client Registration to register (since we don't
|
|
75
|
+
// necessarily want the user to have to register this app each time they
|
|
76
|
+
// login).
|
|
19
77
|
await this.sessionInfoManager.clear(options.sessionId);
|
|
20
|
-
|
|
78
|
+
// In the case of the user hitting the 'back' button in their browser, they
|
|
79
|
+
// could return to a previous redirect URL that contains OIDC params that
|
|
80
|
+
// are now longer valid. To be safe, strip relevant params now.
|
|
81
|
+
// If the user is providing a redirect IRI, it should not be modified, so
|
|
82
|
+
// normalization only applies if we default to the current location (which is
|
|
83
|
+
// a bad practice and should be discouraged).
|
|
84
|
+
const redirectUrl = (_a = options.redirectUrl) !== null && _a !== void 0 ? _a : normalizeCallbackUrl(window.location.href);
|
|
21
85
|
if (!isValidRedirectUrl(redirectUrl)) {
|
|
22
86
|
throw new Error(`${redirectUrl} is not a valid redirect URL, it is either a malformed IRI, includes a hash fragment, or reserved query parameters ('code' or 'state').`);
|
|
23
87
|
}
|
|
24
88
|
await this.loginHandler.handle({
|
|
25
89
|
...options,
|
|
26
90
|
redirectUrl,
|
|
91
|
+
// If no clientName is provided, the clientId may be used instead.
|
|
27
92
|
clientName: (_b = options.clientName) !== null && _b !== void 0 ? _b : options.clientId,
|
|
28
93
|
eventEmitter,
|
|
29
94
|
});
|
|
30
95
|
};
|
|
96
|
+
// Collects session information from storage, and returns them. Returns null
|
|
97
|
+
// if the expected information cannot be found.
|
|
98
|
+
// Note that the ID token is not stored, which means the session information
|
|
99
|
+
// cannot be validated at this point.
|
|
31
100
|
this.validateCurrentSession = async (currentSessionId) => {
|
|
32
101
|
const sessionInfo = await this.sessionInfoManager.get(currentSessionId);
|
|
33
102
|
if (sessionInfo === undefined ||
|
|
@@ -40,9 +109,14 @@ class ClientAuthentication extends ClientAuthentication$1 {
|
|
|
40
109
|
this.handleIncomingRedirect = async (url, eventEmitter) => {
|
|
41
110
|
try {
|
|
42
111
|
const redirectInfo = await this.redirectHandler.handle(url, eventEmitter);
|
|
112
|
+
// The `FallbackRedirectHandler` directly returns the global `fetch` for
|
|
113
|
+
// his value, so we should ensure it's bound to `window` rather than to
|
|
114
|
+
// ClientAuthentication, to avoid the following error:
|
|
115
|
+
// > 'fetch' called on an object that does not implement interface Window.
|
|
43
116
|
this.fetch = redirectInfo.fetch.bind(window);
|
|
44
117
|
this.boundLogout = redirectInfo.getLogoutUrl;
|
|
45
|
-
|
|
118
|
+
// Strip the oauth params:
|
|
119
|
+
await this.cleanUrlAfterRedirect(url);
|
|
46
120
|
return {
|
|
47
121
|
isLoggedIn: redirectInfo.isLoggedIn,
|
|
48
122
|
webId: redirectInfo.webId,
|
|
@@ -51,31 +125,69 @@ class ClientAuthentication extends ClientAuthentication$1 {
|
|
|
51
125
|
};
|
|
52
126
|
}
|
|
53
127
|
catch (err) {
|
|
54
|
-
|
|
128
|
+
// Strip the oauth params:
|
|
129
|
+
await this.cleanUrlAfterRedirect(url);
|
|
130
|
+
// FIXME: EVENTS.ERROR should be errorCode, errorDescription
|
|
131
|
+
//
|
|
132
|
+
// I'm not sure if "redirect" is a good error code, and in theory `err`
|
|
133
|
+
// maybe an Error object and not a string; Maybe we want to just hardcode
|
|
134
|
+
// a description instead?
|
|
55
135
|
eventEmitter.emit(EVENTS.ERROR, "redirect", err);
|
|
56
136
|
return undefined;
|
|
57
137
|
}
|
|
58
138
|
};
|
|
59
139
|
}
|
|
60
|
-
cleanUrlAfterRedirect(url) {
|
|
61
|
-
const cleanedUpUrl =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
140
|
+
async cleanUrlAfterRedirect(url) {
|
|
141
|
+
const cleanedUpUrl = removeOpenIdParams(url).href;
|
|
142
|
+
// Remove OAuth-specific query params (since the login flow finishes with
|
|
143
|
+
// the browser being redirected back with OAuth2 query params (e.g. for
|
|
144
|
+
// 'code' and 'state'), and so if the user simply refreshes this page our
|
|
145
|
+
// authentication library will be called again with what are now invalid
|
|
146
|
+
// query parameters!).
|
|
147
|
+
window.history.replaceState(null, "", cleanedUpUrl);
|
|
148
|
+
while (window.location.href !== cleanedUpUrl) {
|
|
149
|
+
// Poll the current URL every ms. Active polling is required because
|
|
150
|
+
// window.history.replaceState is asynchronous, but the associated
|
|
151
|
+
// 'popstate' event which should be listened to is only sent on active
|
|
152
|
+
// navigation, which we will not have here.
|
|
153
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event#when_popstate_is_sent
|
|
154
|
+
// eslint-disable-next-line no-await-in-loop
|
|
155
|
+
await new Promise((resolve) => {
|
|
156
|
+
setTimeout(() => resolve(), 1);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
70
159
|
}
|
|
71
160
|
}
|
|
72
161
|
|
|
162
|
+
//
|
|
163
|
+
// Copyright Inrupt Inc.
|
|
164
|
+
//
|
|
165
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
166
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
167
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
168
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
169
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
170
|
+
// subject to the following conditions:
|
|
171
|
+
//
|
|
172
|
+
// The above copyright notice and this permission notice shall be included in
|
|
173
|
+
// all copies or substantial portions of the Software.
|
|
174
|
+
//
|
|
175
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
176
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
177
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
178
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
179
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
180
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
181
|
+
//
|
|
73
182
|
function hasIssuer(options) {
|
|
74
183
|
return typeof options.oidcIssuer === "string";
|
|
75
184
|
}
|
|
76
185
|
function hasRedirectUrl(options) {
|
|
77
186
|
return typeof options.redirectUrl === "string";
|
|
78
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* @hidden
|
|
190
|
+
*/
|
|
79
191
|
class OidcLoginHandler {
|
|
80
192
|
constructor(storageUtility, oidcHandler, issuerConfigFetcher, clientRegistrar) {
|
|
81
193
|
this.storageUtility = storageUtility;
|
|
@@ -97,22 +209,57 @@ class OidcLoginHandler {
|
|
|
97
209
|
if (!hasRedirectUrl(options)) {
|
|
98
210
|
throw new ConfigurationError(`OidcLoginHandler requires a redirect URL: missing property 'redirectUrl' in ${JSON.stringify(options)}`);
|
|
99
211
|
}
|
|
212
|
+
// Fetch issuer config.
|
|
100
213
|
const issuerConfig = await this.issuerConfigFetcher.fetchConfig(options.oidcIssuer);
|
|
101
214
|
const clientRegistration = await handleRegistration(options, issuerConfig, this.storageUtility, this.clientRegistrar);
|
|
215
|
+
// Construct OIDC Options
|
|
102
216
|
const OidcOptions = {
|
|
217
|
+
// Note that here, the issuer is not the one from the received options, but
|
|
218
|
+
// from the issuer's config. This enforces the canonical URL is used and stored,
|
|
219
|
+
// which is also the one present in the ID token, so storing a technically
|
|
220
|
+
// valid, but different issuer URL (e.g. using a trailing slash or not) now
|
|
221
|
+
// could prevent from validating the ID token later.
|
|
103
222
|
issuer: issuerConfig.issuer,
|
|
223
|
+
// TODO: differentiate if DPoP should be true
|
|
104
224
|
dpop: options.tokenType.toLowerCase() === "dpop",
|
|
105
225
|
...options,
|
|
106
226
|
issuerConfiguration: issuerConfig,
|
|
107
227
|
client: clientRegistration,
|
|
108
228
|
};
|
|
229
|
+
// Call proper OIDC Handler
|
|
109
230
|
return this.oidcHandler.handle(OidcOptions);
|
|
110
231
|
}
|
|
111
232
|
}
|
|
112
233
|
|
|
234
|
+
//
|
|
235
|
+
// Copyright Inrupt Inc.
|
|
236
|
+
//
|
|
237
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
238
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
239
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
240
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
241
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
242
|
+
// subject to the following conditions:
|
|
243
|
+
//
|
|
244
|
+
// The above copyright notice and this permission notice shall be included in
|
|
245
|
+
// all copies or substantial portions of the Software.
|
|
246
|
+
//
|
|
247
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
248
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
249
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
250
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
251
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
252
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
253
|
+
//
|
|
254
|
+
/**
|
|
255
|
+
* @hidden
|
|
256
|
+
* Authorization code flow spec: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
|
|
257
|
+
* PKCE: https://tools.ietf.org/html/rfc7636
|
|
258
|
+
*/
|
|
113
259
|
class AuthorizationCodeWithPkceOidcHandler extends AuthorizationCodeWithPkceOidcHandlerBase {
|
|
114
260
|
async handle(oidcLoginOptions) {
|
|
115
261
|
var _a;
|
|
262
|
+
/* eslint-disable camelcase */
|
|
116
263
|
const oidcOptions = {
|
|
117
264
|
authority: oidcLoginOptions.issuer.toString(),
|
|
118
265
|
client_id: oidcLoginOptions.client.clientId,
|
|
@@ -122,28 +269,58 @@ class AuthorizationCodeWithPkceOidcHandler extends AuthorizationCodeWithPkceOidc
|
|
|
122
269
|
response_type: "code",
|
|
123
270
|
scope: DEFAULT_SCOPES,
|
|
124
271
|
filterProtocolClaims: true,
|
|
272
|
+
// The userinfo endpoint on NSS fails, so disable this for now
|
|
273
|
+
// Note that in Solid, information should be retrieved from the
|
|
274
|
+
// profile referenced by the WebId.
|
|
125
275
|
loadUserInfo: false,
|
|
126
276
|
code_verifier: true,
|
|
127
277
|
prompt: (_a = oidcLoginOptions.prompt) !== null && _a !== void 0 ? _a : "consent",
|
|
128
278
|
};
|
|
279
|
+
/* eslint-enable camelcase */
|
|
129
280
|
const oidcClientLibrary = new OidcClient(oidcOptions);
|
|
130
281
|
try {
|
|
131
282
|
const signingRequest = await oidcClientLibrary.createSigninRequest();
|
|
283
|
+
// Make sure to await the promise before returning so that the error is caught.
|
|
132
284
|
return await this.handleRedirect({
|
|
133
285
|
oidcLoginOptions,
|
|
286
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
134
287
|
state: signingRequest.state._id,
|
|
288
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
135
289
|
codeVerifier: signingRequest.state._code_verifier,
|
|
136
290
|
targetUrl: signingRequest.url.toString(),
|
|
137
291
|
});
|
|
138
292
|
}
|
|
139
293
|
catch (err) {
|
|
294
|
+
// eslint-disable-next-line no-console
|
|
140
295
|
console.error(err);
|
|
141
296
|
}
|
|
297
|
+
// The login is only completed AFTER redirect, so nothing to return here.
|
|
142
298
|
return undefined;
|
|
143
299
|
}
|
|
144
300
|
}
|
|
145
301
|
|
|
302
|
+
//
|
|
303
|
+
// Copyright Inrupt Inc.
|
|
304
|
+
//
|
|
305
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
306
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
307
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
308
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
309
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
310
|
+
// subject to the following conditions:
|
|
311
|
+
//
|
|
312
|
+
// The above copyright notice and this permission notice shall be included in
|
|
313
|
+
// all copies or substantial portions of the Software.
|
|
314
|
+
//
|
|
315
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
316
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
317
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
318
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
319
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
320
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
321
|
+
//
|
|
146
322
|
const WELL_KNOWN_OPENID_CONFIG = ".well-known/openid-configuration";
|
|
323
|
+
/* eslint-disable camelcase */
|
|
147
324
|
const issuerConfigKeyMap = {
|
|
148
325
|
issuer: {
|
|
149
326
|
toKey: "issuer",
|
|
@@ -231,10 +408,15 @@ const issuerConfigKeyMap = {
|
|
|
231
408
|
convertToUrl: true,
|
|
232
409
|
},
|
|
233
410
|
};
|
|
411
|
+
/* eslint-enable camelcase */
|
|
234
412
|
function processConfig(config) {
|
|
235
413
|
const parsedConfig = {};
|
|
236
414
|
Object.keys(config).forEach((key) => {
|
|
237
415
|
if (issuerConfigKeyMap[key]) {
|
|
416
|
+
// TODO: PMcB55: Validate URL if "issuerConfigKeyMap[key].convertToUrl"
|
|
417
|
+
// if (issuerConfigKeyMap[key].convertToUrl) {
|
|
418
|
+
// validateUrl(config[key]);
|
|
419
|
+
// }
|
|
238
420
|
parsedConfig[issuerConfigKeyMap[key].toKey] = config[key];
|
|
239
421
|
}
|
|
240
422
|
});
|
|
@@ -243,33 +425,71 @@ function processConfig(config) {
|
|
|
243
425
|
}
|
|
244
426
|
return parsedConfig;
|
|
245
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* @hidden
|
|
430
|
+
*/
|
|
246
431
|
class IssuerConfigFetcher {
|
|
247
432
|
constructor(storageUtility) {
|
|
248
433
|
this.storageUtility = storageUtility;
|
|
249
434
|
this.storageUtility = storageUtility;
|
|
250
435
|
}
|
|
436
|
+
// This method needs no state (so can be static), and can be exposed to allow
|
|
437
|
+
// callers to know where this implementation puts state it needs.
|
|
251
438
|
static getLocalStorageKey(issuer) {
|
|
252
439
|
return `issuerConfig:${issuer}`;
|
|
253
440
|
}
|
|
254
441
|
async fetchConfig(issuer) {
|
|
255
442
|
let issuerConfig;
|
|
256
|
-
const openIdConfigUrl = new URL(WELL_KNOWN_OPENID_CONFIG,
|
|
443
|
+
const openIdConfigUrl = new URL(WELL_KNOWN_OPENID_CONFIG,
|
|
444
|
+
// Make sure to append a slash at issuer URL, so that the .well-known URL
|
|
445
|
+
// includes the full issuer path. See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig.
|
|
446
|
+
issuer.endsWith("/") ? issuer : `${issuer}/`).href;
|
|
257
447
|
const issuerConfigRequestBody = await fetch$1.call(globalThis, openIdConfigUrl);
|
|
448
|
+
// Check the validity of the fetched config
|
|
258
449
|
try {
|
|
259
450
|
issuerConfig = processConfig(await issuerConfigRequestBody.json());
|
|
260
451
|
}
|
|
261
452
|
catch (err) {
|
|
262
453
|
throw new ConfigurationError(`[${issuer.toString()}] has an invalid configuration: ${err.message}`);
|
|
263
454
|
}
|
|
455
|
+
// Update store with fetched config
|
|
264
456
|
await this.storageUtility.set(IssuerConfigFetcher.getLocalStorageKey(issuer), JSON.stringify(issuerConfig));
|
|
265
457
|
return issuerConfig;
|
|
266
458
|
}
|
|
267
459
|
}
|
|
268
460
|
|
|
461
|
+
//
|
|
462
|
+
// Copyright Inrupt Inc.
|
|
463
|
+
//
|
|
464
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
465
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
466
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
467
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
468
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
469
|
+
// subject to the following conditions:
|
|
470
|
+
//
|
|
471
|
+
// The above copyright notice and this permission notice shall be included in
|
|
472
|
+
// all copies or substantial portions of the Software.
|
|
473
|
+
//
|
|
474
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
475
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
476
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
477
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
478
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
479
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
480
|
+
//
|
|
481
|
+
/**
|
|
482
|
+
* @param sessionId
|
|
483
|
+
* @param storage
|
|
484
|
+
* @hidden
|
|
485
|
+
*/
|
|
269
486
|
async function clear(sessionId, storage) {
|
|
270
487
|
await clear$1(sessionId, storage);
|
|
271
488
|
await clearOidcPersistentStorage();
|
|
272
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* @hidden
|
|
492
|
+
*/
|
|
273
493
|
class SessionInfoManager extends SessionInfoManagerBase {
|
|
274
494
|
async get(sessionId) {
|
|
275
495
|
const [isLoggedIn, webId, clientId, clientSecret, redirectUrl, refreshToken, issuer, tokenType,] = await Promise.all([
|
|
@@ -299,6 +519,9 @@ class SessionInfoManager extends SessionInfoManagerBase {
|
|
|
299
519
|
}),
|
|
300
520
|
]);
|
|
301
521
|
if (typeof redirectUrl === "string" && !isValidRedirectUrl(redirectUrl)) {
|
|
522
|
+
// This resolves the issue for people experiencing https://github.com/inrupt/solid-client-authn-js/issues/2891.
|
|
523
|
+
// An invalid redirect URL is present in the storage, and the session should
|
|
524
|
+
// be cleared to get a fresh start. This will require the user to log back in.
|
|
302
525
|
await Promise.all([
|
|
303
526
|
this.storageUtility.deleteAllUserData(sessionId, { secure: false }),
|
|
304
527
|
this.storageUtility.deleteAllUserData(sessionId, { secure: true }),
|
|
@@ -323,17 +546,52 @@ class SessionInfoManager extends SessionInfoManagerBase {
|
|
|
323
546
|
issuer,
|
|
324
547
|
clientAppId: clientId,
|
|
325
548
|
clientAppSecret: clientSecret,
|
|
549
|
+
// Default the token type to DPoP if unspecified.
|
|
326
550
|
tokenType: tokenType !== null && tokenType !== void 0 ? tokenType : "DPoP",
|
|
327
551
|
};
|
|
328
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* This function removes all session-related information from storage.
|
|
555
|
+
* @param sessionId the session identifier
|
|
556
|
+
* @param storage the storage where session info is stored
|
|
557
|
+
* @hidden
|
|
558
|
+
*/
|
|
329
559
|
async clear(sessionId) {
|
|
330
560
|
return clear(sessionId, this.storageUtility);
|
|
331
561
|
}
|
|
332
562
|
}
|
|
333
563
|
|
|
564
|
+
//
|
|
565
|
+
// Copyright Inrupt Inc.
|
|
566
|
+
//
|
|
567
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
568
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
569
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
570
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
571
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
572
|
+
// subject to the following conditions:
|
|
573
|
+
//
|
|
574
|
+
// The above copyright notice and this permission notice shall be included in
|
|
575
|
+
// all copies or substantial portions of the Software.
|
|
576
|
+
//
|
|
577
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
578
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
579
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
580
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
581
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
582
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
583
|
+
//
|
|
584
|
+
/**
|
|
585
|
+
* This class handles redirect IRIs without any query params, and returns an unauthenticated
|
|
586
|
+
* session. It serves as a fallback so that consuming libraries don't have to test
|
|
587
|
+
* for the query params themselves, and can always try to use them as a redirect IRI.
|
|
588
|
+
* @hidden
|
|
589
|
+
*/
|
|
334
590
|
class FallbackRedirectHandler {
|
|
335
591
|
async canHandle(redirectUrl) {
|
|
336
592
|
try {
|
|
593
|
+
// The next URL object is built for validating it.
|
|
594
|
+
// eslint-disable-next-line no-new
|
|
337
595
|
new URL(redirectUrl);
|
|
338
596
|
return true;
|
|
339
597
|
}
|
|
@@ -341,12 +599,44 @@ class FallbackRedirectHandler {
|
|
|
341
599
|
throw new Error(`[${redirectUrl}] is not a valid URL, and cannot be used as a redirect URL: ${e}`);
|
|
342
600
|
}
|
|
343
601
|
}
|
|
344
|
-
async handle(
|
|
602
|
+
async handle(
|
|
603
|
+
// The argument is ignored, but must be present to implement the interface
|
|
604
|
+
_redirectUrl) {
|
|
345
605
|
return getUnauthenticatedSession();
|
|
346
606
|
}
|
|
347
607
|
}
|
|
348
608
|
|
|
609
|
+
//
|
|
610
|
+
// Copyright Inrupt Inc.
|
|
611
|
+
//
|
|
612
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
613
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
614
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
615
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
616
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
617
|
+
// subject to the following conditions:
|
|
618
|
+
//
|
|
619
|
+
// The above copyright notice and this permission notice shall be included in
|
|
620
|
+
// all copies or substantial portions of the Software.
|
|
621
|
+
//
|
|
622
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
623
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
624
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
625
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
626
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
627
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
628
|
+
//
|
|
629
|
+
/**
|
|
630
|
+
* @hidden
|
|
631
|
+
* @packageDocumentation
|
|
632
|
+
*/
|
|
633
|
+
// FIXME: The following doesn't work in the browser, it results in all the fetches
|
|
634
|
+
// being unauthenticated. This should be looked into when migrating to universal-fetch.
|
|
635
|
+
// import { fetch } from "cross-fetch";
|
|
349
636
|
const globalFetch = (...args) => fetch$1.call(globalThis, ...args);
|
|
637
|
+
/**
|
|
638
|
+
* @hidden
|
|
639
|
+
*/
|
|
350
640
|
class AuthCodeRedirectHandler {
|
|
351
641
|
constructor(storageUtility, sessionInfoManager, issuerConfigFetcher, clientRegistrar, tokerRefresher) {
|
|
352
642
|
this.storageUtility = storageUtility;
|
|
@@ -396,10 +686,15 @@ class AuthCodeRedirectHandler {
|
|
|
396
686
|
if (isDpop) {
|
|
397
687
|
tokens = await getDpopToken(issuerConfig, client, {
|
|
398
688
|
grantType: "authorization_code",
|
|
689
|
+
// We rely on our 'canHandle' function checking that the OAuth 'code'
|
|
690
|
+
// parameter is present in our query string.
|
|
399
691
|
code: url.searchParams.get("code"),
|
|
400
692
|
codeVerifier,
|
|
401
693
|
redirectUrl: storedRedirectIri,
|
|
402
694
|
});
|
|
695
|
+
// Delete oidc-client-specific session information from storage. This is
|
|
696
|
+
// done automatically when retrieving a bearer token, but since the DPoP
|
|
697
|
+
// binding uses our custom code, this needs to be done manually.
|
|
403
698
|
window.localStorage.removeItem(`oidc.${oauthState}`);
|
|
404
699
|
}
|
|
405
700
|
else {
|
|
@@ -440,12 +735,58 @@ class AuthCodeRedirectHandler {
|
|
|
440
735
|
}
|
|
441
736
|
}
|
|
442
737
|
|
|
738
|
+
//
|
|
739
|
+
// Copyright Inrupt Inc.
|
|
740
|
+
//
|
|
741
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
742
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
743
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
744
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
745
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
746
|
+
// subject to the following conditions:
|
|
747
|
+
//
|
|
748
|
+
// The above copyright notice and this permission notice shall be included in
|
|
749
|
+
// all copies or substantial portions of the Software.
|
|
750
|
+
//
|
|
751
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
752
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
753
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
754
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
755
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
756
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
757
|
+
//
|
|
758
|
+
/**
|
|
759
|
+
* @hidden
|
|
760
|
+
*/
|
|
443
761
|
class AggregateRedirectHandler extends AggregateHandler {
|
|
444
762
|
constructor(redirectHandlers) {
|
|
445
763
|
super(redirectHandlers);
|
|
446
764
|
}
|
|
447
765
|
}
|
|
448
766
|
|
|
767
|
+
//
|
|
768
|
+
// Copyright Inrupt Inc.
|
|
769
|
+
//
|
|
770
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
771
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
772
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
773
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
774
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
775
|
+
// subject to the following conditions:
|
|
776
|
+
//
|
|
777
|
+
// The above copyright notice and this permission notice shall be included in
|
|
778
|
+
// all copies or substantial portions of the Software.
|
|
779
|
+
//
|
|
780
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
781
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
782
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
783
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
784
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
785
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
786
|
+
//
|
|
787
|
+
/**
|
|
788
|
+
* @hidden
|
|
789
|
+
*/
|
|
449
790
|
class BrowserStorage {
|
|
450
791
|
get storage() {
|
|
451
792
|
return window.localStorage;
|
|
@@ -461,6 +802,29 @@ class BrowserStorage {
|
|
|
461
802
|
}
|
|
462
803
|
}
|
|
463
804
|
|
|
805
|
+
//
|
|
806
|
+
// Copyright Inrupt Inc.
|
|
807
|
+
//
|
|
808
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
809
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
810
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
811
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
812
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
813
|
+
// subject to the following conditions:
|
|
814
|
+
//
|
|
815
|
+
// The above copyright notice and this permission notice shall be included in
|
|
816
|
+
// all copies or substantial portions of the Software.
|
|
817
|
+
//
|
|
818
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
819
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
820
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
821
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
822
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
823
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
824
|
+
//
|
|
825
|
+
/**
|
|
826
|
+
* @hidden
|
|
827
|
+
*/
|
|
464
828
|
class Redirector {
|
|
465
829
|
redirect(redirectUrl, options) {
|
|
466
830
|
if (options && options.handleRedirect) {
|
|
@@ -475,19 +839,49 @@ class Redirector {
|
|
|
475
839
|
}
|
|
476
840
|
}
|
|
477
841
|
|
|
842
|
+
//
|
|
843
|
+
// Copyright Inrupt Inc.
|
|
844
|
+
//
|
|
845
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
846
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
847
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
848
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
849
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
850
|
+
// subject to the following conditions:
|
|
851
|
+
//
|
|
852
|
+
// The above copyright notice and this permission notice shall be included in
|
|
853
|
+
// all copies or substantial portions of the Software.
|
|
854
|
+
//
|
|
855
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
856
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
857
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
858
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
859
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
860
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
861
|
+
//
|
|
862
|
+
/**
|
|
863
|
+
* @hidden
|
|
864
|
+
*/
|
|
478
865
|
class ClientRegistrar {
|
|
479
866
|
constructor(storageUtility) {
|
|
480
867
|
this.storageUtility = storageUtility;
|
|
481
868
|
this.storageUtility = storageUtility;
|
|
482
869
|
}
|
|
483
870
|
async getClient(options, issuerConfig) {
|
|
484
|
-
|
|
871
|
+
// If client secret and/or client id are stored in storage, use those.
|
|
872
|
+
const [storedClientId, storedClientSecret,
|
|
873
|
+
// storedClientName,
|
|
874
|
+
] = await Promise.all([
|
|
485
875
|
this.storageUtility.getForUser(options.sessionId, "clientId", {
|
|
486
876
|
secure: false,
|
|
487
877
|
}),
|
|
488
878
|
this.storageUtility.getForUser(options.sessionId, "clientSecret", {
|
|
489
879
|
secure: false,
|
|
490
880
|
}),
|
|
881
|
+
// this.storageUtility.getForUser(options.sessionId, "clientName", {
|
|
882
|
+
// // FIXME: figure out how to persist secure storage at reload
|
|
883
|
+
// secure: false,
|
|
884
|
+
// }),
|
|
491
885
|
]);
|
|
492
886
|
if (storedClientId) {
|
|
493
887
|
return {
|
|
@@ -498,6 +892,7 @@ class ClientRegistrar {
|
|
|
498
892
|
}
|
|
499
893
|
try {
|
|
500
894
|
const registeredClient = await registerClient(options, issuerConfig);
|
|
895
|
+
// Save info
|
|
501
896
|
const infoToSave = {
|
|
502
897
|
clientId: registeredClient.clientId,
|
|
503
898
|
};
|
|
@@ -509,6 +904,9 @@ class ClientRegistrar {
|
|
|
509
904
|
registeredClient.idTokenSignedResponseAlg;
|
|
510
905
|
}
|
|
511
906
|
await this.storageUtility.setForUser(options.sessionId, infoToSave, {
|
|
907
|
+
// FIXME: figure out how to persist secure storage at reload
|
|
908
|
+
// Otherwise, the client info cannot be retrieved from storage, and
|
|
909
|
+
// the lib tries to re-register the client on each fetch
|
|
512
910
|
secure: false,
|
|
513
911
|
});
|
|
514
912
|
return registeredClient;
|
|
@@ -519,9 +917,36 @@ class ClientRegistrar {
|
|
|
519
917
|
}
|
|
520
918
|
}
|
|
521
919
|
|
|
920
|
+
//
|
|
921
|
+
// Copyright Inrupt Inc.
|
|
922
|
+
//
|
|
923
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
924
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
925
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
926
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
927
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
928
|
+
// subject to the following conditions:
|
|
929
|
+
//
|
|
930
|
+
// The above copyright notice and this permission notice shall be included in
|
|
931
|
+
// all copies or substantial portions of the Software.
|
|
932
|
+
//
|
|
933
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
934
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
935
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
936
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
937
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
938
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
939
|
+
//
|
|
940
|
+
/**
|
|
941
|
+
* This class handles redirect IRIs without any query params, and returns an unauthenticated
|
|
942
|
+
* session. It serves as a fallback so that consuming libraries don't have to test
|
|
943
|
+
* for the query params themselves, and can always try to use them as a redirect IRI.
|
|
944
|
+
* @hidden
|
|
945
|
+
*/
|
|
522
946
|
class ErrorOidcHandler {
|
|
523
947
|
async canHandle(redirectUrl) {
|
|
524
948
|
try {
|
|
949
|
+
// eslint-disable-next-line no-new
|
|
525
950
|
return new URL(redirectUrl).searchParams.has("error");
|
|
526
951
|
}
|
|
527
952
|
catch (e) {
|
|
@@ -539,6 +964,32 @@ class ErrorOidcHandler {
|
|
|
539
964
|
}
|
|
540
965
|
}
|
|
541
966
|
|
|
967
|
+
//
|
|
968
|
+
// Copyright Inrupt Inc.
|
|
969
|
+
//
|
|
970
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
971
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
972
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
973
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
974
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
975
|
+
// subject to the following conditions:
|
|
976
|
+
//
|
|
977
|
+
// The above copyright notice and this permission notice shall be included in
|
|
978
|
+
// all copies or substantial portions of the Software.
|
|
979
|
+
//
|
|
980
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
981
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
982
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
983
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
984
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
985
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
986
|
+
//
|
|
987
|
+
// Some identifiers are not in camelcase on purpose, as they are named using the
|
|
988
|
+
// official names from the OIDC/OAuth2 specifications.
|
|
989
|
+
/* eslint-disable camelcase */
|
|
990
|
+
/**
|
|
991
|
+
* @hidden
|
|
992
|
+
*/
|
|
542
993
|
class TokenRefresher {
|
|
543
994
|
constructor(storageUtility, issuerConfigFetcher, clientRegistrar) {
|
|
544
995
|
this.storageUtility = storageUtility;
|
|
@@ -550,8 +1001,10 @@ class TokenRefresher {
|
|
|
550
1001
|
}
|
|
551
1002
|
async refresh(sessionId, refreshToken, dpopKey, eventEmitter) {
|
|
552
1003
|
const oidcContext = await loadOidcContextFromStorage(sessionId, this.storageUtility, this.issuerConfigFetcher);
|
|
1004
|
+
// This should also retrieve the client from storage
|
|
553
1005
|
const clientInfo = await this.clientRegistrar.getClient({ sessionId }, oidcContext.issuerConfig);
|
|
554
1006
|
if (refreshToken === undefined) {
|
|
1007
|
+
// TODO: in a next PR, look up storage for a refresh token
|
|
555
1008
|
throw new Error(`Session [${sessionId}] has no refresh token to allow it to refresh its access token.`);
|
|
556
1009
|
}
|
|
557
1010
|
if (oidcContext.dpop && dpopKey === undefined) {
|
|
@@ -568,6 +1021,31 @@ class TokenRefresher {
|
|
|
568
1021
|
}
|
|
569
1022
|
}
|
|
570
1023
|
|
|
1024
|
+
//
|
|
1025
|
+
// Copyright Inrupt Inc.
|
|
1026
|
+
//
|
|
1027
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1028
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
1029
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
1030
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
1031
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
1032
|
+
// subject to the following conditions:
|
|
1033
|
+
//
|
|
1034
|
+
// The above copyright notice and this permission notice shall be included in
|
|
1035
|
+
// all copies or substantial portions of the Software.
|
|
1036
|
+
//
|
|
1037
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
1038
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
1039
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
1040
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
1041
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
1042
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1043
|
+
//
|
|
1044
|
+
/**
|
|
1045
|
+
*
|
|
1046
|
+
* @param dependencies
|
|
1047
|
+
* @deprecated This function will be removed from the external API in an upcoming release.
|
|
1048
|
+
*/
|
|
571
1049
|
function getClientAuthenticationWithDependencies(dependencies) {
|
|
572
1050
|
const inMemoryStorage = new InMemoryStorage();
|
|
573
1051
|
const secureStorage = dependencies.secureStorage || inMemoryStorage;
|
|
@@ -578,22 +1056,69 @@ function getClientAuthenticationWithDependencies(dependencies) {
|
|
|
578
1056
|
const sessionInfoManager = new SessionInfoManager(storageUtility);
|
|
579
1057
|
const tokenRefresher = new TokenRefresher(storageUtility, issuerConfigFetcher, clientRegistrar);
|
|
580
1058
|
const redirector = new Redirector();
|
|
1059
|
+
// make new handler for redirect and login
|
|
581
1060
|
const loginHandler = new OidcLoginHandler(storageUtility, new AuthorizationCodeWithPkceOidcHandler(storageUtility, redirector), issuerConfigFetcher, clientRegistrar);
|
|
582
1061
|
const redirectHandler = new AggregateRedirectHandler([
|
|
583
1062
|
new ErrorOidcHandler(),
|
|
584
1063
|
new AuthCodeRedirectHandler(storageUtility, sessionInfoManager, issuerConfigFetcher, clientRegistrar, tokenRefresher),
|
|
1064
|
+
// This catch-all class will always be able to handle the
|
|
1065
|
+
// redirect IRI, so it must be registered last.
|
|
585
1066
|
new FallbackRedirectHandler(),
|
|
586
1067
|
]);
|
|
587
1068
|
return new ClientAuthentication(loginHandler, redirectHandler, new IWaterfallLogoutHandler(sessionInfoManager, redirector), sessionInfoManager, issuerConfigFetcher);
|
|
588
1069
|
}
|
|
589
1070
|
|
|
1071
|
+
//
|
|
1072
|
+
// Copyright Inrupt Inc.
|
|
1073
|
+
//
|
|
1074
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1075
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
1076
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
1077
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
1078
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
1079
|
+
// subject to the following conditions:
|
|
1080
|
+
//
|
|
1081
|
+
// The above copyright notice and this permission notice shall be included in
|
|
1082
|
+
// all copies or substantial portions of the Software.
|
|
1083
|
+
//
|
|
1084
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
1085
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
1086
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
1087
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
1088
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
1089
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1090
|
+
//
|
|
590
1091
|
const KEY_CURRENT_SESSION = `${SOLID_CLIENT_AUTHN_KEY_PREFIX}currentSession`;
|
|
591
1092
|
const KEY_CURRENT_URL = `${SOLID_CLIENT_AUTHN_KEY_PREFIX}currentUrl`;
|
|
592
1093
|
|
|
1094
|
+
//
|
|
1095
|
+
// Copyright Inrupt Inc.
|
|
1096
|
+
//
|
|
1097
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1098
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
1099
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
1100
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
1101
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
1102
|
+
// subject to the following conditions:
|
|
1103
|
+
//
|
|
1104
|
+
// The above copyright notice and this permission notice shall be included in
|
|
1105
|
+
// all copies or substantial portions of the Software.
|
|
1106
|
+
//
|
|
1107
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
1108
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
1109
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
1110
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
1111
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
1112
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1113
|
+
//
|
|
593
1114
|
async function silentlyAuthenticate(sessionId, clientAuthn, session) {
|
|
594
1115
|
var _a;
|
|
595
1116
|
const storedSessionInfo = await clientAuthn.validateCurrentSession(sessionId);
|
|
596
1117
|
if (storedSessionInfo !== null) {
|
|
1118
|
+
// It can be really useful to save the user's current browser location,
|
|
1119
|
+
// so that we can restore it after completing the silent authentication
|
|
1120
|
+
// on incoming redirect. This way, the user is eventually redirected back
|
|
1121
|
+
// to the page they were on and not to the app's redirect page.
|
|
597
1122
|
window.localStorage.setItem(KEY_CURRENT_URL, window.location.href);
|
|
598
1123
|
await clientAuthn.login({
|
|
599
1124
|
sessionId,
|
|
@@ -611,21 +1136,68 @@ async function silentlyAuthenticate(sessionId, clientAuthn, session) {
|
|
|
611
1136
|
function isLoggedIn(sessionInfo) {
|
|
612
1137
|
return !!(sessionInfo === null || sessionInfo === void 0 ? void 0 : sessionInfo.isLoggedIn);
|
|
613
1138
|
}
|
|
1139
|
+
/**
|
|
1140
|
+
* 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.
|
|
1141
|
+
*/
|
|
614
1142
|
class Session extends EventEmitter {
|
|
1143
|
+
/**
|
|
1144
|
+
* Session object constructor. Typically called as follows:
|
|
1145
|
+
*
|
|
1146
|
+
* ```typescript
|
|
1147
|
+
* const session = new Session();
|
|
1148
|
+
* ```
|
|
1149
|
+
*
|
|
1150
|
+
* See also [getDefaultSession](https://docs.inrupt.com/developer-tools/api/javascript/solid-client-authn-browser/functions.html#getdefaultsession).
|
|
1151
|
+
*
|
|
1152
|
+
* @param sessionOptions The options enabling the correct instantiation of
|
|
1153
|
+
* the session. Either both storages or clientAuthentication are required. For
|
|
1154
|
+
* more information, see {@link ISessionOptions}.
|
|
1155
|
+
* @param sessionId A string uniquely identifying the session.
|
|
1156
|
+
*
|
|
1157
|
+
*/
|
|
615
1158
|
constructor(sessionOptions = {}, sessionId = undefined) {
|
|
616
1159
|
super();
|
|
617
1160
|
this.tokenRequestInProgress = false;
|
|
1161
|
+
/**
|
|
1162
|
+
* Triggers the login process. Note that this method will redirect the user away from your app.
|
|
1163
|
+
*
|
|
1164
|
+
* @param options Parameter to customize the login behaviour. In particular, two options are mandatory: `options.oidcIssuer`, the user's identity provider, and `options.redirectUrl`, the URL to which the user will be redirected after logging in their identity provider.
|
|
1165
|
+
* @returns This method should redirect the user away from the app: it does not return anything. The login process is completed by {@linkcode handleIncomingRedirect}.
|
|
1166
|
+
*/
|
|
1167
|
+
// Define these functions as properties so that they don't get accidentally re-bound.
|
|
1168
|
+
// Isn't Javascript fun?
|
|
618
1169
|
this.login = async (options) => {
|
|
619
1170
|
var _a;
|
|
620
1171
|
await this.clientAuthentication.login({
|
|
621
1172
|
sessionId: this.info.sessionId,
|
|
622
1173
|
...options,
|
|
1174
|
+
// Defaults the token type to DPoP
|
|
623
1175
|
tokenType: (_a = options.tokenType) !== null && _a !== void 0 ? _a : "DPoP",
|
|
624
1176
|
}, this.events);
|
|
1177
|
+
// `login` redirects the user away from the app,
|
|
1178
|
+
// so unless it throws an error, there is no code that should run afterwards
|
|
1179
|
+
// (since there is no "after" in the lifetime of the script).
|
|
1180
|
+
// Hence, this Promise never resolves:
|
|
625
1181
|
return new Promise(() => { });
|
|
626
1182
|
};
|
|
1183
|
+
/**
|
|
1184
|
+
* Fetches data using available login information. If the user is not logged in, this will behave as a regular `fetch`. The signature of this method is identical to the [canonical `fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
|
1185
|
+
*
|
|
1186
|
+
* @param url The URL from which data should be fetched.
|
|
1187
|
+
* @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/).
|
|
1188
|
+
*/
|
|
627
1189
|
this.fetch = (url, init) => this.clientAuthentication.fetch(url, init);
|
|
1190
|
+
/**
|
|
1191
|
+
* An internal logout function, to control whether or not the logout signal
|
|
1192
|
+
* should be sent, i.e. if the logout was user-initiated or is the result of
|
|
1193
|
+
* an external event.
|
|
1194
|
+
*
|
|
1195
|
+
* @hidden
|
|
1196
|
+
*/
|
|
628
1197
|
this.internalLogout = async (emitSignal, options) => {
|
|
1198
|
+
// Clearing this value means that silent refresh will no longer be attempted.
|
|
1199
|
+
// In particular, in the case of a silent authentication error it prevents
|
|
1200
|
+
// from getting stuck in an authentication retries loop.
|
|
629
1201
|
window.localStorage.removeItem(KEY_CURRENT_SESSION);
|
|
630
1202
|
await this.clientAuthentication.logout(this.info.sessionId, options);
|
|
631
1203
|
this.info.isLoggedIn = false;
|
|
@@ -633,7 +1205,50 @@ class Session extends EventEmitter {
|
|
|
633
1205
|
this.events.emit(EVENTS.LOGOUT);
|
|
634
1206
|
}
|
|
635
1207
|
};
|
|
1208
|
+
/**
|
|
1209
|
+
* Logs the user out of the application.
|
|
1210
|
+
*
|
|
1211
|
+
* There are 2 types of logout supported by this library,
|
|
1212
|
+
* `app` logout and `idp` logout.
|
|
1213
|
+
*
|
|
1214
|
+
* App logout will log the user out within the application
|
|
1215
|
+
* by clearing any session data from the browser. It does
|
|
1216
|
+
* not log the user out of their Solid identity provider,
|
|
1217
|
+
* and should not redirect the user away.
|
|
1218
|
+
* App logout can be performed as follows:
|
|
1219
|
+
* ```typescript
|
|
1220
|
+
* await session.logout({ logoutType: 'app' });
|
|
1221
|
+
* ```
|
|
1222
|
+
*
|
|
1223
|
+
* IDP logout will log the user out of their Solid identity provider,
|
|
1224
|
+
* and will redirect the user away from the application to do so. In order
|
|
1225
|
+
* for users to be redirected back to `postLogoutUrl` you MUST include the
|
|
1226
|
+
* `postLogoutUrl` value in the `post_logout_redirect_uris` field in the
|
|
1227
|
+
* [Client ID Document](https://docs.inrupt.com/ess/latest/security/authentication/#client-identifier-client-id).
|
|
1228
|
+
* IDP logout can be performed as follows:
|
|
1229
|
+
* ```typescript
|
|
1230
|
+
* await session.logout({
|
|
1231
|
+
* logoutType: 'idp',
|
|
1232
|
+
* // An optional URL to redirect to after logout has completed;
|
|
1233
|
+
* // this MUST match a logout URL listed in the Client ID Document
|
|
1234
|
+
* // of the application that is logged in.
|
|
1235
|
+
* // If the application is logged in with a Client ID that is not
|
|
1236
|
+
* // a URI dereferencing to a Client ID Document then users will
|
|
1237
|
+
* // not be redirected back to the `postLogoutUrl` after logout.
|
|
1238
|
+
* postLogoutUrl: 'https://example.com/logout',
|
|
1239
|
+
* // An optional value to be included in the query parameters
|
|
1240
|
+
* // when the IDP provider redirects the user to the postLogoutRedirectUrl.
|
|
1241
|
+
* state: "my-state"
|
|
1242
|
+
* });
|
|
1243
|
+
* ```
|
|
1244
|
+
*/
|
|
636
1245
|
this.logout = async (options) => this.internalLogout(true, options);
|
|
1246
|
+
/**
|
|
1247
|
+
* Completes the login process by processing the information provided by the
|
|
1248
|
+
* Solid identity provider through redirect.
|
|
1249
|
+
*
|
|
1250
|
+
* @param options See {@see IHandleIncomingRedirectOptions}.
|
|
1251
|
+
*/
|
|
637
1252
|
this.handleIncomingRedirect = async (inputOptions = {}) => {
|
|
638
1253
|
var _a;
|
|
639
1254
|
if (this.info.isLoggedIn) {
|
|
@@ -650,17 +1265,33 @@ class Session extends EventEmitter {
|
|
|
650
1265
|
this.setSessionInfo(sessionInfo);
|
|
651
1266
|
const currentUrl = window.localStorage.getItem(KEY_CURRENT_URL);
|
|
652
1267
|
if (currentUrl === null) {
|
|
1268
|
+
// The login event can only be triggered **after** the user has been
|
|
1269
|
+
// redirected from the IdP with access and ID tokens.
|
|
653
1270
|
this.events.emit(EVENTS.LOGIN);
|
|
654
1271
|
}
|
|
655
1272
|
else {
|
|
1273
|
+
// If an URL is stored in local storage, we are being logged in after a
|
|
1274
|
+
// silent authentication, so remove our currently stored URL location
|
|
1275
|
+
// to clean up our state now that we are completing the re-login process.
|
|
656
1276
|
window.localStorage.removeItem(KEY_CURRENT_URL);
|
|
657
1277
|
this.events.emit(EVENTS.SESSION_RESTORED, currentUrl);
|
|
658
1278
|
}
|
|
659
1279
|
}
|
|
660
1280
|
else if (options.restorePreviousSession === true) {
|
|
1281
|
+
// Silent authentication happens after a refresh, which means there are no
|
|
1282
|
+
// OAuth params in the current location IRI. It can only succeed if a session
|
|
1283
|
+
// was previously logged in, in which case its ID will be present with a known
|
|
1284
|
+
// identifier in local storage.
|
|
1285
|
+
// Check if we have a locally stored session ID...
|
|
661
1286
|
const storedSessionId = window.localStorage.getItem(KEY_CURRENT_SESSION);
|
|
1287
|
+
// ...if not, then there is no ID token, and so silent authentication cannot happen, but
|
|
1288
|
+
// if we do have a stored session ID, attempt to re-authenticate now silently.
|
|
662
1289
|
if (storedSessionId !== null) {
|
|
663
1290
|
const attemptedSilentAuthentication = await silentlyAuthenticate(storedSessionId, this.clientAuthentication, this);
|
|
1291
|
+
// At this point, we know that the main window will imminently be redirected.
|
|
1292
|
+
// However, this redirect is asynchronous and there is no way to halt execution
|
|
1293
|
+
// until it happens precisely. That's why the current Promise simply does not
|
|
1294
|
+
// resolve.
|
|
664
1295
|
if (attemptedSilentAuthentication) {
|
|
665
1296
|
return new Promise(() => { });
|
|
666
1297
|
}
|
|
@@ -669,6 +1300,10 @@ class Session extends EventEmitter {
|
|
|
669
1300
|
this.tokenRequestInProgress = false;
|
|
670
1301
|
return sessionInfo;
|
|
671
1302
|
};
|
|
1303
|
+
// Until Session no longer implements EventEmitter, this.events is just a proxy
|
|
1304
|
+
// to this (with some interface filtering). When we make the breaking change,
|
|
1305
|
+
// this.events will be a regular EventEmitter (implementing ISessionEventEmitter):
|
|
1306
|
+
// this.events = new EventEmitter();
|
|
672
1307
|
this.events = new Proxy(this, buildProxyHandler(Session.prototype, "events only implements ISessionEventListener"));
|
|
673
1308
|
if (sessionOptions.clientAuthentication) {
|
|
674
1309
|
this.clientAuthentication = sessionOptions.clientAuthentication;
|
|
@@ -695,22 +1330,63 @@ class Session extends EventEmitter {
|
|
|
695
1330
|
isLoggedIn: false,
|
|
696
1331
|
};
|
|
697
1332
|
}
|
|
1333
|
+
// When a session is logged in, we want to track its ID in local storage to
|
|
1334
|
+
// enable silent refresh. The current session ID specifically stored in 'localStorage'
|
|
1335
|
+
// (as opposed to using our storage abstraction layer) because it is only
|
|
1336
|
+
// used in a browser-specific mechanism.
|
|
698
1337
|
this.events.on(EVENTS.LOGIN, () => window.localStorage.setItem(KEY_CURRENT_SESSION, this.info.sessionId));
|
|
699
1338
|
this.events.on(EVENTS.SESSION_EXPIRED, () => this.internalLogout(false));
|
|
700
1339
|
this.events.on(EVENTS.ERROR, () => this.internalLogout(false));
|
|
701
1340
|
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Register a callback function to be called when a user completes login.
|
|
1343
|
+
*
|
|
1344
|
+
* The callback is called when {@link handleIncomingRedirect} completes successfully.
|
|
1345
|
+
*
|
|
1346
|
+
* @param callback The function called when a user completes login.
|
|
1347
|
+
* @deprecated Prefer session.events.on(EVENTS.LOGIN, callback)
|
|
1348
|
+
*/
|
|
702
1349
|
onLogin(callback) {
|
|
703
1350
|
this.events.on(EVENTS.LOGIN, callback);
|
|
704
1351
|
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Register a callback function to be called when a user logs out:
|
|
1354
|
+
*
|
|
1355
|
+
* @param callback The function called when a user completes logout.
|
|
1356
|
+
* @deprecated Prefer session.events.on(EVENTS.LOGOUT, callback)
|
|
1357
|
+
*/
|
|
705
1358
|
onLogout(callback) {
|
|
706
1359
|
this.events.on(EVENTS.LOGOUT, callback);
|
|
707
1360
|
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Register a callback function to be called when a user logs out:
|
|
1363
|
+
*
|
|
1364
|
+
* @param callback The function called when an error occurs.
|
|
1365
|
+
* @since 1.11.0
|
|
1366
|
+
* @deprecated Prefer session.events.on(EVENTS.ERROR, callback)
|
|
1367
|
+
*/
|
|
708
1368
|
onError(callback) {
|
|
709
1369
|
this.events.on(EVENTS.ERROR, callback);
|
|
710
1370
|
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Register a callback function to be called when a session is restored.
|
|
1373
|
+
*
|
|
1374
|
+
* Note: the callback will be called with the saved value of the 'current URL'
|
|
1375
|
+
* at the time the session was restored.
|
|
1376
|
+
*
|
|
1377
|
+
* @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.
|
|
1378
|
+
* @deprecated Prefer session.events.on(EVENTS.SESSION_RESTORED, callback)
|
|
1379
|
+
*/
|
|
711
1380
|
onSessionRestore(callback) {
|
|
712
1381
|
this.events.on(EVENTS.SESSION_RESTORED, callback);
|
|
713
1382
|
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Register a callback that runs when the session expires and can no longer
|
|
1385
|
+
* make authenticated requests, but following a user logout.
|
|
1386
|
+
* @param callback The function that runs on session expiration.
|
|
1387
|
+
* @since 1.11.0
|
|
1388
|
+
* @deprecated Prefer session.events.on(EVENTS.SESSION_EXPIRED, callback)
|
|
1389
|
+
*/
|
|
714
1390
|
onSessionExpiration(callback) {
|
|
715
1391
|
this.events.on(EVENTS.SESSION_EXPIRED, callback);
|
|
716
1392
|
}
|
|
@@ -725,41 +1401,132 @@ class Session extends EventEmitter {
|
|
|
725
1401
|
}
|
|
726
1402
|
}
|
|
727
1403
|
|
|
1404
|
+
//
|
|
1405
|
+
// Copyright Inrupt Inc.
|
|
1406
|
+
//
|
|
1407
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1408
|
+
// of this software and associated documentation files (the "Software"), to deal in
|
|
1409
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
1410
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
1411
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
1412
|
+
// subject to the following conditions:
|
|
1413
|
+
//
|
|
1414
|
+
// The above copyright notice and this permission notice shall be included in
|
|
1415
|
+
// all copies or substantial portions of the Software.
|
|
1416
|
+
//
|
|
1417
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
1418
|
+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
1419
|
+
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
1420
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
1421
|
+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
1422
|
+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1423
|
+
//
|
|
728
1424
|
let defaultSession;
|
|
1425
|
+
/**
|
|
1426
|
+
* Obtain the {@link Session} used when not explicitly instantiating one yourself.
|
|
1427
|
+
*
|
|
1428
|
+
* When using the top-level exports {@link fetch}, {@link login}, {@link logout},
|
|
1429
|
+
* {@link handleIncomingRedirect}, {@link onLogin} and {@link onLogout}, these apply to an
|
|
1430
|
+
* implicitly-instantiated {@link Session}.
|
|
1431
|
+
* This function returns a reference to that Session in order to obtain e.g. the current user's
|
|
1432
|
+
* WebID.
|
|
1433
|
+
* @since 1.3.0
|
|
1434
|
+
*/
|
|
729
1435
|
function getDefaultSession() {
|
|
730
1436
|
if (typeof defaultSession === "undefined") {
|
|
731
1437
|
defaultSession = new Session();
|
|
732
1438
|
}
|
|
733
1439
|
return defaultSession;
|
|
734
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* This function's signature is equal to `window.fetch`, but if the current user is authenticated
|
|
1443
|
+
* (see [[login]] and [[handleIncomingRedirect]]), requests made using it will include that user's
|
|
1444
|
+
* credentials. If not, this will behave just like the regular `window.fetch`.
|
|
1445
|
+
*
|
|
1446
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch}
|
|
1447
|
+
* @since 1.3.0
|
|
1448
|
+
*/
|
|
1449
|
+
/* eslint-disable-next-line no-shadow */
|
|
735
1450
|
const fetch = (...args) => {
|
|
736
1451
|
const session = getDefaultSession();
|
|
737
1452
|
return session.fetch(...args);
|
|
738
1453
|
};
|
|
1454
|
+
/**
|
|
1455
|
+
* Triggers the login process. Note that this method will redirect the user away from your app.
|
|
1456
|
+
*
|
|
1457
|
+
* @param options Parameter to customize the login behaviour. In particular, two options are mandatory: `options.oidcIssuer`, the user's identity provider, and `options.redirectUrl`, the URL to which the user will be redirected after logging in their identity provider.
|
|
1458
|
+
* @returns This method should redirect the user away from the app: it does not return anything. The login process is completed by [[handleIncomingRedirect]].
|
|
1459
|
+
* @since 1.3.0
|
|
1460
|
+
*/
|
|
739
1461
|
const login = (...args) => {
|
|
740
1462
|
const session = getDefaultSession();
|
|
741
1463
|
return session.login(...args);
|
|
742
1464
|
};
|
|
1465
|
+
/**
|
|
1466
|
+
* Logs the user out of the application.
|
|
1467
|
+
*
|
|
1468
|
+
* By default this does not log the user out of their Solid identity provider.
|
|
1469
|
+
* In order to do so, you must set the logoutType to `idp`. For usage details
|
|
1470
|
+
* see {@link Session.logout}.
|
|
1471
|
+
*
|
|
1472
|
+
* @since 1.3.0
|
|
1473
|
+
*/
|
|
743
1474
|
const logout = (...args) => {
|
|
744
1475
|
const session = getDefaultSession();
|
|
745
1476
|
return session.logout(...args);
|
|
746
1477
|
};
|
|
1478
|
+
/**
|
|
1479
|
+
* Completes the login process by processing the information provided by the Solid identity provider through redirect.
|
|
1480
|
+
*
|
|
1481
|
+
* @param url The URL of the page handling the redirect, including the query parameters — these contain the information to process the login.
|
|
1482
|
+
* @since 1.3.0
|
|
1483
|
+
*/
|
|
747
1484
|
const handleIncomingRedirect = (...args) => {
|
|
748
1485
|
const session = getDefaultSession();
|
|
749
1486
|
return session.handleIncomingRedirect(...args);
|
|
750
1487
|
};
|
|
1488
|
+
/**
|
|
1489
|
+
* Register a callback function to be called when a user completes login.
|
|
1490
|
+
*
|
|
1491
|
+
* The callback is called when {@link handleIncomingRedirect} completes successfully.
|
|
1492
|
+
* @since 1.3.0
|
|
1493
|
+
*
|
|
1494
|
+
* @param callback The function called when a user completes login.
|
|
1495
|
+
* @deprecated Prefer events.on(EVENTS.LOGIN, callback)
|
|
1496
|
+
|
|
1497
|
+
*/
|
|
751
1498
|
const onLogin = (...args) => {
|
|
752
1499
|
const session = getDefaultSession();
|
|
753
1500
|
return session.onLogin(...args);
|
|
754
1501
|
};
|
|
1502
|
+
/**
|
|
1503
|
+
* Register a callback function to be called when a user logs out:
|
|
1504
|
+
*
|
|
1505
|
+
* @param callback The function called when a user completes logout.
|
|
1506
|
+
* @since 1.3.0
|
|
1507
|
+
* @deprecated Prefer events.on(EVENTS.LOGOUT, callback)
|
|
1508
|
+
*
|
|
1509
|
+
*/
|
|
755
1510
|
const onLogout = (...args) => {
|
|
756
1511
|
const session = getDefaultSession();
|
|
757
1512
|
return session.onLogout(...args);
|
|
758
1513
|
};
|
|
1514
|
+
/**
|
|
1515
|
+
* Register a callback function to be called when a session is restored:
|
|
1516
|
+
*
|
|
1517
|
+
* @param callback The function called when a session is restored.
|
|
1518
|
+
* @since 1.3.0
|
|
1519
|
+
* @deprecated Prefer events.on(EVENTS.SESSION_RESTORED, callback)
|
|
1520
|
+
*/
|
|
759
1521
|
const onSessionRestore = (...args) => {
|
|
760
1522
|
const session = getDefaultSession();
|
|
761
1523
|
return session.onSessionRestore(...args);
|
|
762
1524
|
};
|
|
1525
|
+
/**
|
|
1526
|
+
* {@link SessionEventEmitter} instance to subscribe to events by the default session.
|
|
1527
|
+
*
|
|
1528
|
+
* @since 1.14.0
|
|
1529
|
+
*/
|
|
763
1530
|
const events = () => {
|
|
764
1531
|
return getDefaultSession().events;
|
|
765
1532
|
};
|