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