You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-08-07 21:23:00 +03:00
Revert "Support refresh tokens (#7802)"
This reverts commit 839593412c
.
This commit is contained in:
242
src/Lifecycle.ts
242
src/Lifecycle.ts
@@ -58,7 +58,6 @@ import LazyLoadingDisabledDialog from "./components/views/dialogs/LazyLoadingDis
|
||||
import SessionRestoreErrorDialog from "./components/views/dialogs/SessionRestoreErrorDialog";
|
||||
import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
|
||||
import { setSentryUser } from "./sentry";
|
||||
import { IRenewedMatrixClientCreds, TokenLifecycle } from "./TokenLifecycle";
|
||||
|
||||
const HOMESERVER_URL_KEY = "mx_hs_url";
|
||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||
@@ -204,7 +203,6 @@ export function attemptTokenLogin(
|
||||
"m.login.token", {
|
||||
token: queryParams.loginToken as string,
|
||||
initial_device_display_name: defaultDeviceDisplayName,
|
||||
refresh_token: TokenLifecycle.instance.isFeasible,
|
||||
},
|
||||
).then(function(creds) {
|
||||
logger.log("Logged in with token");
|
||||
@@ -311,8 +309,6 @@ export interface IStoredSession {
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
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> {
|
||||
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
|
||||
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
|
||||
let accessToken: string;
|
||||
let accessToken;
|
||||
try {
|
||||
accessToken = await StorageManager.idbLoad("account", "mx_access_token");
|
||||
} 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
|
||||
// token, then we should say we have an access token
|
||||
const hasAccessToken =
|
||||
@@ -393,17 +352,7 @@ export async function getStoredSessionVars(): Promise<IStoredSession> {
|
||||
isGuest = localStorage.getItem("matrix-is-guest") === "true";
|
||||
}
|
||||
|
||||
return {
|
||||
hsUrl,
|
||||
isUrl,
|
||||
hasAccessToken,
|
||||
accessToken,
|
||||
accessTokenExpiryTs,
|
||||
accessTokenRefreshToken,
|
||||
userId,
|
||||
deviceId,
|
||||
isGuest,
|
||||
};
|
||||
return { hsUrl, isUrl, hasAccessToken, accessToken, userId, deviceId, isGuest };
|
||||
}
|
||||
|
||||
// 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
|
||||
// localstorage
|
||||
//
|
||||
@@ -494,16 +408,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
hsUrl,
|
||||
isUrl,
|
||||
hasAccessToken,
|
||||
accessToken,
|
||||
userId,
|
||||
deviceId,
|
||||
isGuest,
|
||||
accessTokenExpiryTs,
|
||||
} = await getStoredSessionVars();
|
||||
const { hsUrl, isUrl, hasAccessToken, accessToken, userId, deviceId, isGuest } = await getStoredSessionVars();
|
||||
|
||||
if (hasAccessToken && !accessToken) {
|
||||
abortLogin();
|
||||
@@ -515,11 +420,18 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||
return false;
|
||||
}
|
||||
|
||||
let decryptedAccessToken = accessToken;
|
||||
const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId);
|
||||
const {
|
||||
accessToken: decryptedAccessToken,
|
||||
accessTokenRefreshToken: decryptedRefreshToken,
|
||||
} = await getRenewedStoredSessionVars();
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
logger.log("No pickle key available");
|
||||
}
|
||||
|
||||
const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
|
||||
sessionStorage.removeItem("mx_fresh_login");
|
||||
@@ -534,8 +446,6 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||
guest: isGuest,
|
||||
pickleKey: pickleKey,
|
||||
freshLogin: freshLogin,
|
||||
accessTokenExpiryTs: accessTokenExpiryTs,
|
||||
accessTokenRefreshToken: decryptedRefreshToken as string,
|
||||
}, false);
|
||||
return true;
|
||||
} 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,
|
||||
* 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
|
||||
*/
|
||||
export async function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
|
||||
export function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
|
||||
const oldUserId = MatrixClientPeg.get().getUserId();
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* to localstorage, starts the new client.
|
||||
@@ -675,10 +554,8 @@ async function doSetLoggedIn(
|
||||
" deviceId: " + credentials.deviceId +
|
||||
" guest: " + credentials.guest +
|
||||
" hs: " + credentials.homeserverUrl +
|
||||
" softLogout: " + softLogout +
|
||||
" freshLogin: " + credentials.freshLogin +
|
||||
" tokenExpires: " + (!!credentials.accessTokenExpiryTs) +
|
||||
" tokenRenewable: " + (!!credentials.accessTokenRefreshToken),
|
||||
" softLogout: " + softLogout,
|
||||
" freshLogin: " + credentials.freshLogin,
|
||||
);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
if (PosthogAnalytics.instance.isEnabled()) {
|
||||
@@ -751,12 +605,8 @@ async function doSetLoggedIn(
|
||||
if (localStorage) {
|
||||
try {
|
||||
await persistCredentials(credentials);
|
||||
// make sure we don't think that it's a fresh login anymore
|
||||
// make sure we don't think that it's a fresh login any more
|
||||
sessionStorage.removeItem("mx_fresh_login");
|
||||
|
||||
if (didTokenRefresh) {
|
||||
TokenLifecycle.instance.flagNewCredentialsPersisted();
|
||||
}
|
||||
} catch (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!");
|
||||
}
|
||||
|
||||
// Start the token lifecycle as late as possible in case something above goes wrong
|
||||
TokenLifecycle.instance.startTimers(credentials);
|
||||
|
||||
dis.dispatch({ action: 'on_logged_in' });
|
||||
|
||||
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_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
|
||||
// where IndexedDB is blown away
|
||||
if (credentials.accessToken) {
|
||||
localStorage.setItem("mx_has_access_token", "true");
|
||||
} else {
|
||||
localStorage.removeItem("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
|
||||
}
|
||||
localStorage.deleteItem("mx_has_access_token");
|
||||
}
|
||||
|
||||
if (credentials.pickleKey) {
|
||||
let encryptedAccessToken: IEncryptedPayload;
|
||||
let encryptedRefreshToken: IEncryptedPayload;
|
||||
let encryptedAccessToken;
|
||||
try {
|
||||
// try to encrypt the access token using the pickle key
|
||||
const encrKey = await pickleKeyToAesKey(credentials.pickleKey);
|
||||
encryptedAccessToken = await encryptAES(credentials.accessToken, encrKey, "access_token");
|
||||
if (credentials.accessTokenRefreshToken) {
|
||||
encryptedRefreshToken = await encryptAES(
|
||||
credentials.accessTokenRefreshToken, encrKey, "refresh_token",
|
||||
);
|
||||
}
|
||||
encrKey.fill(0);
|
||||
} catch (e) {
|
||||
logger.warn("Could not encrypt access token", e);
|
||||
@@ -843,20 +666,11 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
|
||||
"account", "mx_access_token",
|
||||
encryptedAccessToken || credentials.accessToken,
|
||||
);
|
||||
if (encryptedRefreshToken) {
|
||||
await StorageManager.idbSave(
|
||||
"account", "mx_refresh_token",
|
||||
encryptedRefreshToken || credentials.accessTokenRefreshToken,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// if we couldn't save to indexedDB, fall back to localStorage. We
|
||||
// store the access token unencrypted since localStorage only saves
|
||||
// strings.
|
||||
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));
|
||||
} else {
|
||||
@@ -867,15 +681,6 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
|
||||
} catch (e) {
|
||||
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")) {
|
||||
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.
|
||||
*/
|
||||
export function stopMatrixClient(unsetClient = true): void {
|
||||
TokenLifecycle.instance.stopTimers();
|
||||
Notifier.stop();
|
||||
CallHandler.instance.stop();
|
||||
UserActivity.sharedInstance().stop();
|
||||
|
Reference in New Issue
Block a user