Skip to content
142 changes: 142 additions & 0 deletions app/sagas/__tests__/login.forcedLogout.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
jest.mock('../../lib/methods/getPermissions', () => ({
getPermissions: jest.fn()
}));

jest.mock('../../lib/methods/enterpriseModules', () => ({
getEnterpriseModules: jest.fn(),
isOmnichannelModuleAvailable: jest.fn(() => false),
isOmnichannelStatusAvailable: jest.fn(() => false),
isVoipModuleAvailable: jest.fn(() => false)
}));

jest.mock('../../lib/methods/getCustomEmojis', () => ({
getCustomEmojis: jest.fn()
}));

jest.mock('../../lib/methods/getRoles', () => ({
getRoles: jest.fn()
}));

jest.mock('../../lib/methods/getSlashCommands', () => ({
getSlashCommands: jest.fn()
}));

jest.mock('../../lib/methods/getSettings', () => ({
subscribeSettings: jest.fn()
}));

jest.mock('../../lib/methods/getUsersPresence', () => ({
getUserPresence: jest.fn(),
refreshDmUsersPresence: jest.fn(),
subscribeUsersPresence: jest.fn()
}));

jest.mock('../../lib/services/restApi', () => ({
getUsersRoles: jest.fn(() => []),
registerPushToken: jest.fn(),
saveUserProfile: jest.fn(),
setUserPresenceAway: jest.fn()
}));

jest.mock('../../lib/services/connect', () => ({
disconnect: jest.fn(),
login: jest.fn(),
loginWithPassword: jest.fn()
}));

jest.mock('../../lib/methods/logout', () => ({
logout: jest.fn(),
removeServerData: jest.fn(),
removeServerDatabase: jest.fn()
}));

jest.mock('../../lib/services/voip/MediaSessionInstance', () => ({
mediaSessionInstance: { init: jest.fn(), reset: jest.fn() }
}));

jest.mock('../../lib/services/voip/MediaSessionStore', () => ({
mediaSessionStore: { getCurrentInstance: jest.fn(() => null) }
}));

jest.mock('../../lib/services/voip/isInActiveVoipCall', () => ({
isInActiveVoipCall: jest.fn(() => false)
}));

jest.mock('../../lib/methods/helpers/localAuthentication', () => ({
localAuthenticate: jest.fn()
}));

jest.mock('../../lib/methods/helpers/info', () => ({
showErrorAlert: jest.fn()
}));

jest.mock('../../lib/services/sdk', () => ({
__esModule: true,
default: {
current: { client: { host: '' } },
subscribe: jest.fn()
}
}));

jest.mock('../../lib/methods/helpers/log', () => ({
...jest.requireActual('../../lib/methods/helpers/log'),
__esModule: true,
default: jest.fn()
}));

const mockServersQuery = { query: jest.fn(() => ({ fetch: jest.fn() })) };

jest.mock('../../lib/database', () => ({
__esModule: true,
default: {
active: { get: jest.fn() },
servers: {
get: jest.fn(() => mockServersQuery),
write: jest.fn(async (block: () => Promise<void>) => block())
}
}
}));

import loginRoot from '../login';
import { logout } from '../../actions/login';
import { selectServerSuccess } from '../../actions/server';
import UserPreferences from '../../lib/methods/userPreferences';
import { TOKEN_KEY } from '../../lib/constants/keys';
import { cancelSagaTasks, createRecordingStore, flushSagaMicrotasks } from '../../lib/testUtils/sagaStore';

afterEach(cancelSagaTasks);

const LOGGED_OUT_SERVER = 'https://logged-out.rocket.chat';
const OTHER_SERVER = 'https://other.rocket.chat';

const setRemainingServers = (servers: { id: string }[]): void => {
mockServersQuery.query.mockReturnValue({ fetch: jest.fn(() => Promise.resolve(servers)) });
};

const runForcedLogout = async (): Promise<string | null> => {
const { store } = createRecordingStore(loginRoot);
store.dispatch(selectServerSuccess({ server: LOGGED_OUT_SERVER, version: '7.0.0', name: 'Logged out' }));
store.dispatch(logout(true, 'Logged_out_by_server'));
await flushSagaMicrotasks();
return store.getState().server.previousServer;
};

