Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions apps/meteor/app/utils/client/lib/RestApiClient.ts
Original file line number Diff line number Diff line change
@@ -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():
Expand All @@ -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;
Expand Down
28 changes: 14 additions & 14 deletions apps/meteor/client/lib/e2ee/rocketchat.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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),
};
}

Expand All @@ -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();
Expand Down Expand Up @@ -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({
Expand All @@ -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;
Expand All @@ -438,8 +438,8 @@ class E2E extends Emitter {
async changePassword(newPassword: string): Promise<void> {
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);
}
}

Expand All @@ -464,13 +464,13 @@ class E2E extends Emitter {

async loadKeys({ public_key, private_key }: { public_key: string; private_key: string }): Promise<void> {
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);
Expand All @@ -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');
Expand All @@ -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');
Expand All @@ -518,7 +518,7 @@ class E2E extends Emitter {

async createRandomPassword(): Promise<string> {
const randomPassword = await generatePassphrase();
Accounts.storageLocation.setItem('e2e.randomPassword', randomPassword);
setStoredItem(STORAGE_KEYS.E2EE_RANDOM_PASSWORD, randomPassword);
return randomPassword;
}

Expand Down
3 changes: 2 additions & 1 deletion apps/meteor/client/lib/sdk/ddpSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<void> | undefined;

Expand Down
22 changes: 22 additions & 0 deletions apps/meteor/client/lib/sdk/storage.ts
Original file line number Diff line number Diff line change
@@ -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);
3 changes: 2 additions & 1 deletion apps/meteor/client/meteor/login/saml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' {
Expand Down Expand Up @@ -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)}`));
Expand Down
Loading