diff --git a/apps/meteor/app/utils/client/lib/RestApiClient.ts b/apps/meteor/app/utils/client/lib/RestApiClient.ts index 3ea1d39f4f2f6..7cdb81beb7835 100644 --- a/apps/meteor/app/utils/client/lib/RestApiClient.ts +++ b/apps/meteor/app/utils/client/lib/RestApiClient.ts @@ -1,9 +1,9 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { RestClient } from '@rocket.chat/api-client'; -import { Accounts } from 'meteor/accounts-base'; import { invokeTwoFactorModal } from '../../../../client/lib/2fa/process2faReturn'; import { baseURI } from '../../../../client/lib/baseURI'; +import { STORAGE_KEYS, getStoredItem } from '../../../../client/lib/sdk/storage'; class RestApiClient extends RestClient { override getCredentials(): @@ -12,10 +12,7 @@ class RestApiClient extends RestClient { 'X-Auth-Token': string; } | undefined { - const [uid, token] = [ - Accounts.storageLocation.getItem(Accounts.USER_ID_KEY), - Accounts.storageLocation.getItem(Accounts.LOGIN_TOKEN_KEY), - ]; + const [uid, token] = [getStoredItem(STORAGE_KEYS.USER_ID), getStoredItem(STORAGE_KEYS.LOGIN_TOKEN)]; if (!uid || !token) { return; diff --git a/apps/meteor/client/lib/e2ee/rocketchat.e2e.ts b/apps/meteor/client/lib/e2ee/rocketchat.e2e.ts index 530a85203293c..ae6e90498f2b4 100644 --- a/apps/meteor/client/lib/e2ee/rocketchat.e2e.ts +++ b/apps/meteor/client/lib/e2ee/rocketchat.e2e.ts @@ -8,7 +8,6 @@ import { isTruthy } from '@rocket.chat/tools'; import { imperativeModal } from '@rocket.chat/ui-client'; import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; import sampleSize from 'lodash/sampleSize'; -import { Accounts } from 'meteor/accounts-base'; import type { E2EEState } from './E2EEState'; import * as Rsa from './crypto/rsa'; @@ -28,6 +27,7 @@ import SaveE2EPasswordModal from '../../views/e2e/SaveE2EPasswordModal'; import * as banners from '../banners'; import type { LegacyBannerPayload } from '../banners'; import { getDdpSdk } from '../sdk/ddpSdk'; +import { STORAGE_KEYS, getStoredItem, removeStoredItem, setStoredItem } from '../sdk/storage'; import { settings } from '../settings'; import { dispatchToastMessage } from '../toast'; import { mapMessageFromApi } from '../utils/mapMessageFromApi'; @@ -315,8 +315,8 @@ class E2E extends Emitter { getKeysFromLocalStorage(): KeyPair { return { - public_key: Accounts.storageLocation.getItem('public_key'), - private_key: Accounts.storageLocation.getItem('private_key'), + public_key: getStoredItem(STORAGE_KEYS.E2EE_PUBLIC_KEY), + private_key: getStoredItem(STORAGE_KEYS.E2EE_PRIVATE_KEY), }; } @@ -335,7 +335,7 @@ class E2E extends Emitter { imperativeModal.close(); }, onConfirm: () => { - Accounts.storageLocation.removeItem('e2e.randomPassword'); + removeStoredItem(STORAGE_KEYS.E2EE_RANDOM_PASSWORD); this.setState('READY'); dispatchToastMessage({ type: 'success', message: t('E2E_encryption_enabled') }); this.closeAlert(); @@ -403,7 +403,7 @@ class E2E extends Emitter { await this.persistKeys(this.getKeysFromLocalStorage(), await this.createRandomPassword()); } - const randomPassword = Accounts.storageLocation.getItem('e2e.randomPassword'); + const randomPassword = getStoredItem(STORAGE_KEYS.E2EE_RANDOM_PASSWORD); if (randomPassword) { this.setState('SAVE_PASSWORD'); this.openAlert({ @@ -422,8 +422,8 @@ class E2E extends Emitter { span.info(this.state); this.closeAlert(); - Accounts.storageLocation.removeItem('public_key'); - Accounts.storageLocation.removeItem('private_key'); + removeStoredItem(STORAGE_KEYS.E2EE_PUBLIC_KEY); + removeStoredItem(STORAGE_KEYS.E2EE_PRIVATE_KEY); this.instancesByRoomId = {}; this.privateKey = undefined; this.publicKey = undefined; @@ -438,8 +438,8 @@ class E2E extends Emitter { async changePassword(newPassword: string): Promise { await this.persistKeys(this.getKeysFromLocalStorage(), newPassword, { force: true }); - if (Accounts.storageLocation.getItem('e2e.randomPassword')) { - Accounts.storageLocation.setItem('e2e.randomPassword', newPassword); + if (getStoredItem(STORAGE_KEYS.E2EE_RANDOM_PASSWORD)) { + setStoredItem(STORAGE_KEYS.E2EE_RANDOM_PASSWORD, newPassword); } } @@ -464,13 +464,13 @@ class E2E extends Emitter { async loadKeys({ public_key, private_key }: { public_key: string; private_key: string }): Promise { const span = log.span('loadKeys'); - Accounts.storageLocation.setItem('public_key', public_key); + setStoredItem(STORAGE_KEYS.E2EE_PUBLIC_KEY, public_key); this.publicKey = public_key; try { this.privateKey = await Rsa.importPrivateKey(JSON.parse(private_key)); - Accounts.storageLocation.setItem('private_key', private_key); + setStoredItem(STORAGE_KEYS.E2EE_PRIVATE_KEY, private_key); } catch (error) { this.setState('ERROR'); return span.error('Error importing private key: ', error); @@ -494,7 +494,7 @@ class E2E extends Emitter { const publicKey = await Rsa.exportPublicKey(keyPair.publicKey); this.publicKey = JSON.stringify(publicKey); - Accounts.storageLocation.setItem('public_key', JSON.stringify(publicKey)); + setStoredItem(STORAGE_KEYS.E2EE_PUBLIC_KEY, JSON.stringify(publicKey)); } catch (error) { this.setState('ERROR'); return span.set('error', error).error('Error exporting public key'); @@ -503,7 +503,7 @@ class E2E extends Emitter { try { const privateKey = await Rsa.exportPrivateKey(keyPair.privateKey); - Accounts.storageLocation.setItem('private_key', JSON.stringify(privateKey)); + setStoredItem(STORAGE_KEYS.E2EE_PRIVATE_KEY, JSON.stringify(privateKey)); } catch (error) { this.setState('ERROR'); return span.set('error', error).error('Error exporting private key'); @@ -518,7 +518,7 @@ class E2E extends Emitter { async createRandomPassword(): Promise { const randomPassword = await generatePassphrase(); - Accounts.storageLocation.setItem('e2e.randomPassword', randomPassword); + setStoredItem(STORAGE_KEYS.E2EE_RANDOM_PASSWORD, randomPassword); return randomPassword; } diff --git a/apps/meteor/client/lib/sdk/ddpSdk.ts b/apps/meteor/client/lib/sdk/ddpSdk.ts index 426c767f2aa6c..38354ef22fcfe 100644 --- a/apps/meteor/client/lib/sdk/ddpSdk.ts +++ b/apps/meteor/client/lib/sdk/ddpSdk.ts @@ -5,6 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { createMeteorBackedSdk } from './meteorBackedSdk'; import { isSdkTransportEnabled } from './sdkTransportEnabled'; +import { STORAGE_KEYS, getStoredItem } from './storage'; import { userIdStore } from '../user'; const sdkTransportEnabled = isSdkTransportEnabled(); @@ -66,7 +67,7 @@ export const getDdpSdk = (): DDPSDK => { return instance; }; -const readStoredLoginToken = (): string | null => (typeof window !== 'undefined' ? window.localStorage.getItem('Meteor.loginToken') : null); +const readStoredLoginToken = (): string | null => getStoredItem(STORAGE_KEYS.LOGIN_TOKEN); let inflightLogin: Promise | undefined; diff --git a/apps/meteor/client/lib/sdk/storage.ts b/apps/meteor/client/lib/sdk/storage.ts new file mode 100644 index 0000000000000..ba71bf1ab1459 --- /dev/null +++ b/apps/meteor/client/lib/sdk/storage.ts @@ -0,0 +1,22 @@ +// Single point of access to the client-side persistent storage that +// Rocket.Chat shares with Meteor's accounts-base. Reads and writes use +// window.localStorage under the hood; the keys mirror the names Meteor +// originally wrote so sessions persist across the Meteor → SDK migration. + +export const STORAGE_KEYS = { + USER_ID: 'Meteor.userId', + LOGIN_TOKEN: 'Meteor.loginToken', + E2EE_PUBLIC_KEY: 'public_key', + E2EE_PRIVATE_KEY: 'private_key', + E2EE_RANDOM_PASSWORD: 'e2e.randomPassword', +} as const; + +export type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS]; + +const getStorage = (): Storage | undefined => (typeof window !== 'undefined' ? window.localStorage : undefined); + +export const getStoredItem = (key: string): string | null => getStorage()?.getItem(key) ?? null; + +export const setStoredItem = (key: string, value: string): void => getStorage()?.setItem(key, value); + +export const removeStoredItem = (key: string): void => getStorage()?.removeItem(key); diff --git a/apps/meteor/client/meteor/login/saml.ts b/apps/meteor/client/meteor/login/saml.ts index 1949455e907a4..1dcb48d91f067 100644 --- a/apps/meteor/client/meteor/login/saml.ts +++ b/apps/meteor/client/meteor/login/saml.ts @@ -4,6 +4,7 @@ import { Meteor } from 'meteor/meteor'; import { type LoginCallback, callLoginMethod, handleLogin } from '../../lib/2fa/overrideLoginMethod'; import { absoluteUrl } from '../../lib/absoluteUrl'; +import { STORAGE_KEYS, removeStoredItem } from '../../lib/sdk/storage'; import { settings } from '../../lib/settings'; declare module 'meteor/meteor' { @@ -73,7 +74,7 @@ Meteor.logout = async function (...args) { // Remove the userId from the client to prevent calls to the server while the logout is processed. // If the logout fails, the userId will be reloaded on the resume call - Accounts.storageLocation.removeItem(Accounts.USER_ID_KEY); + removeStoredItem(STORAGE_KEYS.USER_ID); // A nasty bounce: 'result' has the SAML LogoutRequest but we need a proper 302 to redirected from the server. window.location.replace(absoluteUrl(`_saml/sloRedirect/${provider}/?redirect=${encodeURIComponent(result)}`));