describe('login saga — a logout forced by the server', () => {
beforeEach(() => {
UserPreferences.removeItem(`${TOKEN_KEY}-${OTHER_SERVER}`);
jest.clearAllMocks();
});

it('points previousServer at another logged in workspace, so the user can leave NewServerView', async () => {
setRemainingServers([{ id: OTHER_SERVER }]);
UserPreferences.setString(`${TOKEN_KEY}-${OTHER_SERVER}`, 'user-id');

expect(await runForcedLogout()).toBe(OTHER_SERVER);
});

it('leaves previousServer unset when no other workspace is logged in', async () => {
setRemainingServers([{ id: OTHER_SERVER }]);

expect(await runForcedLogout()).toBeNull();
});
});
52 changes: 19 additions & 33 deletions app/sagas/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Q } from '@nozbe/watermelondb';
import dayjs from '../lib/dayjs';
import * as types from '../actions/actionsTypes';
import { appStart } from '../actions/app';
import { selectServerRequest, serverFinishAdd } from '../actions/server';
import { selectServerRequest, serverFinishAdd, serverInitAdd } from '../actions/server';
import { loginFailure, loginSuccess, logout as logoutAction, setUser } from '../actions/login';
import { roomsRequest } from '../actions/rooms';
import log, { events, logEvent } from '../lib/methods/helpers/log';
Expand Down Expand Up @@ -369,6 +369,12 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
}
};

const findLoggedInServer = function* findLoggedInServer() {
const serversCollection = database.servers.get('servers');
const servers = yield serversCollection.query().fetch();
return servers.find(({ id }) => UserPreferences.getString(`${TOKEN_KEY}-${id}`));
};

const handleLogout = function* handleLogout({ forcedByServer, message }) {
yield put(encryptionStop());
yield put(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Logging_out') }));
Expand All @@ -377,32 +383,24 @@ const handleLogout = function* handleLogout({ forcedByServer, message }) {
try {
yield call(logoutCall, { server });

const loggedInServer = yield call(findLoggedInServer);

// if the user was logged out by the server
if (forcedByServer) {
if (loggedInServer) {
yield put(serverInitAdd(loggedInServer.id));
}
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
if (message) {
showErrorAlert(I18n.t(message), I18n.t('Oops'));
}
yield delay(300);
EventEmitter.emit('NewServer', { server });
} else {
const serversDB = database.servers;
// all servers
const serversCollection = serversDB.get('servers');
const servers = yield serversCollection.query().fetch();

// see if there're other logged in servers and selects first one
if (servers.length > 0) {
for (let i = 0; i < servers.length; i += 1) {
const { id: newServer, version } = servers[i];
const token = UserPreferences.getString(`${TOKEN_KEY}-${newServer}`);
if (token) {
yield put(selectServerRequest(newServer, version));
return;
}
}
if (loggedInServer) {
yield put(selectServerRequest(loggedInServer.id, loggedInServer.version));
return;
}
// if there's no servers, go outside
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
}
} catch (e) {
Expand Down Expand Up @@ -453,23 +451,11 @@ const handleDeleteAccount = function* handleDeleteAccount() {
try {
yield call(removeServerData, { server });
yield call(removeServerDatabase, { server });
const serversDB = database.servers;
// all servers
const serversCollection = serversDB.get('servers');
const servers = yield serversCollection.query().fetch();

// see if there're other logged in servers and selects first one
if (servers.length > 0) {
for (let i = 0; i < servers.length; i += 1) {
const { id: newServer, version } = servers[i];
const token = UserPreferences.getString(`${TOKEN_KEY}-${newServer}`);
if (token) {
yield put(selectServerRequest(newServer, version));
return;
}
}
const loggedInServer = yield call(findLoggedInServer);
if (loggedInServer) {
yield put(selectServerRequest(loggedInServer.id, loggedInServer.version));
return;
}
// if there's no servers, go outside
disconnect();
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} catch (e) {
Expand Down
Loading