Revert "Support refresh tokens (#7802)"
This reverts commit 839593412c
.
This commit is contained in:
parent
81f52283cf
commit
aba61fa390
9 changed files with 25 additions and 504 deletions
|
@ -83,7 +83,6 @@
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"highlight.js": "^11.3.1",
|
"highlight.js": "^11.3.1",
|
||||||
"html-entities": "^1.4.0",
|
"html-entities": "^1.4.0",
|
||||||
"idb-mutex": "^0.11.0",
|
|
||||||
"is-ip": "^3.1.0",
|
"is-ip": "^3.1.0",
|
||||||
"jszip": "^3.7.0",
|
"jszip": "^3.7.0",
|
||||||
"katex": "^0.12.0",
|
"katex": "^0.12.0",
|
||||||
|
|
240
src/Lifecycle.ts
240
src/Lifecycle.ts
|
@ -58,7 +58,6 @@ import LazyLoadingDisabledDialog from "./components/views/dialogs/LazyLoadingDis
|
||||||
import SessionRestoreErrorDialog from "./components/views/dialogs/SessionRestoreErrorDialog";
|
import SessionRestoreErrorDialog from "./components/views/dialogs/SessionRestoreErrorDialog";
|
||||||
import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
|
import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
|
||||||
import { setSentryUser } from "./sentry";
|
import { setSentryUser } from "./sentry";
|
||||||
import { IRenewedMatrixClientCreds, TokenLifecycle } from "./TokenLifecycle";
|
|
||||||
|
|
||||||
const HOMESERVER_URL_KEY = "mx_hs_url";
|
const HOMESERVER_URL_KEY = "mx_hs_url";
|
||||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||||
|
@ -204,7 +203,6 @@ export function attemptTokenLogin(
|
||||||
"m.login.token", {
|
"m.login.token", {
|
||||||
token: queryParams.loginToken as string,
|
token: queryParams.loginToken as string,
|
||||||
initial_device_display_name: defaultDeviceDisplayName,
|
initial_device_display_name: defaultDeviceDisplayName,
|
||||||
refresh_token: TokenLifecycle.instance.isFeasible,
|
|
||||||
},
|
},
|
||||||
).then(function(creds) {
|
).then(function(creds) {
|
||||||
logger.log("Logged in with token");
|
logger.log("Logged in with token");
|
||||||
|
@ -311,8 +309,6 @@ export interface IStoredSession {
|
||||||
userId: string;
|
userId: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
isGuest: boolean;
|
isGuest: boolean;
|
||||||
accessTokenExpiryTs?: number; // set if the token expires
|
|
||||||
accessTokenRefreshToken?: string | IEncryptedPayload; // set if the token can be renewed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -323,7 +319,7 @@ export interface IStoredSession {
|
||||||
export async function getStoredSessionVars(): Promise<IStoredSession> {
|
export async function getStoredSessionVars(): Promise<IStoredSession> {
|
||||||
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
|
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
|
||||||
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
|
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
|
||||||
let accessToken: string;
|
let accessToken;
|
||||||
try {
|
try {
|
||||||
accessToken = await StorageManager.idbLoad("account", "mx_access_token");
|
accessToken = await StorageManager.idbLoad("account", "mx_access_token");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -341,43 +337,6 @@ export async function getStoredSessionVars(): Promise<IStoredSession> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let accessTokenExpiryTs: number;
|
|
||||||
let accessTokenRefreshToken: string;
|
|
||||||
if (accessToken) {
|
|
||||||
const expiration = localStorage.getItem("mx_access_token_expires_ts");
|
|
||||||
if (expiration) accessTokenExpiryTs = Number(expiration);
|
|
||||||
|
|
||||||
if (localStorage.getItem("mx_has_refresh_token")) {
|
|
||||||
try {
|
|
||||||
accessTokenRefreshToken = await StorageManager.idbLoad(
|
|
||||||
"account", "mx_refresh_token",
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn(
|
|
||||||
"StorageManager.idbLoad failed for account:mx_refresh_token " +
|
|
||||||
"(presuming no refresh token)",
|
|
||||||
e,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessTokenRefreshToken) {
|
|
||||||
accessTokenRefreshToken = localStorage.getItem("mx_refresh_token");
|
|
||||||
if (accessTokenRefreshToken) {
|
|
||||||
try {
|
|
||||||
// try to migrate refresh token to IndexedDB if we can
|
|
||||||
await StorageManager.idbSave(
|
|
||||||
"account", "mx_refresh_token", accessTokenRefreshToken,
|
|
||||||
);
|
|
||||||
localStorage.removeItem("mx_refresh_token");
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("migration of refresh token to IndexedDB failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we pre-date storing "mx_has_access_token", but we retrieved an access
|
// if we pre-date storing "mx_has_access_token", but we retrieved an access
|
||||||
// token, then we should say we have an access token
|
// token, then we should say we have an access token
|
||||||
const hasAccessToken =
|
const hasAccessToken =
|
||||||
|
@ -393,17 +352,7 @@ export async function getStoredSessionVars(): Promise<IStoredSession> {
|
||||||
isGuest = localStorage.getItem("matrix-is-guest") === "true";
|
isGuest = localStorage.getItem("matrix-is-guest") === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { hsUrl, isUrl, hasAccessToken, accessToken, userId, deviceId, isGuest };
|
||||||
hsUrl,
|
|
||||||
isUrl,
|
|
||||||
hasAccessToken,
|
|
||||||
accessToken,
|
|
||||||
accessTokenExpiryTs,
|
|
||||||
accessTokenRefreshToken,
|
|
||||||
userId,
|
|
||||||
deviceId,
|
|
||||||
isGuest,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pickle key is a string of unspecified length and format. For AES, we
|
// The pickle key is a string of unspecified length and format. For AES, we
|
||||||
|
@ -442,41 +391,6 @@ async function abortLogin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRenewedStoredSessionVars(): Promise<IRenewedMatrixClientCreds> {
|
|
||||||
const {
|
|
||||||
userId,
|
|
||||||
deviceId,
|
|
||||||
accessToken,
|
|
||||||
accessTokenExpiryTs,
|
|
||||||
accessTokenRefreshToken,
|
|
||||||
} = await getStoredSessionVars();
|
|
||||||
|
|
||||||
let decryptedAccessToken = accessToken;
|
|
||||||
let decryptedRefreshToken = accessTokenRefreshToken;
|
|
||||||
const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId);
|
|
||||||
if (pickleKey) {
|
|
||||||
logger.log("Got pickle key");
|
|
||||||
if (typeof accessToken !== "string") {
|
|
||||||
const encrKey = await pickleKeyToAesKey(pickleKey);
|
|
||||||
decryptedAccessToken = await decryptAES(accessToken, encrKey, "access_token");
|
|
||||||
encrKey.fill(0);
|
|
||||||
}
|
|
||||||
if (accessTokenRefreshToken && typeof accessTokenRefreshToken !== "string") {
|
|
||||||
const encrKey = await pickleKeyToAesKey(pickleKey);
|
|
||||||
decryptedRefreshToken = await decryptAES(accessTokenRefreshToken, encrKey, "refresh_token");
|
|
||||||
encrKey.fill(0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.log("No pickle key available");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
accessToken: decryptedAccessToken as string,
|
|
||||||
accessTokenExpiryTs: accessTokenExpiryTs,
|
|
||||||
accessTokenRefreshToken: decryptedRefreshToken as string,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns a promise which resolves to true if a session is found in
|
// returns a promise which resolves to true if a session is found in
|
||||||
// localstorage
|
// localstorage
|
||||||
//
|
//
|
||||||
|
@ -494,16 +408,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { hsUrl, isUrl, hasAccessToken, accessToken, userId, deviceId, isGuest } = await getStoredSessionVars();
|
||||||
hsUrl,
|
|
||||||
isUrl,
|
|
||||||
hasAccessToken,
|
|
||||||
accessToken,
|
|
||||||
userId,
|
|
||||||
deviceId,
|
|
||||||
isGuest,
|
|
||||||
accessTokenExpiryTs,
|
|
||||||
} = await getStoredSessionVars();
|
|
||||||
|
|
||||||
if (hasAccessToken && !accessToken) {
|
if (hasAccessToken && !accessToken) {
|
||||||
abortLogin();
|
abortLogin();
|
||||||
|
@ -515,11 +420,18 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let decryptedAccessToken = accessToken;
|
||||||
const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId);
|
const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId);
|
||||||
const {
|
if (pickleKey) {
|
||||||
accessToken: decryptedAccessToken,
|
logger.log("Got pickle key");
|
||||||
accessTokenRefreshToken: decryptedRefreshToken,
|
if (typeof accessToken !== "string") {
|
||||||
} = await getRenewedStoredSessionVars();
|
const encrKey = await pickleKeyToAesKey(pickleKey);
|
||||||
|
decryptedAccessToken = await decryptAES(accessToken, encrKey, "access_token");
|
||||||
|
encrKey.fill(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.log("No pickle key available");
|
||||||
|
}
|
||||||
|
|
||||||
const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
|
const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
|
||||||
sessionStorage.removeItem("mx_fresh_login");
|
sessionStorage.removeItem("mx_fresh_login");
|
||||||
|
@ -534,8 +446,6 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||||
guest: isGuest,
|
guest: isGuest,
|
||||||
pickleKey: pickleKey,
|
pickleKey: pickleKey,
|
||||||
freshLogin: freshLogin,
|
freshLogin: freshLogin,
|
||||||
accessTokenExpiryTs: accessTokenExpiryTs,
|
|
||||||
accessTokenRefreshToken: decryptedRefreshToken as string,
|
|
||||||
}, false);
|
}, false);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -601,10 +511,12 @@ export async function setLoggedIn(credentials: IMatrixClientCreds): Promise<Matr
|
||||||
*
|
*
|
||||||
* If the credentials belong to a different user from the session already stored,
|
* If the credentials belong to a different user from the session already stored,
|
||||||
* the old session will be cleared automatically.
|
* the old session will be cleared automatically.
|
||||||
* @param {IMatrixClientCreds} credentials The credentials to use
|
*
|
||||||
|
* @param {MatrixClientCreds} credentials The credentials to use
|
||||||
|
*
|
||||||
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
||||||
*/
|
*/
|
||||||
export async function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
|
export function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
|
||||||
const oldUserId = MatrixClientPeg.get().getUserId();
|
const oldUserId = MatrixClientPeg.get().getUserId();
|
||||||
const oldDeviceId = MatrixClientPeg.get().getDeviceId();
|
const oldDeviceId = MatrixClientPeg.get().getDeviceId();
|
||||||
|
|
||||||
|
@ -617,42 +529,9 @@ export async function hydrateSession(credentials: IMatrixClientCreds): Promise<M
|
||||||
logger.warn("Clearing all data: Old session belongs to a different user/session");
|
logger.warn("Clearing all data: Old session belongs to a different user/session");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!credentials.pickleKey) {
|
|
||||||
logger.info("Lifecycle#hydrateSession: Pickle key not provided - trying to get one");
|
|
||||||
credentials.pickleKey = await PlatformPeg.get().getPickleKey(credentials.userId, credentials.deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return doSetLoggedIn(credentials, overwrite);
|
return doSetLoggedIn(credentials, overwrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to hydrateSession(), this will update the credentials used by the current
|
|
||||||
* session in-place. Services will not be restarted, and storage will not be deleted.
|
|
||||||
* @param {IMatrixClientCreds} credentials The credentials to use
|
|
||||||
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
|
||||||
*/
|
|
||||||
export async function hydrateSessionInPlace(credentials: IMatrixClientCreds): Promise<MatrixClient> {
|
|
||||||
const oldUserId = MatrixClientPeg.get().getUserId();
|
|
||||||
const oldDeviceId = MatrixClientPeg.get().getDeviceId();
|
|
||||||
if (credentials.userId !== oldUserId || credentials.deviceId !== oldDeviceId) {
|
|
||||||
throw new Error("Attempted to hydrate in-place with a different session");
|
|
||||||
}
|
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
|
||||||
if (!cli) {
|
|
||||||
throw new Error("Attempted to hydrate a non-existent MatrixClient");
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Lifecycle#hydrateInPlace: Persisting credentials and updating access token");
|
|
||||||
await persistCredentials(credentials);
|
|
||||||
MatrixClientPeg.updateUsingCreds(credentials);
|
|
||||||
|
|
||||||
// reset the token timers
|
|
||||||
TokenLifecycle.instance.startTimers(credentials);
|
|
||||||
|
|
||||||
return cli;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fires on_logging_in, optionally clears localstorage, persists new credentials
|
* fires on_logging_in, optionally clears localstorage, persists new credentials
|
||||||
* to localstorage, starts the new client.
|
* to localstorage, starts the new client.
|
||||||
|
@ -675,10 +554,8 @@ async function doSetLoggedIn(
|
||||||
" deviceId: " + credentials.deviceId +
|
" deviceId: " + credentials.deviceId +
|
||||||
" guest: " + credentials.guest +
|
" guest: " + credentials.guest +
|
||||||
" hs: " + credentials.homeserverUrl +
|
" hs: " + credentials.homeserverUrl +
|
||||||
" softLogout: " + softLogout +
|
" softLogout: " + softLogout,
|
||||||
" freshLogin: " + credentials.freshLogin +
|
" freshLogin: " + credentials.freshLogin,
|
||||||
" tokenExpires: " + (!!credentials.accessTokenExpiryTs) +
|
|
||||||
" tokenRenewable: " + (!!credentials.accessTokenRefreshToken),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// This is dispatched to indicate that the user is still in the process of logging in
|
// This is dispatched to indicate that the user is still in the process of logging in
|
||||||
|
@ -706,29 +583,6 @@ async function doSetLoggedIn(
|
||||||
|
|
||||||
MatrixClientPeg.replaceUsingCreds(credentials);
|
MatrixClientPeg.replaceUsingCreds(credentials);
|
||||||
|
|
||||||
// Check the token's renewal early so we don't have to undo some of the work down below.
|
|
||||||
logger.info("Lifecycle#doSetLoggedIn: Trying token refresh in case it is needed");
|
|
||||||
let didTokenRefresh = false;
|
|
||||||
try {
|
|
||||||
const result = await TokenLifecycle.instance.tryTokenExchangeIfNeeded(credentials, MatrixClientPeg.get());
|
|
||||||
if (result) {
|
|
||||||
logger.info("Lifecycle#doSetLoggedIn: Token refresh successful, using credentials");
|
|
||||||
credentials.accessToken = result.accessToken;
|
|
||||||
credentials.accessTokenExpiryTs = result.accessTokenExpiryTs;
|
|
||||||
credentials.accessTokenRefreshToken = result.accessTokenRefreshToken;
|
|
||||||
|
|
||||||
// don't forget to replace the client with the new credentials
|
|
||||||
MatrixClientPeg.replaceUsingCreds(credentials);
|
|
||||||
|
|
||||||
didTokenRefresh = true;
|
|
||||||
} else {
|
|
||||||
logger.info("Lifecycle#doSetLoggedIn: Token refresh indicated as not needed");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Lifecycle#doSetLoggedIn: Failed to exchange token", e);
|
|
||||||
await abortLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
setSentryUser(credentials.userId);
|
setSentryUser(credentials.userId);
|
||||||
|
|
||||||
if (PosthogAnalytics.instance.isEnabled()) {
|
if (PosthogAnalytics.instance.isEnabled()) {
|
||||||
|
@ -753,10 +607,6 @@ async function doSetLoggedIn(
|
||||||
await persistCredentials(credentials);
|
await persistCredentials(credentials);
|
||||||
// make sure we don't think that it's a fresh login any more
|
// make sure we don't think that it's a fresh login any more
|
||||||
sessionStorage.removeItem("mx_fresh_login");
|
sessionStorage.removeItem("mx_fresh_login");
|
||||||
|
|
||||||
if (didTokenRefresh) {
|
|
||||||
TokenLifecycle.instance.flagNewCredentialsPersisted();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn("Error using local storage: can't persist session!", e);
|
logger.warn("Error using local storage: can't persist session!", e);
|
||||||
}
|
}
|
||||||
|
@ -764,9 +614,6 @@ async function doSetLoggedIn(
|
||||||
logger.warn("No local storage available: can't persist session!");
|
logger.warn("No local storage available: can't persist session!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the token lifecycle as late as possible in case something above goes wrong
|
|
||||||
TokenLifecycle.instance.startTimers(credentials);
|
|
||||||
|
|
||||||
dis.dispatch({ action: 'on_logged_in' });
|
dis.dispatch({ action: 'on_logged_in' });
|
||||||
|
|
||||||
await startMatrixClient(/*startSyncing=*/!softLogout);
|
await startMatrixClient(/*startSyncing=*/!softLogout);
|
||||||
|
@ -793,44 +640,20 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
|
||||||
localStorage.setItem("mx_user_id", credentials.userId);
|
localStorage.setItem("mx_user_id", credentials.userId);
|
||||||
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
|
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
|
||||||
|
|
||||||
if (credentials.accessTokenExpiryTs) {
|
|
||||||
localStorage.setItem("mx_access_token_expires_ts", credentials.accessTokenExpiryTs.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// store whether we expect to find an access token, to detect the case
|
// store whether we expect to find an access token, to detect the case
|
||||||
// where IndexedDB is blown away
|
// where IndexedDB is blown away
|
||||||
if (credentials.accessToken) {
|
if (credentials.accessToken) {
|
||||||
localStorage.setItem("mx_has_access_token", "true");
|
localStorage.setItem("mx_has_access_token", "true");
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("mx_has_access_token");
|
localStorage.deleteItem("mx_has_access_token");
|
||||||
}
|
|
||||||
|
|
||||||
// store a similar flag for the refresh token
|
|
||||||
if (credentials.accessTokenRefreshToken) {
|
|
||||||
localStorage.setItem("mx_has_refresh_token", "true");
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem("mx_has_refresh_token");
|
|
||||||
localStorage.removeItem("mx_refresh_token");
|
|
||||||
|
|
||||||
try {
|
|
||||||
await StorageManager.idbDelete("account", "mx_refresh_token");
|
|
||||||
} catch (e) {
|
|
||||||
// ignore - no action needed
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (credentials.pickleKey) {
|
if (credentials.pickleKey) {
|
||||||
let encryptedAccessToken: IEncryptedPayload;
|
let encryptedAccessToken;
|
||||||
let encryptedRefreshToken: IEncryptedPayload;
|
|
||||||
try {
|
try {
|
||||||
// try to encrypt the access token using the pickle key
|
// try to encrypt the access token using the pickle key
|
||||||
const encrKey = await pickleKeyToAesKey(credentials.pickleKey);
|
const encrKey = await pickleKeyToAesKey(credentials.pickleKey);
|
||||||
encryptedAccessToken = await encryptAES(credentials.accessToken, encrKey, "access_token");
|
encryptedAccessToken = await encryptAES(credentials.accessToken, encrKey, "access_token");
|
||||||
if (credentials.accessTokenRefreshToken) {
|
|
||||||
encryptedRefreshToken = await encryptAES(
|
|
||||||
credentials.accessTokenRefreshToken, encrKey, "refresh_token",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
encrKey.fill(0);
|
encrKey.fill(0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn("Could not encrypt access token", e);
|
logger.warn("Could not encrypt access token", e);
|
||||||
|
@ -843,20 +666,11 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
|
||||||
"account", "mx_access_token",
|
"account", "mx_access_token",
|
||||||
encryptedAccessToken || credentials.accessToken,
|
encryptedAccessToken || credentials.accessToken,
|
||||||
);
|
);
|
||||||
if (encryptedRefreshToken) {
|
|
||||||
await StorageManager.idbSave(
|
|
||||||
"account", "mx_refresh_token",
|
|
||||||
encryptedRefreshToken || credentials.accessTokenRefreshToken,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// if we couldn't save to indexedDB, fall back to localStorage. We
|
// if we couldn't save to indexedDB, fall back to localStorage. We
|
||||||
// store the access token unencrypted since localStorage only saves
|
// store the access token unencrypted since localStorage only saves
|
||||||
// strings.
|
// strings.
|
||||||
localStorage.setItem("mx_access_token", credentials.accessToken);
|
localStorage.setItem("mx_access_token", credentials.accessToken);
|
||||||
if (credentials.accessTokenRefreshToken) {
|
|
||||||
localStorage.setItem("mx_refresh_token", credentials.accessTokenRefreshToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
localStorage.setItem("mx_has_pickle_key", String(true));
|
localStorage.setItem("mx_has_pickle_key", String(true));
|
||||||
} else {
|
} else {
|
||||||
|
@ -867,15 +681,6 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
localStorage.setItem("mx_access_token", credentials.accessToken);
|
localStorage.setItem("mx_access_token", credentials.accessToken);
|
||||||
}
|
}
|
||||||
if (credentials.accessTokenRefreshToken) {
|
|
||||||
try {
|
|
||||||
await StorageManager.idbSave(
|
|
||||||
"account", "mx_refresh_token", credentials.accessTokenRefreshToken,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
localStorage.setItem("mx_refresh_token", credentials.accessTokenRefreshToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (localStorage.getItem("mx_has_pickle_key")) {
|
if (localStorage.getItem("mx_has_pickle_key")) {
|
||||||
logger.error("Expected a pickle key, but none provided. Encryption may not work.");
|
logger.error("Expected a pickle key, but none provided. Encryption may not work.");
|
||||||
}
|
}
|
||||||
|
@ -1086,7 +891,6 @@ async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void
|
||||||
* on MatrixClientPeg after stopping.
|
* on MatrixClientPeg after stopping.
|
||||||
*/
|
*/
|
||||||
export function stopMatrixClient(unsetClient = true): void {
|
export function stopMatrixClient(unsetClient = true): void {
|
||||||
TokenLifecycle.instance.stopTimers();
|
|
||||||
Notifier.stop();
|
Notifier.stop();
|
||||||
CallHandler.instance.stop();
|
CallHandler.instance.stop();
|
||||||
UserActivity.sharedInstance().stop();
|
UserActivity.sharedInstance().stop();
|
||||||
|
|
10
src/Login.ts
10
src/Login.ts
|
@ -22,7 +22,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { IMatrixClientCreds } from "./MatrixClientPeg";
|
import { IMatrixClientCreds } from "./MatrixClientPeg";
|
||||||
import SecurityCustomisations from "./customisations/Security";
|
import SecurityCustomisations from "./customisations/Security";
|
||||||
import { TokenLifecycle } from "./TokenLifecycle";
|
|
||||||
|
|
||||||
interface ILoginOptions {
|
interface ILoginOptions {
|
||||||
defaultDeviceDisplayName?: string;
|
defaultDeviceDisplayName?: string;
|
||||||
|
@ -65,11 +64,6 @@ interface ILoginParams {
|
||||||
token?: string;
|
token?: string;
|
||||||
device_id?: string;
|
device_id?: string;
|
||||||
initial_device_display_name?: string;
|
initial_device_display_name?: string;
|
||||||
|
|
||||||
// If true, a refresh token will be requested. If the server supports it, it
|
|
||||||
// will be returned. Does nothing out of the ordinary if not set, false, or
|
|
||||||
// the server doesn't support the feature.
|
|
||||||
refresh_token?: boolean;
|
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
@ -168,7 +162,6 @@ export default class Login {
|
||||||
password,
|
password,
|
||||||
identifier,
|
identifier,
|
||||||
initial_device_display_name: this.defaultDeviceDisplayName,
|
initial_device_display_name: this.defaultDeviceDisplayName,
|
||||||
refresh_token: TokenLifecycle.instance.isFeasible,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const tryFallbackHs = (originalError) => {
|
const tryFallbackHs = (originalError) => {
|
||||||
|
@ -242,9 +235,6 @@ export async function sendLoginRequest(
|
||||||
userId: data.user_id,
|
userId: data.user_id,
|
||||||
deviceId: data.device_id,
|
deviceId: data.device_id,
|
||||||
accessToken: data.access_token,
|
accessToken: data.access_token,
|
||||||
// Use the browser's local time for expiration timestamp - see TokenLifecycle for more info
|
|
||||||
accessTokenExpiryTs: data.expires_in_ms ? (data.expires_in_ms + Date.now()) : null,
|
|
||||||
accessTokenRefreshToken: data.refresh_token,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SecurityCustomisations.examineLoginResponse?.(data, creds);
|
SecurityCustomisations.examineLoginResponse?.(data, creds);
|
||||||
|
|
|
@ -44,8 +44,6 @@ export interface IMatrixClientCreds {
|
||||||
userId: string;
|
userId: string;
|
||||||
deviceId?: string;
|
deviceId?: string;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
accessTokenExpiryTs?: number; // set if access token expires
|
|
||||||
accessTokenRefreshToken?: string; // set if access token can be renewed
|
|
||||||
guest?: boolean;
|
guest?: boolean;
|
||||||
pickleKey?: string;
|
pickleKey?: string;
|
||||||
freshLogin?: boolean;
|
freshLogin?: boolean;
|
||||||
|
@ -101,14 +99,6 @@ export interface IMatrixClientPeg {
|
||||||
* @param {IMatrixClientCreds} creds The new credentials to use.
|
* @param {IMatrixClientCreds} creds The new credentials to use.
|
||||||
*/
|
*/
|
||||||
replaceUsingCreds(creds: IMatrixClientCreds): void;
|
replaceUsingCreds(creds: IMatrixClientCreds): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to replaceUsingCreds(), but without the replacement operation.
|
|
||||||
* Credentials that can be updated in-place will be updated. All others
|
|
||||||
* will be ignored.
|
|
||||||
* @param {IMatrixClientCreds} creds The new credentials to use.
|
|
||||||
*/
|
|
||||||
updateUsingCreds(creds: IMatrixClientCreds): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,15 +164,6 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
||||||
this.createClient(creds);
|
this.createClient(creds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateUsingCreds(creds: IMatrixClientCreds): void {
|
|
||||||
if (creds?.accessToken) {
|
|
||||||
this.currentClientCreds = creds;
|
|
||||||
this.matrixClient.setAccessToken(creds.accessToken);
|
|
||||||
} else {
|
|
||||||
// ignore, per signature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async assign(): Promise<any> {
|
public async assign(): Promise<any> {
|
||||||
for (const dbType of ['indexeddb', 'memory']) {
|
for (const dbType of ['indexeddb', 'memory']) {
|
||||||
try {
|
try {
|
||||||
|
@ -252,15 +233,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCredentials(): IMatrixClientCreds {
|
public getCredentials(): IMatrixClientCreds {
|
||||||
let copiedCredentials = this.currentClientCreds;
|
|
||||||
if (this.currentClientCreds?.userId !== this.matrixClient?.credentials?.userId) {
|
|
||||||
// cached credentials belong to a different user - don't use them
|
|
||||||
copiedCredentials = null;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
// Copy the cached credentials before overriding what we can.
|
|
||||||
...(copiedCredentials ?? {}),
|
|
||||||
|
|
||||||
homeserverUrl: this.matrixClient.baseUrl,
|
homeserverUrl: this.matrixClient.baseUrl,
|
||||||
identityServerUrl: this.matrixClient.idBaseUrl,
|
identityServerUrl: this.matrixClient.idBaseUrl,
|
||||||
userId: this.matrixClient.credentials.userId,
|
userId: this.matrixClient.credentials.userId,
|
||||||
|
|
|
@ -1,233 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
|
||||||
import { MatrixClient } from "matrix-js-sdk/src";
|
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
|
||||||
import Mutex from "idb-mutex";
|
|
||||||
import { Optional } from "matrix-events-sdk";
|
|
||||||
|
|
||||||
import { IMatrixClientCreds, MatrixClientPeg } from "./MatrixClientPeg";
|
|
||||||
import { getRenewedStoredSessionVars, hydrateSessionInPlace } from "./Lifecycle";
|
|
||||||
import { IDB_SUPPORTED } from "./utils/StorageManager";
|
|
||||||
|
|
||||||
export interface IRenewedMatrixClientCreds extends Pick<IMatrixClientCreds,
|
|
||||||
"accessToken" | "accessTokenExpiryTs" | "accessTokenRefreshToken"> {}
|
|
||||||
|
|
||||||
const LOCALSTORAGE_UPDATED_BY_KEY = "mx_token_updated_by";
|
|
||||||
|
|
||||||
const CLIENT_ID = randomString(64);
|
|
||||||
|
|
||||||
export class TokenLifecycle {
|
|
||||||
public static readonly instance = new TokenLifecycle();
|
|
||||||
|
|
||||||
private refreshAtTimerId: number;
|
|
||||||
private mutex: Mutex;
|
|
||||||
|
|
||||||
protected constructor() {
|
|
||||||
// we only really want one of these floating around, so private-ish
|
|
||||||
// constructor. Protected allows for unit tests.
|
|
||||||
|
|
||||||
// Don't try to create a mutex if it'll explode
|
|
||||||
if (IDB_SUPPORTED) {
|
|
||||||
this.mutex = new Mutex("token_refresh", null, {
|
|
||||||
expiry: 120000, // 2 minutes - enough time for the refresh request to time out
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for other tabs causing token refreshes, so we can react to them too.
|
|
||||||
window.addEventListener("storage", (ev: StorageEvent) => {
|
|
||||||
if (ev.key === LOCALSTORAGE_UPDATED_BY_KEY) {
|
|
||||||
const updateBy = localStorage.getItem(LOCALSTORAGE_UPDATED_BY_KEY);
|
|
||||||
if (!updateBy || updateBy === CLIENT_ID) return; // ignore deletions & echos
|
|
||||||
|
|
||||||
logger.info("TokenLifecycle#storageWatch: Token update received");
|
|
||||||
|
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
|
||||||
this.forceHydration();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can the client reasonably support token refreshes?
|
|
||||||
*/
|
|
||||||
public get isFeasible(): boolean {
|
|
||||||
return IDB_SUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// noinspection JSMethodCanBeStatic
|
|
||||||
private get fiveMinutesAgo(): number {
|
|
||||||
return Date.now() - 300000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// noinspection JSMethodCanBeStatic
|
|
||||||
private get fiveMinutesFromNow(): number {
|
|
||||||
return Date.now() + 300000;
|
|
||||||
}
|
|
||||||
|
|
||||||
public flagNewCredentialsPersisted() {
|
|
||||||
logger.info("TokenLifecycle#flagPersisted: Credentials marked as persisted - flagging for other tabs");
|
|
||||||
if (localStorage.getItem(LOCALSTORAGE_UPDATED_BY_KEY) !== CLIENT_ID) {
|
|
||||||
localStorage.setItem(LOCALSTORAGE_UPDATED_BY_KEY, CLIENT_ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts a token renewal, if renewal is needed/possible. If renewal is not possible
|
|
||||||
* then this will return falsy. Otherwise, the new token's details (credentials) will
|
|
||||||
* be returned or an error if something went wrong.
|
|
||||||
* @param {IMatrixClientCreds} credentials The input credentials.
|
|
||||||
* @param {MatrixClient} client A client set up with those credentials.
|
|
||||||
* @returns {Promise<Optional<IRenewedMatrixClientCreds>>} Resolves to the new credentials,
|
|
||||||
* or falsy if renewal not possible/needed. Throws on error.
|
|
||||||
*/
|
|
||||||
public async tryTokenExchangeIfNeeded(
|
|
||||||
credentials: IMatrixClientCreds,
|
|
||||||
client: MatrixClient,
|
|
||||||
): Promise<Optional<IRenewedMatrixClientCreds>> {
|
|
||||||
if (!credentials.accessTokenExpiryTs && credentials.accessTokenRefreshToken) {
|
|
||||||
logger.warn(
|
|
||||||
"TokenLifecycle#tryExchange: Got a refresh token, but no expiration time. The server is " +
|
|
||||||
"not compliant with the specification and might result in unexpected logouts.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isFeasible) {
|
|
||||||
logger.warn("TokenLifecycle#tryExchange: Client cannot do token refreshes reliably");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (credentials.accessTokenExpiryTs && credentials.accessTokenRefreshToken) {
|
|
||||||
if (this.fiveMinutesAgo >= credentials.accessTokenExpiryTs) {
|
|
||||||
logger.info("TokenLifecycle#tryExchange: Token has or will expire soon, refreshing");
|
|
||||||
return await this.doTokenRefresh(credentials, client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// noinspection JSMethodCanBeStatic
|
|
||||||
private async doTokenRefresh(
|
|
||||||
credentials: IMatrixClientCreds,
|
|
||||||
client: MatrixClient,
|
|
||||||
): Promise<Optional<IRenewedMatrixClientCreds>> {
|
|
||||||
try {
|
|
||||||
logger.info("TokenLifecycle#doRefresh: Acquiring lock");
|
|
||||||
await this.mutex.lock();
|
|
||||||
logger.info("TokenLifecycle#doRefresh: Lock acquired");
|
|
||||||
|
|
||||||
logger.info("TokenLifecycle#doRefresh: Performing refresh");
|
|
||||||
localStorage.removeItem(LOCALSTORAGE_UPDATED_BY_KEY);
|
|
||||||
const newCreds = await client.refreshToken(credentials.accessTokenRefreshToken);
|
|
||||||
return {
|
|
||||||
// We use the browser's local time to do two things:
|
|
||||||
// 1. Avoid having to write code that counts down and stores a "time left" variable
|
|
||||||
// 2. Work around any time drift weirdness by assuming the user's local machine will
|
|
||||||
// drift consistently with itself.
|
|
||||||
// We additionally add our own safety buffer when renewing tokens to avoid cases where
|
|
||||||
// the time drift is accelerating.
|
|
||||||
accessTokenExpiryTs: Date.now() + newCreds.expires_in_ms,
|
|
||||||
accessToken: newCreds.access_token,
|
|
||||||
accessTokenRefreshToken: newCreds.refresh_token,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("TokenLifecycle#doRefresh: Error refreshing token: ", e);
|
|
||||||
if (e.errcode === "M_UNKNOWN_TOKEN") {
|
|
||||||
// Emit the logout manually because the function inhibits it.
|
|
||||||
client.emit("Session.logged_out", e);
|
|
||||||
} else {
|
|
||||||
throw e; // we can't do anything with it, so re-throw
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
logger.info("TokenLifecycle#doRefresh: Releasing lock");
|
|
||||||
await this.mutex.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public startTimers(credentials: IMatrixClientCreds) {
|
|
||||||
this.stopTimers();
|
|
||||||
|
|
||||||
if (!credentials.accessTokenExpiryTs && credentials.accessTokenRefreshToken) {
|
|
||||||
logger.warn(
|
|
||||||
"TokenLifecycle#start: Got a refresh token, but no expiration time. The server is " +
|
|
||||||
"not compliant with the specification and might result in unexpected logouts.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isFeasible) {
|
|
||||||
logger.warn("TokenLifecycle#start: Not starting refresh timers - browser unsupported");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (credentials.accessTokenExpiryTs && credentials.accessTokenRefreshToken) {
|
|
||||||
// We schedule the refresh task for 5 minutes before the expiration timestamp as
|
|
||||||
// a safety buffer. We assume/hope that servers won't be expiring tokens faster
|
|
||||||
// than every 5 minutes, but we do need to consider cases where the expiration is
|
|
||||||
// fairly quick (<10 minutes, for example).
|
|
||||||
let relativeTime = credentials.accessTokenExpiryTs - this.fiveMinutesFromNow;
|
|
||||||
if (relativeTime <= 0) {
|
|
||||||
logger.warn(`TokenLifecycle#start: Refresh was set for ${relativeTime}ms - readjusting`);
|
|
||||||
relativeTime = Math.floor(Math.random() * 5000) + 30000; // 30 seconds + 5s jitter
|
|
||||||
}
|
|
||||||
this.refreshAtTimerId = setTimeout(() => {
|
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
|
||||||
this.forceTokenExchange();
|
|
||||||
}, relativeTime);
|
|
||||||
logger.info(`TokenLifecycle#start: Refresh timer set for ${relativeTime}ms from now`);
|
|
||||||
} else {
|
|
||||||
logger.info("TokenLifecycle#start: Not setting a refresh timer - token not renewable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public stopTimers() {
|
|
||||||
clearTimeout(this.refreshAtTimerId);
|
|
||||||
logger.info("TokenLifecycle#stop: Stopped refresh timer (if it was running)");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async forceTokenExchange() {
|
|
||||||
const credentials = MatrixClientPeg.getCredentials();
|
|
||||||
await this.rehydrate(await this.doTokenRefresh(credentials, MatrixClientPeg.get()));
|
|
||||||
this.flagNewCredentialsPersisted();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async forceHydration() {
|
|
||||||
const {
|
|
||||||
accessToken,
|
|
||||||
accessTokenRefreshToken,
|
|
||||||
accessTokenExpiryTs,
|
|
||||||
} = await getRenewedStoredSessionVars();
|
|
||||||
return this.rehydrate({ accessToken, accessTokenRefreshToken, accessTokenExpiryTs });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async rehydrate(newCreds: IRenewedMatrixClientCreds) {
|
|
||||||
const credentials = MatrixClientPeg.getCredentials();
|
|
||||||
try {
|
|
||||||
if (!newCreds) {
|
|
||||||
logger.error("TokenLifecycle#expireExchange: Expecting new credentials, got nothing. Rescheduling.");
|
|
||||||
this.startTimers(credentials);
|
|
||||||
} else {
|
|
||||||
logger.info("TokenLifecycle#expireExchange: Updating client credentials using rehydration");
|
|
||||||
await hydrateSessionInPlace({
|
|
||||||
...credentials,
|
|
||||||
...newCreds, // override from credentials
|
|
||||||
});
|
|
||||||
// hydrateSessionInPlace will ultimately call back to startTimers() for us, so no need to do it here.
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("TokenLifecycle#expireExchange: Error getting new credentials. Rescheduling.", e);
|
|
||||||
this.startTimers(credentials);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -37,7 +37,6 @@ import AuthBody from "../../views/auth/AuthBody";
|
||||||
import AuthHeader from "../../views/auth/AuthHeader";
|
import AuthHeader from "../../views/auth/AuthHeader";
|
||||||
import InteractiveAuth from "../InteractiveAuth";
|
import InteractiveAuth from "../InteractiveAuth";
|
||||||
import Spinner from "../../views/elements/Spinner";
|
import Spinner from "../../views/elements/Spinner";
|
||||||
import { TokenLifecycle } from "../../../TokenLifecycle";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
serverConfig: ValidatedServerConfig;
|
serverConfig: ValidatedServerConfig;
|
||||||
|
@ -416,7 +415,6 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
initial_device_display_name: this.props.defaultDeviceDisplayName,
|
initial_device_display_name: this.props.defaultDeviceDisplayName,
|
||||||
auth: undefined,
|
auth: undefined,
|
||||||
inhibit_login: undefined,
|
inhibit_login: undefined,
|
||||||
refresh_token: TokenLifecycle.instance.isFeasible,
|
|
||||||
};
|
};
|
||||||
if (auth) registerParams.auth = auth;
|
if (auth) registerParams.auth = auth;
|
||||||
if (inhibitLogin !== undefined && inhibitLogin !== null) registerParams.inhibit_login = inhibitLogin;
|
if (inhibitLogin !== undefined && inhibitLogin !== null) registerParams.inhibit_login = inhibitLogin;
|
||||||
|
|
|
@ -33,7 +33,6 @@ import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||||
import Spinner from "../../views/elements/Spinner";
|
import Spinner from "../../views/elements/Spinner";
|
||||||
import AuthHeader from "../../views/auth/AuthHeader";
|
import AuthHeader from "../../views/auth/AuthHeader";
|
||||||
import AuthBody from "../../views/auth/AuthBody";
|
import AuthBody from "../../views/auth/AuthBody";
|
||||||
import { TokenLifecycle } from "../../../TokenLifecycle";
|
|
||||||
|
|
||||||
const LOGIN_VIEW = {
|
const LOGIN_VIEW = {
|
||||||
LOADING: 1,
|
LOADING: 1,
|
||||||
|
@ -155,7 +154,6 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
},
|
},
|
||||||
password: this.state.password,
|
password: this.state.password,
|
||||||
device_id: MatrixClientPeg.get().getDeviceId(),
|
device_id: MatrixClientPeg.get().getDeviceId(),
|
||||||
refresh_token: TokenLifecycle.instance.isFeasible,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let credentials = null;
|
let credentials = null;
|
||||||
|
@ -189,7 +187,6 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
const loginParams = {
|
const loginParams = {
|
||||||
token: this.props.realQueryParams['loginToken'],
|
token: this.props.realQueryParams['loginToken'],
|
||||||
device_id: MatrixClientPeg.get().getDeviceId(),
|
device_id: MatrixClientPeg.get().getDeviceId(),
|
||||||
refresh_token: TokenLifecycle.instance.isFeasible,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let credentials = null;
|
let credentials = null;
|
||||||
|
|
|
@ -25,13 +25,11 @@ const localStorage = window.localStorage;
|
||||||
|
|
||||||
// just *accessing* indexedDB throws an exception in firefox with
|
// just *accessing* indexedDB throws an exception in firefox with
|
||||||
// indexeddb disabled.
|
// indexeddb disabled.
|
||||||
let indexedDB: IDBFactory;
|
let indexedDB;
|
||||||
try {
|
try {
|
||||||
indexedDB = window.indexedDB;
|
indexedDB = window.indexedDB;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
export const IDB_SUPPORTED = !!indexedDB;
|
|
||||||
|
|
||||||
// The JS SDK will add a prefix of "matrix-js-sdk:" to the sync store name.
|
// The JS SDK will add a prefix of "matrix-js-sdk:" to the sync store name.
|
||||||
const SYNC_STORE_NAME = "riot-web-sync";
|
const SYNC_STORE_NAME = "riot-web-sync";
|
||||||
const CRYPTO_STORE_NAME = "matrix-js-sdk:crypto";
|
const CRYPTO_STORE_NAME = "matrix-js-sdk:crypto";
|
||||||
|
@ -199,7 +197,7 @@ export function setCryptoInitialised(cryptoInited) {
|
||||||
/* Simple wrapper functions around IndexedDB.
|
/* Simple wrapper functions around IndexedDB.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let idb: IDBDatabase = null;
|
let idb = null;
|
||||||
|
|
||||||
async function idbInit(): Promise<void> {
|
async function idbInit(): Promise<void> {
|
||||||
if (!indexedDB) {
|
if (!indexedDB) {
|
||||||
|
|
|
@ -4750,11 +4750,6 @@ iconv-lite@^0.6.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||||
|
|
||||||
idb-mutex@^0.11.0:
|
|
||||||
version "0.11.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/idb-mutex/-/idb-mutex-0.11.0.tgz#1573321f74ab83c12c3d200c7cf22ee7c6800d2d"
|
|
||||||
integrity sha512-jirzMahSlkvNpq9MXzr5uBKjxQrA9gdPYhOJkQXhDW7MvP6RuJpSbog50HYOugkmZWfJ0WmHVhhX0/lG39qOZQ==
|
|
||||||
|
|
||||||
ieee754@^1.1.12, ieee754@^1.1.13:
|
ieee754@^1.1.12, ieee754@^1.1.13:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||||
|
|
Loading…
Reference in a new issue