@inrupt/solid-client-authn-browser 2.3.0 → 2.4.1

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/index.js CHANGED
@@ -62,82 +62,77 @@ class StorageUtilityBrowser extends solidClientAuthnCore.StorageUtility {
62
62
  * @hidden
63
63
  */
64
64
  class ClientAuthentication extends solidClientAuthnCore.ClientAuthentication {
65
- constructor() {
66
- super(...arguments);
67
- // Define these functions as properties so that they don't get accidentally re-bound.
68
- // Isn't Javascript fun?
69
- this.login = async (options, eventEmitter) => {
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).
65
+ // Define these functions as properties so that they don't get accidentally re-bound.
66
+ // Isn't Javascript fun?
67
+ login = async (options, eventEmitter) => {
68
+ // In order to get a clean start, make sure that the session is logged out
69
+ // on login, except when doing a silent login so that Dynamic Client information
70
+ // is preserved.
71
+ if (options.prompt !== "none") {
77
72
  await this.sessionInfoManager.clear(options.sessionId);
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 : oidcClientExt.normalizeCallbackUrl(window.location.href);
85
- if (!solidClientAuthnCore.isValidRedirectUrl(redirectUrl)) {
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').`);
87
- }
88
- await this.loginHandler.handle({
89
- ...options,
90
- redirectUrl,
91
- // If no clientName is provided, the clientId may be used instead.
92
- clientName: (_b = options.clientName) !== null && _b !== void 0 ? _b : options.clientId,
93
- eventEmitter,
94
- });
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.
100
- this.validateCurrentSession = async (currentSessionId) => {
101
- const sessionInfo = await this.sessionInfoManager.get(currentSessionId);
102
- if (sessionInfo === undefined ||
103
- sessionInfo.clientAppId === undefined ||
104
- sessionInfo.issuer === undefined) {
105
- return null;
106
- }
107
- return sessionInfo;
108
- };
109
- this.handleIncomingRedirect = async (url, eventEmitter) => {
110
- try {
111
- const redirectInfo = await this.redirectHandler.handle(url, eventEmitter, undefined);
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.
116
- this.fetch = redirectInfo.fetch.bind(window);
117
- this.boundLogout = redirectInfo.getLogoutUrl;
118
- // Strip the oauth params:
119
- await this.cleanUrlAfterRedirect(url);
120
- return {
121
- isLoggedIn: redirectInfo.isLoggedIn,
122
- webId: redirectInfo.webId,
123
- sessionId: redirectInfo.sessionId,
124
- expirationDate: redirectInfo.expirationDate,
125
- clientAppId: redirectInfo.clientAppId,
126
- };
127
- }
128
- catch (err) {
129
- // Strip the oauth params:
130
- await this.cleanUrlAfterRedirect(url);
131
- // FIXME: EVENTS.ERROR should be errorCode, errorDescription
132
- //
133
- // I'm not sure if "redirect" is a good error code, and in theory `err`
134
- // maybe an Error object and not a string; Maybe we want to just hardcode
135
- // a description instead?
136
- eventEmitter.emit(solidClientAuthnCore.EVENTS.ERROR, "redirect", err);
137
- return undefined;
138
- }
139
- };
140
- }
73
+ }
74
+ // In the case of the user hitting the 'back' button in their browser, they
75
+ // could return to a previous redirect URL that contains OIDC params that
76
+ // are now longer valid. To be safe, strip relevant params now.
77
+ // If the user is providing a redirect IRI, it should not be modified, so
78
+ // normalization only applies if we default to the current location (which is
79
+ // a bad practice and should be discouraged).
80
+ const redirectUrl = options.redirectUrl ?? oidcClientExt.normalizeCallbackUrl(window.location.href);
81
+ if (!solidClientAuthnCore.isValidRedirectUrl(redirectUrl)) {
82
+ 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').`);
83
+ }
84
+ await this.loginHandler.handle({
85
+ ...options,
86
+ redirectUrl,
87
+ // If no clientName is provided, the clientId may be used instead.
88
+ clientName: options.clientName ?? options.clientId,
89
+ eventEmitter,
90
+ });
91
+ };
92
+ // Collects session information from storage, and returns them. Returns null
93
+ // if the expected information cannot be found.
94
+ // Note that the ID token is not stored, which means the session information
95
+ // cannot be validated at this point.
96
+ validateCurrentSession = async (currentSessionId) => {
97
+ const sessionInfo = await this.sessionInfoManager.get(currentSessionId);
98
+ if (sessionInfo === undefined ||
99
+ sessionInfo.clientAppId === undefined ||
100
+ sessionInfo.issuer === undefined) {
101
+ return null;
102
+ }
103
+ return sessionInfo;
104
+ };
105
+ handleIncomingRedirect = async (url, eventEmitter) => {
106
+ try {
107
+ const redirectInfo = await this.redirectHandler.handle(url, eventEmitter, undefined);
108
+ // The `FallbackRedirectHandler` directly returns the global `fetch` for
109
+ // his value, so we should ensure it's bound to `window` rather than to
110
+ // ClientAuthentication, to avoid the following error:
111
+ // > 'fetch' called on an object that does not implement interface Window.
112
+ this.fetch = redirectInfo.fetch.bind(window);
113
+ this.boundLogout = redirectInfo.getLogoutUrl;
114
+ // Strip the oauth params:
115
+ await this.cleanUrlAfterRedirect(url);
116
+ return {
117
+ isLoggedIn: redirectInfo.isLoggedIn,
118
+ webId: redirectInfo.webId,
119
+ sessionId: redirectInfo.sessionId,
120
+ expirationDate: redirectInfo.expirationDate,
121
+ clientAppId: redirectInfo.clientAppId,
122
+ };
123
+ }
124
+ catch (err) {
125
+ // Strip the oauth params:
126
+ await this.cleanUrlAfterRedirect(url);
127
+ // FIXME: EVENTS.ERROR should be errorCode, errorDescription
128
+ //
129
+ // I'm not sure if "redirect" is a good error code, and in theory `err`
130
+ // maybe an Error object and not a string; Maybe we want to just hardcode
131
+ // a description instead?
132
+ eventEmitter.emit(solidClientAuthnCore.EVENTS.ERROR, "redirect", err);
133
+ return undefined;
134
+ }
135
+ };
141
136
  async cleanUrlAfterRedirect(url) {
142
137
  const cleanedUpUrl = solidClientAuthnCore.removeOpenIdParams(url).href;
143
138
  // Remove OAuth-specific query params (since the login flow finishes with
@@ -190,6 +185,10 @@ function hasRedirectUrl(options) {
190
185
  * @hidden
191
186
  */
192
187
  class OidcLoginHandler {
188
+ storageUtility;
189
+ oidcHandler;
190
+ issuerConfigFetcher;
191
+ clientRegistrar;
193
192
  constructor(storageUtility, oidcHandler, issuerConfigFetcher, clientRegistrar) {
194
193
  this.storageUtility = storageUtility;
195
194
  this.oidcHandler = oidcHandler;
@@ -259,7 +258,6 @@ class OidcLoginHandler {
259
258
  */
260
259
  class AuthorizationCodeWithPkceOidcHandler extends solidClientAuthnCore.AuthorizationCodeWithPkceOidcHandlerBase {
261
260
  async handle(oidcLoginOptions) {
262
- var _a;
263
261
  /* eslint-disable camelcase */
264
262
  const oidcOptions = {
265
263
  authority: oidcLoginOptions.issuer.toString(),
@@ -274,7 +272,7 @@ class AuthorizationCodeWithPkceOidcHandler extends solidClientAuthnCore.Authoriz
274
272
  // profile referenced by the WebId.
275
273
  loadUserInfo: false,
276
274
  code_verifier: true,
277
- prompt: (_a = oidcLoginOptions.prompt) !== null && _a !== void 0 ? _a : "consent",
275
+ prompt: oidcLoginOptions.prompt ?? "consent",
278
276
  };
279
277
  /* eslint-enable camelcase */
280
278
  const oidcClientLibrary = new oidcClientExt.OidcClient(oidcOptions);
@@ -429,6 +427,7 @@ function processConfig(config) {
429
427
  * @hidden
430
428
  */
431
429
  class IssuerConfigFetcher {
430
+ storageUtility;
432
431
  constructor(storageUtility) {
433
432
  this.storageUtility = storageUtility;
434
433
  this.storageUtility = storageUtility;
@@ -547,7 +546,7 @@ class SessionInfoManager extends solidClientAuthnCore.SessionInfoManagerBase {
547
546
  clientAppId: clientId,
548
547
  clientAppSecret: clientSecret,
549
548
  // Default the token type to DPoP if unspecified.
550
- tokenType: tokenType !== null && tokenType !== void 0 ? tokenType : "DPoP",
549
+ tokenType: tokenType ?? "DPoP",
551
550
  };
552
551
  }
553
552
  /**
@@ -630,6 +629,11 @@ class FallbackRedirectHandler {
630
629
  * @hidden
631
630
  */
632
631
  class AuthCodeRedirectHandler {
632
+ storageUtility;
633
+ sessionInfoManager;
634
+ issuerConfigFetcher;
635
+ clientRegistrar;
636
+ tokerRefresher;
633
637
  constructor(storageUtility, sessionInfoManager, issuerConfigFetcher, clientRegistrar, tokerRefresher) {
634
638
  this.storageUtility = storageUtility;
635
639
  this.sessionInfoManager = sessionInfoManager;
@@ -693,7 +697,7 @@ class AuthCodeRedirectHandler {
693
697
  tokenRefresher: this.tokerRefresher,
694
698
  };
695
699
  }
696
- const authFetch = await solidClientAuthnCore.buildAuthenticatedFetch(tokens.accessToken, {
700
+ const authFetch = solidClientAuthnCore.buildAuthenticatedFetch(tokens.accessToken, {
697
701
  dpopKey: tokens.dpopKey,
698
702
  refreshOptions,
699
703
  eventEmitter,
@@ -845,19 +849,23 @@ class Redirector {
845
849
  * @hidden
846
850
  */
847
851
  class ClientRegistrar {
852
+ storageUtility;
848
853
  constructor(storageUtility) {
849
854
  this.storageUtility = storageUtility;
850
855
  this.storageUtility = storageUtility;
851
856
  }
852
857
  async getClient(options, issuerConfig) {
853
858
  // If client secret and/or client id are stored in storage, use those.
854
- const [storedClientId, storedClientSecret, storedClientName, storedClientType,] = await Promise.all([
859
+ const [storedClientId, storedClientSecret, expiresAt, storedClientName, storedClientType,] = await Promise.all([
855
860
  this.storageUtility.getForUser(options.sessionId, "clientId", {
856
861
  secure: false,
857
862
  }),
858
863
  this.storageUtility.getForUser(options.sessionId, "clientSecret", {
859
864
  secure: false,
860
865
  }),
866
+ this.storageUtility.getForUser(options.sessionId, "expiresAt", {
867
+ secure: false,
868
+ }),
861
869
  this.storageUtility.getForUser(options.sessionId, "clientName", {
862
870
  secure: false,
863
871
  }),
@@ -865,14 +873,30 @@ class ClientRegistrar {
865
873
  secure: false,
866
874
  }),
867
875
  ]);
868
- if (storedClientId && solidClientAuthnCore.isKnownClientType(storedClientType)) {
869
- return {
870
- clientId: storedClientId,
871
- clientSecret: storedClientSecret,
872
- clientName: storedClientName,
873
- // Note: static clients are not applicable in a browser context.
874
- clientType: storedClientType,
875
- };
876
+ // -1 is used as a default to identify legacy cases when a value should
877
+ // have been stored, but wasn't. It will be treated as an expired client.
878
+ const expirationDate = expiresAt !== undefined ? Number.parseInt(expiresAt, 10) : -1;
879
+ // Expiration is only applicable to confidential clients.
880
+ // Note that Date.now() is in milliseconds, and expirationDate in seconds.
881
+ const expired = storedClientSecret !== undefined &&
882
+ Math.floor(Date.now() / 1000) > expirationDate;
883
+ if (storedClientId && solidClientAuthnCore.isKnownClientType(storedClientType) && !expired) {
884
+ return storedClientSecret !== undefined
885
+ ? {
886
+ clientId: storedClientId,
887
+ clientSecret: storedClientSecret,
888
+ clientName: storedClientName,
889
+ // Note: static clients are not applicable in a browser context.
890
+ clientType: "dynamic",
891
+ expiresAt: expirationDate,
892
+ }
893
+ : {
894
+ clientId: storedClientId,
895
+ clientName: storedClientName,
896
+ // Note: static clients are not applicable in a browser context.
897
+ clientType: storedClientType,
898
+ // The type assertion is required even though the type should match the declaration.
899
+ };
876
900
  }
877
901
  try {
878
902
  const registeredClient = await oidcClientExt.registerClient(options, issuerConfig);
@@ -881,8 +905,9 @@ class ClientRegistrar {
881
905
  clientId: registeredClient.clientId,
882
906
  clientType: "dynamic",
883
907
  };
884
- if (registeredClient.clientSecret) {
908
+ if (registeredClient.clientSecret !== undefined) {
885
909
  infoToSave.clientSecret = registeredClient.clientSecret;
910
+ infoToSave.expiresAt = String(registeredClient.expiresAt);
886
911
  }
887
912
  if (registeredClient.idTokenSignedResponseAlg) {
888
913
  infoToSave.idTokenSignedResponseAlg =
@@ -897,7 +922,7 @@ class ClientRegistrar {
897
922
  return registeredClient;
898
923
  }
899
924
  catch (error) {
900
- throw new Error(`Client registration failed: [${error}]`);
925
+ throw new Error(`Client registration failed.`, { cause: error });
901
926
  }
902
927
  }
903
928
  }
@@ -976,6 +1001,9 @@ class ErrorOidcHandler {
976
1001
  * @hidden
977
1002
  */
978
1003
  class TokenRefresher {
1004
+ storageUtility;
1005
+ issuerConfigFetcher;
1006
+ clientRegistrar;
979
1007
  constructor(storageUtility, issuerConfigFetcher, clientRegistrar) {
980
1008
  this.storageUtility = storageUtility;
981
1009
  this.issuerConfigFetcher = issuerConfigFetcher;
@@ -997,7 +1025,7 @@ class TokenRefresher {
997
1025
  }
998
1026
  const tokenSet = await oidcClientExt.refresh(refreshToken, oidcContext.issuerConfig, clientInfo, dpopKey);
999
1027
  if (tokenSet.refreshToken !== undefined) {
1000
- eventEmitter === null || eventEmitter === void 0 ? void 0 : eventEmitter.emit(solidClientAuthnCore.EVENTS.NEW_REFRESH_TOKEN, tokenSet.refreshToken);
1028
+ eventEmitter?.emit(solidClientAuthnCore.EVENTS.NEW_REFRESH_TOKEN, tokenSet.refreshToken);
1001
1029
  }
1002
1030
  return tokenSet;
1003
1031
  }
@@ -1093,7 +1121,6 @@ const KEY_CURRENT_URL = `${solidClientAuthnCore.SOLID_CLIENT_AUTHN_KEY_PREFIX}cu
1093
1121
  // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1094
1122
  //
1095
1123
  async function silentlyAuthenticate(sessionId, clientAuthn, session) {
1096
- var _a;
1097
1124
  const storedSessionInfo = await clientAuthn.validateCurrentSession(sessionId);
1098
1125
  if (storedSessionInfo !== null) {
1099
1126
  // It can be really useful to save the user's current browser location,
@@ -1108,19 +1135,31 @@ async function silentlyAuthenticate(sessionId, clientAuthn, session) {
1108
1135
  redirectUrl: storedSessionInfo.redirectUrl,
1109
1136
  clientId: storedSessionInfo.clientAppId,
1110
1137
  clientSecret: storedSessionInfo.clientAppSecret,
1111
- tokenType: (_a = storedSessionInfo.tokenType) !== null && _a !== void 0 ? _a : "DPoP",
1138
+ tokenType: storedSessionInfo.tokenType ?? "DPoP",
1112
1139
  }, session.events);
1113
1140
  return true;
1114
1141
  }
1115
1142
  return false;
1116
1143
  }
1117
1144
  function isLoggedIn(sessionInfo) {
1118
- return !!(sessionInfo === null || sessionInfo === void 0 ? void 0 : sessionInfo.isLoggedIn);
1145
+ return !!sessionInfo?.isLoggedIn;
1119
1146
  }
1120
1147
  /**
1121
1148
  * A {@link Session} object represents a user's session on an application. The session holds state, as it stores information enabling access to private resources after login for instance.
1122
1149
  */
1123
1150
  class Session {
1151
+ /**
1152
+ * Information regarding the current session.
1153
+ */
1154
+ info;
1155
+ /**
1156
+ * Session attribute exposing the EventEmitter interface, to listen on session
1157
+ * events such as login, logout, etc.
1158
+ * @since 1.15.0
1159
+ */
1160
+ events;
1161
+ clientAuthentication;
1162
+ tokenRequestInProgress = false;
1124
1163
  /**
1125
1164
  * Session object constructor. Typically called as follows:
1126
1165
  *
@@ -1137,149 +1176,6 @@ class Session {
1137
1176
  *
1138
1177
  */
1139
1178
  constructor(sessionOptions = {}, sessionId = undefined) {
1140
- this.tokenRequestInProgress = false;
1141
- /**
1142
- * Triggers the login process. Note that this method will redirect the user away from your app.
1143
- *
1144
- * @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.
1145
- * @returns This method should redirect the user away from the app: it does not return anything. The login process is completed by {@linkcode handleIncomingRedirect}.
1146
- */
1147
- // Define these functions as properties so that they don't get accidentally re-bound.
1148
- // Isn't Javascript fun?
1149
- this.login = async (options) => {
1150
- var _a;
1151
- await this.clientAuthentication.login({
1152
- sessionId: this.info.sessionId,
1153
- ...options,
1154
- // Defaults the token type to DPoP
1155
- tokenType: (_a = options.tokenType) !== null && _a !== void 0 ? _a : "DPoP",
1156
- }, this.events);
1157
- // `login` redirects the user away from the app,
1158
- // so unless it throws an error, there is no code that should run afterwards
1159
- // (since there is no "after" in the lifetime of the script).
1160
- // Hence, this Promise never resolves:
1161
- return new Promise(() => { });
1162
- };
1163
- /**
1164
- * 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).
1165
- *
1166
- * @param url The URL from which data should be fetched.
1167
- * @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/).
1168
- */
1169
- this.fetch = (url, init) => this.clientAuthentication.fetch(url, init);
1170
- /**
1171
- * An internal logout function, to control whether or not the logout signal
1172
- * should be sent, i.e. if the logout was user-initiated or is the result of
1173
- * an external event.
1174
- *
1175
- * @hidden
1176
- */
1177
- this.internalLogout = async (emitSignal, options) => {
1178
- // Clearing this value means that silent refresh will no longer be attempted.
1179
- // In particular, in the case of a silent authentication error it prevents
1180
- // from getting stuck in an authentication retries loop.
1181
- window.localStorage.removeItem(KEY_CURRENT_SESSION);
1182
- await this.clientAuthentication.logout(this.info.sessionId, options);
1183
- this.info.isLoggedIn = false;
1184
- if (emitSignal) {
1185
- this.events.emit(solidClientAuthnCore.EVENTS.LOGOUT);
1186
- }
1187
- };
1188
- /**
1189
- * Logs the user out of the application.
1190
- *
1191
- * There are 2 types of logout supported by this library,
1192
- * `app` logout and `idp` logout.
1193
- *
1194
- * App logout will log the user out within the application
1195
- * by clearing any session data from the browser. It does
1196
- * not log the user out of their Solid identity provider,
1197
- * and should not redirect the user away.
1198
- * App logout can be performed as follows:
1199
- * ```typescript
1200
- * await session.logout({ logoutType: 'app' });
1201
- * ```
1202
- *
1203
- * IDP logout will log the user out of their Solid identity provider,
1204
- * and will redirect the user away from the application to do so. In order
1205
- * for users to be redirected back to `postLogoutUrl` you MUST include the
1206
- * `postLogoutUrl` value in the `post_logout_redirect_uris` field in the
1207
- * [Client ID Document](https://docs.inrupt.com/ess/latest/security/authentication/#client-identifier-client-id).
1208
- * IDP logout can be performed as follows:
1209
- * ```typescript
1210
- * await session.logout({
1211
- * logoutType: 'idp',
1212
- * // An optional URL to redirect to after logout has completed;
1213
- * // this MUST match a logout URL listed in the Client ID Document
1214
- * // of the application that is logged in.
1215
- * // If the application is logged in with a Client ID that is not
1216
- * // a URI dereferencing to a Client ID Document then users will
1217
- * // not be redirected back to the `postLogoutUrl` after logout.
1218
- * postLogoutUrl: 'https://example.com/logout',
1219
- * // An optional value to be included in the query parameters
1220
- * // when the IDP provider redirects the user to the postLogoutRedirectUrl.
1221
- * state: "my-state"
1222
- * });
1223
- * ```
1224
- */
1225
- this.logout = async (options) => this.internalLogout(true, options);
1226
- /**
1227
- * Completes the login process by processing the information provided by the
1228
- * Solid identity provider through redirect.
1229
- *
1230
- * @param options See {@link IHandleIncomingRedirectOptions}.
1231
- */
1232
- this.handleIncomingRedirect = async (inputOptions = {}) => {
1233
- var _a;
1234
- if (this.info.isLoggedIn) {
1235
- return this.info;
1236
- }
1237
- if (this.tokenRequestInProgress) {
1238
- return undefined;
1239
- }
1240
- const options = typeof inputOptions === "string" ? { url: inputOptions } : inputOptions;
1241
- const url = (_a = options.url) !== null && _a !== void 0 ? _a : window.location.href;
1242
- this.tokenRequestInProgress = true;
1243
- const sessionInfo = await this.clientAuthentication.handleIncomingRedirect(url, this.events);
1244
- if (isLoggedIn(sessionInfo)) {
1245
- this.setSessionInfo(sessionInfo);
1246
- const currentUrl = window.localStorage.getItem(KEY_CURRENT_URL);
1247
- if (currentUrl === null) {
1248
- // The login event can only be triggered **after** the user has been
1249
- // redirected from the IdP with access and ID tokens.
1250
- this.events.emit(solidClientAuthnCore.EVENTS.LOGIN);
1251
- }
1252
- else {
1253
- // If an URL is stored in local storage, we are being logged in after a
1254
- // silent authentication, so remove our currently stored URL location
1255
- // to clean up our state now that we are completing the re-login process.
1256
- window.localStorage.removeItem(KEY_CURRENT_URL);
1257
- this.events.emit(solidClientAuthnCore.EVENTS.SESSION_RESTORED, currentUrl);
1258
- }
1259
- }
1260
- else if (options.restorePreviousSession === true) {
1261
- // Silent authentication happens after a refresh, which means there are no
1262
- // OAuth params in the current location IRI. It can only succeed if a session
1263
- // was previously logged in, in which case its ID will be present with a known
1264
- // identifier in local storage.
1265
- // Check if we have a locally stored session ID...
1266
- const storedSessionId = window.localStorage.getItem(KEY_CURRENT_SESSION);
1267
- // ...if not, then there is no ID token, and so silent authentication cannot happen, but
1268
- // if we do have a stored session ID, attempt to re-authenticate now silently.
1269
- if (storedSessionId !== null) {
1270
- const attemptedSilentAuthentication = await silentlyAuthenticate(storedSessionId, this.clientAuthentication, this);
1271
- // At this point, we know that the main window will imminently be redirected.
1272
- // However, this redirect is asynchronous and there is no way to halt execution
1273
- // until it happens precisely. That's why the current Promise simply does not
1274
- // resolve.
1275
- if (attemptedSilentAuthentication) {
1276
- return new Promise(() => { });
1277
- }
1278
- }
1279
- }
1280
- this.tokenRequestInProgress = false;
1281
- return sessionInfo;
1282
- };
1283
1179
  this.events = new EventEmitter();
1284
1180
  if (sessionOptions.clientAuthentication) {
1285
1181
  this.clientAuthentication = sessionOptions.clientAuthentication;
@@ -1303,7 +1199,7 @@ class Session {
1303
1199
  }
1304
1200
  else {
1305
1201
  this.info = {
1306
- sessionId: sessionId !== null && sessionId !== void 0 ? sessionId : uuid.v4(),
1202
+ sessionId: sessionId ?? uuid.v4(),
1307
1203
  isLoggedIn: false,
1308
1204
  };
1309
1205
  }
@@ -1315,6 +1211,146 @@ class Session {
1315
1211
  this.events.on(solidClientAuthnCore.EVENTS.SESSION_EXPIRED, () => this.internalLogout(false));
1316
1212
  this.events.on(solidClientAuthnCore.EVENTS.ERROR, () => this.internalLogout(false));
1317
1213
  }
1214
+ /**
1215
+ * Triggers the login process. Note that this method will redirect the user away from your app.
1216
+ *
1217
+ * @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.
1218
+ * @returns This method should redirect the user away from the app: it does not return anything. The login process is completed by {@linkcode handleIncomingRedirect}.
1219
+ */
1220
+ // Define these functions as properties so that they don't get accidentally re-bound.
1221
+ // Isn't Javascript fun?
1222
+ login = async (options) => {
1223
+ await this.clientAuthentication.login({
1224
+ sessionId: this.info.sessionId,
1225
+ ...options,
1226
+ // Defaults the token type to DPoP
1227
+ tokenType: options.tokenType ?? "DPoP",
1228
+ }, this.events);
1229
+ // `login` redirects the user away from the app,
1230
+ // so unless it throws an error, there is no code that should run afterwards
1231
+ // (since there is no "after" in the lifetime of the script).
1232
+ // Hence, this Promise never resolves:
1233
+ return new Promise(() => { });
1234
+ };
1235
+ /**
1236
+ * 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).
1237
+ *
1238
+ * @param url The URL from which data should be fetched.
1239
+ * @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/).
1240
+ */
1241
+ fetch = (url, init) => this.clientAuthentication.fetch(url, init);
1242
+ /**
1243
+ * An internal logout function, to control whether or not the logout signal
1244
+ * should be sent, i.e. if the logout was user-initiated or is the result of
1245
+ * an external event.
1246
+ *
1247
+ * @hidden
1248
+ */
1249
+ internalLogout = async (emitSignal, options) => {
1250
+ // Clearing this value means that silent refresh will no longer be attempted.
1251
+ // In particular, in the case of a silent authentication error it prevents
1252
+ // from getting stuck in an authentication retries loop.
1253
+ window.localStorage.removeItem(KEY_CURRENT_SESSION);
1254
+ await this.clientAuthentication.logout(this.info.sessionId, options);
1255
+ this.info.isLoggedIn = false;
1256
+ if (emitSignal) {
1257
+ this.events.emit(solidClientAuthnCore.EVENTS.LOGOUT);
1258
+ }
1259
+ };
1260
+ /**
1261
+ * Logs the user out of the application.
1262
+ *
1263
+ * There are 2 types of logout supported by this library,
1264
+ * `app` logout and `idp` logout.
1265
+ *
1266
+ * App logout will log the user out within the application
1267
+ * by clearing any session data from the browser. It does
1268
+ * not log the user out of their Solid identity provider,
1269
+ * and should not redirect the user away.
1270
+ * App logout can be performed as follows:
1271
+ * ```typescript
1272
+ * await session.logout({ logoutType: 'app' });
1273
+ * ```
1274
+ *
1275
+ * IDP logout will log the user out of their Solid identity provider,
1276
+ * and will redirect the user away from the application to do so. In order
1277
+ * for users to be redirected back to `postLogoutUrl` you MUST include the
1278
+ * `postLogoutUrl` value in the `post_logout_redirect_uris` field in the
1279
+ * [Client ID Document](https://docs.inrupt.com/ess/latest/security/authentication/#client-identifier-client-id).
1280
+ * IDP logout can be performed as follows:
1281
+ * ```typescript
1282
+ * await session.logout({
1283
+ * logoutType: 'idp',
1284
+ * // An optional URL to redirect to after logout has completed;
1285
+ * // this MUST match a logout URL listed in the Client ID Document
1286
+ * // of the application that is logged in.
1287
+ * // If the application is logged in with a Client ID that is not
1288
+ * // a URI dereferencing to a Client ID Document then users will
1289
+ * // not be redirected back to the `postLogoutUrl` after logout.
1290
+ * postLogoutUrl: 'https://example.com/logout',
1291
+ * // An optional value to be included in the query parameters
1292
+ * // when the IDP provider redirects the user to the postLogoutRedirectUrl.
1293
+ * state: "my-state"
1294
+ * });
1295
+ * ```
1296
+ */
1297
+ logout = async (options) => this.internalLogout(true, options);
1298
+ /**
1299
+ * Completes the login process by processing the information provided by the
1300
+ * Solid identity provider through redirect.
1301
+ *
1302
+ * @param options See {@link IHandleIncomingRedirectOptions}.
1303
+ */
1304
+ handleIncomingRedirect = async (inputOptions = {}) => {
1305
+ if (this.info.isLoggedIn) {
1306
+ return this.info;
1307
+ }
1308
+ if (this.tokenRequestInProgress) {
1309
+ return undefined;
1310
+ }
1311
+ const options = typeof inputOptions === "string" ? { url: inputOptions } : inputOptions;
1312
+ const url = options.url ?? window.location.href;
1313
+ this.tokenRequestInProgress = true;
1314
+ const sessionInfo = await this.clientAuthentication.handleIncomingRedirect(url, this.events);
1315
+ if (isLoggedIn(sessionInfo)) {
1316
+ this.setSessionInfo(sessionInfo);
1317
+ const currentUrl = window.localStorage.getItem(KEY_CURRENT_URL);
1318
+ if (currentUrl === null) {
1319
+ // The login event can only be triggered **after** the user has been
1320
+ // redirected from the IdP with access and ID tokens.
1321
+ this.events.emit(solidClientAuthnCore.EVENTS.LOGIN);
1322
+ }
1323
+ else {
1324
+ // If an URL is stored in local storage, we are being logged in after a
1325
+ // silent authentication, so remove our currently stored URL location
1326
+ // to clean up our state now that we are completing the re-login process.
1327
+ window.localStorage.removeItem(KEY_CURRENT_URL);
1328
+ this.events.emit(solidClientAuthnCore.EVENTS.SESSION_RESTORED, currentUrl);
1329
+ }
1330
+ }
1331
+ else if (options.restorePreviousSession === true) {
1332
+ // Silent authentication happens after a refresh, which means there are no
1333
+ // OAuth params in the current location IRI. It can only succeed if a session
1334
+ // was previously logged in, in which case its ID will be present with a known
1335
+ // identifier in local storage.
1336
+ // Check if we have a locally stored session ID...
1337
+ const storedSessionId = window.localStorage.getItem(KEY_CURRENT_SESSION);
1338
+ // ...if not, then there is no ID token, and so silent authentication cannot happen, but
1339
+ // if we do have a stored session ID, attempt to re-authenticate now silently.
1340
+ if (storedSessionId !== null) {
1341
+ const attemptedSilentAuthentication = await silentlyAuthenticate(storedSessionId, this.clientAuthentication, this);
1342
+ // At this point, we know that the main window will imminently be redirected.
1343
+ // However, this redirect is asynchronous and there is no way to halt execution
1344
+ // until it happens precisely. That's why the current Promise simply does not
1345
+ // resolve.
1346
+ if (attemptedSilentAuthentication) {
1347
+ return new Promise(() => { });
1348
+ }
1349
+ }
1350
+ }
1351
+ this.tokenRequestInProgress = false;
1352
+ return sessionInfo;
1353
+ };
1318
1354
  setSessionInfo(sessionInfo) {
1319
1355
  this.info.isLoggedIn = sessionInfo.isLoggedIn;
1320
1356
  this.info.webId = sessionInfo.webId;