Merge pull request #2571 from Bilb/fix-private-sogs-authentication

fix: include auth sogs headers for join room and file download
pull/2582/head
Audric Ackermann 3 years ago committed by GitHub
commit cb1d67be64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -42,12 +42,18 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
} }
public componentDidMount() { public componentDidMount() {
document.addEventListener('keyup', this.onEnterPressed);
setTimeout(() => { setTimeout(() => {
// tslint:disable-next-line: no-unused-expression // tslint:disable-next-line: no-unused-expression
this.passportInput && this.passportInput.focus(); this.passportInput && this.passportInput.focus();
}, 1); }, 1);
} }
public componentWillUnmount() {
document.removeEventListener('keyup', this.onEnterPressed);
}
public render() { public render() {
const { passwordAction } = this.props; const { passwordAction } = this.props;
let placeholders: Array<string> = []; let placeholders: Array<string> = [];
@ -93,7 +99,8 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.passportInput = input; this.passportInput = input;
}} }}
placeholder={placeholders[0]} placeholder={placeholders[0]}
onKeyUp={this.onPasswordInput} onChange={this.onPasswordInput}
onPaste={this.onPasswordInput}
data-testid="password-input" data-testid="password-input"
/> />
{passwordAction !== 'enter' && passwordAction !== 'remove' && ( {passwordAction !== 'enter' && passwordAction !== 'remove' && (
@ -101,7 +108,8 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
type="password" type="password"
id="password-modal-input-confirm" id="password-modal-input-confirm"
placeholder={placeholders[1]} placeholder={placeholders[1]}
onKeyUp={this.onPasswordConfirmInput} onChange={this.onPasswordConfirmInput}
onPaste={this.onPasswordConfirmInput}
data-testid="password-input-confirm" data-testid="password-input-confirm"
/> />
)} )}
@ -110,7 +118,8 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
type="password" type="password"
id="password-modal-input-reconfirm" id="password-modal-input-reconfirm"
placeholder={placeholders[2]} placeholder={placeholders[2]}
onKeyUp={this.onPasswordRetypeInput} onPaste={this.onPasswordRetypeInput}
onChange={this.onPasswordRetypeInput}
data-testid="password-input-reconfirm" data-testid="password-input-reconfirm"
/> />
)} )}
@ -258,6 +267,13 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.closeDialog(); this.closeDialog();
} }
private async onEnterPressed(event: any) {
if (event.key === 'Enter') {
event.stopPropagation();
return this.setPassword();
}
}
private async handleActionEnter(enteredPassword: string) { private async handleActionEnter(enteredPassword: string) {
// be sure the password is valid // be sure the password is valid
if (!this.validatePassword(enteredPassword)) { if (!this.validatePassword(enteredPassword)) {
@ -321,30 +337,18 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
window.inboxStore?.dispatch(sessionPassword(null)); window.inboxStore?.dispatch(sessionPassword(null));
} }
private async onPasswordInput(event: any) { private onPasswordInput(event: any) {
if (event.key === 'Enter') {
return this.setPassword();
}
const currentPasswordEntered = event.target.value; const currentPasswordEntered = event.target.value;
this.setState({ currentPasswordEntered }); this.setState({ currentPasswordEntered });
} }
private async onPasswordConfirmInput(event: any) { private onPasswordConfirmInput(event: any) {
if (event.key === 'Enter') {
return this.setPassword();
}
const currentPasswordConfirmEntered = event.target.value; const currentPasswordConfirmEntered = event.target.value;
this.setState({ currentPasswordConfirmEntered }); this.setState({ currentPasswordConfirmEntered });
} }
private async onPasswordRetypeInput(event: any) { private onPasswordRetypeInput(event: any) {
if (event.key === 'Enter') {
return this.setPassword();
}
const currentPasswordRetypeEntered = event.target.value; const currentPasswordRetypeEntered = event.target.value;
this.setState({ currentPasswordRetypeEntered }); this.setState({ currentPasswordRetypeEntered });
} }
} }

@ -97,6 +97,7 @@ export const SettingsCategoryPrivacy = (props: {
displayPasswordModal('change', props.onPasswordUpdated); displayPasswordModal('change', props.onPasswordUpdated);
}} }}
buttonText={window.i18n('changePassword')} buttonText={window.i18n('changePassword')}
dataTestId="change-password-settings-button"
/> />
)} )}
{props.hasPassword && ( {props.hasPassword && (
@ -108,6 +109,7 @@ export const SettingsCategoryPrivacy = (props: {
}} }}
buttonColor={SessionButtonColor.Danger} buttonColor={SessionButtonColor.Danger}
buttonText={window.i18n('removePassword')} buttonText={window.i18n('removePassword')}
dataTestId="remove-password-settings-button"
/> />
)} )}
</> </>

@ -142,36 +142,33 @@ const defaultServerPublicKey = 'a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f4
const defaultRoom = `${defaultServer}/main?public_key=${defaultServerPublicKey}`; const defaultRoom = `${defaultServer}/main?public_key=${defaultServerPublicKey}`;
const loadDefaultRoomsSingle = () => const loadDefaultRoomsSingle = () =>
allowOnlyOneAtATime( allowOnlyOneAtATime('loadDefaultRoomsSingle', async () => {
'loadDefaultRoomsSingle', const roomInfos = parseOpenGroupV2(defaultRoom);
async (): Promise<Array<OpenGroupV2InfoJoinable>> => { if (roomInfos) {
const roomInfos = parseOpenGroupV2(defaultRoom); try {
if (roomInfos) { const roomsGot = await getAllRoomInfos(roomInfos);
try {
const roomsGot = await getAllRoomInfos(roomInfos); if (!roomsGot) {
return [];
if (!roomsGot) {
return [];
}
return roomsGot.map(room => {
return {
...room,
completeUrl: getCompleteUrlFromRoom({
serverUrl: roomInfos.serverUrl,
serverPublicKey: roomInfos.serverPublicKey,
roomId: room.id,
}),
};
});
} catch (e) {
window?.log?.warn('loadDefaultRoomloadDefaultRoomssIfNeeded failed', e);
} }
return [];
return roomsGot.map(room => {
return {
...room,
completeUrl: getCompleteUrlFromRoom({
serverUrl: roomInfos.serverUrl,
serverPublicKey: roomInfos.serverPublicKey,
roomId: room.id,
}),
};
});
} catch (e) {
window?.log?.warn('loadDefaultRoomloadDefaultRoomssIfNeeded failed', e);
} }
return []; return [];
} }
); return [];
});
/** /**
* Load to the cache all the details of the room of the default opengroupv2 server * Load to the cache all the details of the room of the default opengroupv2 server

@ -45,7 +45,7 @@ export class OpenGroupManagerV2 {
serverUrl: string, serverUrl: string,
roomId: string, roomId: string,
publicKey: string publicKey: string
): Promise<ConversationModel> { ): Promise<ConversationModel | undefined> {
const oneAtaTimeStr = `oneAtaTimeOpenGroupV2Join:${serverUrl}${roomId}`; const oneAtaTimeStr = `oneAtaTimeOpenGroupV2Join:${serverUrl}${roomId}`;
return allowOnlyOneAtATime(oneAtaTimeStr, async () => { return allowOnlyOneAtATime(oneAtaTimeStr, async () => {
return this.attemptConnectionV2(serverUrl, roomId, publicKey); return this.attemptConnectionV2(serverUrl, roomId, publicKey);

@ -5,7 +5,7 @@ import { OpenGroupData, OpenGroupV2Room } from '../../../../data/opengroups';
import AbortController, { AbortSignal } from 'abort-controller'; import AbortController, { AbortSignal } from 'abort-controller';
import { batchGlobalIsSuccess } from './sogsV3BatchPoll'; import { batchGlobalIsSuccess } from './sogsV3BatchPoll';
export const capabilitiesFetchForServer = async ( const capabilitiesFetchForServer = async (
serverUrl: string, serverUrl: string,
serverPubKey: string, serverPubKey: string,
abortSignal: AbortSignal abortSignal: AbortSignal
@ -13,7 +13,8 @@ export const capabilitiesFetchForServer = async (
const endpoint = '/capabilities'; const endpoint = '/capabilities';
const method = 'GET'; const method = 'GET';
const serverPubkey = serverPubKey; const serverPubkey = serverPubKey;
const blinded = false; // for capabilities, blinding is always false as the request will fail if the server requires blinding // for the capabilities call, we require blinded to be ON now. A sogs with blinding disabled will still allow this call and verify the blinded signature
const blinded = true;
const capabilityHeaders = await OpenGroupPollingUtils.getOurOpenGroupHeaders( const capabilityHeaders = await OpenGroupPollingUtils.getOurOpenGroupHeaders(
serverPubkey, serverPubkey,
endpoint, endpoint,
@ -33,7 +34,6 @@ export const capabilitiesFetchForServer = async (
serverPubkey, serverPubkey,
serverUrl, serverUrl,
stringifiedBody: null, stringifiedBody: null,
doNotIncludeOurSogsHeaders: true, // the first capabilities needs to not have any authentification to pass on a blinding-required sogs,
headers: null, headers: null,
throwErrors: false, throwErrors: false,
}); });

@ -1,6 +1,10 @@
import AbortController, { AbortSignal } from 'abort-controller'; import AbortController, { AbortSignal } from 'abort-controller';
import { isUndefined, toNumber } from 'lodash'; import { isUndefined, toNumber } from 'lodash';
import { OpenGroupV2Room, OpenGroupV2RoomWithImageID } from '../../../../data/opengroups'; import {
OpenGroupData,
OpenGroupV2Room,
OpenGroupV2RoomWithImageID,
} from '../../../../data/opengroups';
import { MIME } from '../../../../types'; import { MIME } from '../../../../types';
import { processNewAttachment } from '../../../../types/MessageAttachment'; import { processNewAttachment } from '../../../../types/MessageAttachment';
import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface'; import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface';
@ -16,7 +20,6 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
serverPubkey: string; serverPubkey: string;
blinded: boolean; blinded: boolean;
abortSignal: AbortSignal; abortSignal: AbortSignal;
doNotIncludeOurSogsHeaders?: boolean;
headers: Record<string, any> | null; headers: Record<string, any> | null;
roomId: string; roomId: string;
fileId: string; fileId: string;
@ -28,7 +31,6 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
blinded, blinded,
abortSignal, abortSignal,
headers: includedHeaders, headers: includedHeaders,
doNotIncludeOurSogsHeaders,
roomId, roomId,
fileId, fileId,
throwError, throwError,
@ -41,15 +43,13 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
throw new Error('endpoint needs a leading /'); throw new Error('endpoint needs a leading /');
} }
const builtUrl = new URL(`${serverUrl}${endpoint}`); const builtUrl = new URL(`${serverUrl}${endpoint}`);
let headersWithSogsHeadersIfNeeded = doNotIncludeOurSogsHeaders let headersWithSogsHeadersIfNeeded = await OpenGroupPollingUtils.getOurOpenGroupHeaders(
? {} serverPubkey,
: await OpenGroupPollingUtils.getOurOpenGroupHeaders( endpoint,
serverPubkey, method,
endpoint, blinded,
method, stringifiedBody
blinded, );
stringifiedBody
);
if (isUndefined(headersWithSogsHeadersIfNeeded)) { if (isUndefined(headersWithSogsHeadersIfNeeded)) {
return null; return null;
@ -98,12 +98,15 @@ export async function sogsV3FetchPreviewAndSaveIt(roomInfos: OpenGroupV2RoomWith
return; return;
} }
const room = OpenGroupData.getV2OpenGroupRoom(convoId);
const blinded = roomHasBlindEnabled(room);
// make sure this runs only once for each rooms. // make sure this runs only once for each rooms.
// we don't want to trigger one of those on each setPollInfo resultsas it happens on each batch poll. // we don't want to trigger one of those on each setPollInfo results as it happens on each batch poll.
const oneAtAtimeResult = (await allowOnlyOneAtATime( const oneAtAtimeResult = await allowOnlyOneAtATime(
`sogsV3FetchPreview-${serverUrl}-${roomId}`, `sogsV3FetchPreview-${serverUrl}-${roomId}`,
() => sogsV3FetchPreview(roomInfos) () => sogsV3FetchPreview(roomInfos, blinded)
)) as Uint8Array | null; // force the return type as allowOnlyOneAtATime does not keep it );
if (!oneAtAtimeResult || !oneAtAtimeResult?.byteLength) { if (!oneAtAtimeResult || !oneAtAtimeResult?.byteLength) {
window?.log?.warn('sogsV3FetchPreviewAndSaveIt failed for room: ', roomId); window?.log?.warn('sogsV3FetchPreviewAndSaveIt failed for room: ', roomId);
@ -139,7 +142,7 @@ export async function sogsV3FetchPreviewAndSaveIt(roomInfos: OpenGroupV2RoomWith
* @returns the fetchedData in base64 * @returns the fetchedData in base64
*/ */
export async function sogsV3FetchPreviewBase64(roomInfos: OpenGroupV2RoomWithImageID) { export async function sogsV3FetchPreviewBase64(roomInfos: OpenGroupV2RoomWithImageID) {
const fetched = await sogsV3FetchPreview(roomInfos); const fetched = await sogsV3FetchPreview(roomInfos, true); // left pane are session official default rooms, which do require blinded
if (fetched && fetched.byteLength) { if (fetched && fetched.byteLength) {
return callUtilsWorker('arrayBufferToStringBase64', fetched); return callUtilsWorker('arrayBufferToStringBase64', fetched);
} }
@ -155,7 +158,8 @@ export async function sogsV3FetchPreviewBase64(roomInfos: OpenGroupV2RoomWithIma
* Those default rooms do not have a conversation associated with them, as they are not joined yet * Those default rooms do not have a conversation associated with them, as they are not joined yet
*/ */
const sogsV3FetchPreview = async ( const sogsV3FetchPreview = async (
roomInfos: OpenGroupV2RoomWithImageID roomInfos: OpenGroupV2RoomWithImageID,
blinded: boolean
): Promise<Uint8Array | null> => { ): Promise<Uint8Array | null> => {
if (!roomInfos || !roomInfos.imageID) { if (!roomInfos || !roomInfos.imageID) {
return null; return null;
@ -164,11 +168,10 @@ const sogsV3FetchPreview = async (
// not a batch call yet as we need to exclude headers for this call for now // not a batch call yet as we need to exclude headers for this call for now
const fetched = await fetchBinaryFromSogsWithOnionV4({ const fetched = await fetchBinaryFromSogsWithOnionV4({
abortSignal: new AbortController().signal, abortSignal: new AbortController().signal,
blinded: false, blinded,
headers: null, headers: null,
serverPubkey: roomInfos.serverPublicKey, serverPubkey: roomInfos.serverPublicKey,
serverUrl: roomInfos.serverUrl, serverUrl: roomInfos.serverUrl,
doNotIncludeOurSogsHeaders: true,
roomId: roomInfos.roomId, roomId: roomInfos.roomId,
fileId: roomInfos.imageID, fileId: roomInfos.imageID,
throwError: false, throwError: false,
@ -198,7 +201,6 @@ export const sogsV3FetchFileByFileID = async (
headers: null, headers: null,
serverPubkey: roomInfos.serverPublicKey, serverPubkey: roomInfos.serverPublicKey,
serverUrl: roomInfos.serverUrl, serverUrl: roomInfos.serverUrl,
doNotIncludeOurSogsHeaders: true,
roomId: roomInfos.roomId, roomId: roomInfos.roomId,
fileId, fileId,
throwError: true, throwError: true,

@ -11,7 +11,7 @@ import {
export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => { export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => {
const result = await OnionSending.sendJsonViaOnionV4ToSogs({ const result = await OnionSending.sendJsonViaOnionV4ToSogs({
blinded: false, blinded: true,
endpoint: '/rooms', endpoint: '/rooms',
method: 'GET', method: 'GET',
serverPubkey: roomInfos.serverPublicKey, serverPubkey: roomInfos.serverPublicKey,
@ -19,7 +19,6 @@ export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => {
abortSignal: new AbortController().signal, abortSignal: new AbortController().signal,
serverUrl: roomInfos.serverUrl, serverUrl: roomInfos.serverUrl,
headers: null, headers: null,
doNotIncludeOurSogsHeaders: true,
throwErrors: false, throwErrors: false,
}); });
@ -91,7 +90,6 @@ export async function openGroupV2GetRoomInfoViaOnionV4({
stringifiedBody: null, stringifiedBody: null,
serverPubkey, serverPubkey,
headers: null, headers: null,
doNotIncludeOurSogsHeaders: true,
throwErrors: false, throwErrors: false,
}); });
const room = result?.body as Record<string, any> | undefined; const room = result?.body as Record<string, any> | undefined;

@ -146,9 +146,7 @@ export interface SnodeFromSeed {
} }
const getSnodeListFromSeednodeOneAtAtime = async (seedNodes: Array<string>) => const getSnodeListFromSeednodeOneAtAtime = async (seedNodes: Array<string>) =>
allowOnlyOneAtATime('getSnodeListFromSeednode', () => allowOnlyOneAtATime('getSnodeListFromSeednode', () => getSnodeListFromSeednode(seedNodes));
getSnodeListFromSeednode(seedNodes)
) as Promise<Array<SnodeFromSeed>>;
/** /**
* This call will try 4 times to contact a seed nodes (random) and get the snode list from it. * This call will try 4 times to contact a seed nodes (random) and get the snode list from it.

@ -338,6 +338,13 @@ async function processAnyOtherErrorOnPath(
if (status !== 200) { if (status !== 200) {
window?.log?.warn(`[path] Got status: ${status}`); window?.log?.warn(`[path] Got status: ${status}`);
if (status === 404 || status === 400) {
window?.log?.warn(
'processAnyOtherErrorOnPathgot 404 or 400, probably a dead sogs. Skipping bad path update'
);
return;
}
// If we have a specific node in fault we can exclude just this node. // If we have a specific node in fault we can exclude just this node.
if (ciphertext?.startsWith(NEXT_NODE_NOT_FOUND_PREFIX)) { if (ciphertext?.startsWith(NEXT_NODE_NOT_FOUND_PREFIX)) {
const nodeNotFound = ciphertext.substr(NEXT_NODE_NOT_FOUND_PREFIX.length); const nodeNotFound = ciphertext.substr(NEXT_NODE_NOT_FOUND_PREFIX.length);

@ -277,7 +277,6 @@ async function sendJsonViaOnionV4ToSogs(sendOptions: {
method: string; method: string;
stringifiedBody: string | null; stringifiedBody: string | null;
abortSignal: AbortSignal; abortSignal: AbortSignal;
doNotIncludeOurSogsHeaders?: boolean;
headers: Record<string, any> | null; headers: Record<string, any> | null;
throwErrors: boolean; throwErrors: boolean;
}): Promise<OnionV4JSONSnodeResponse | null> { }): Promise<OnionV4JSONSnodeResponse | null> {
@ -290,22 +289,19 @@ async function sendJsonViaOnionV4ToSogs(sendOptions: {
stringifiedBody, stringifiedBody,
abortSignal, abortSignal,
headers: includedHeaders, headers: includedHeaders,
doNotIncludeOurSogsHeaders,
throwErrors, throwErrors,
} = sendOptions; } = sendOptions;
if (!endpoint.startsWith('/')) { if (!endpoint.startsWith('/')) {
throw new Error('endpoint needs a leading /'); throw new Error('endpoint needs a leading /');
} }
const builtUrl = new URL(`${serverUrl}${endpoint}`); const builtUrl = new URL(`${serverUrl}${endpoint}`);
let headersWithSogsHeadersIfNeeded = doNotIncludeOurSogsHeaders let headersWithSogsHeadersIfNeeded = await OpenGroupPollingUtils.getOurOpenGroupHeaders(
? {} serverPubkey,
: await OpenGroupPollingUtils.getOurOpenGroupHeaders( endpoint,
serverPubkey, method,
endpoint, blinded,
method, stringifiedBody
blinded, );
stringifiedBody
);
if (!headersWithSogsHeadersIfNeeded) { if (!headersWithSogsHeadersIfNeeded) {
return null; return null;

@ -92,6 +92,18 @@ const decodeV4Response = (snodeResponse: SnodeResponseV4): DecodedResponseV4 | u
break; break;
case 'application/octet-stream': case 'application/octet-stream':
break; break;
case 'text/html; charset=utf-8':
try {
window?.log?.warn(
'decodeV4Response - received raw body of type "text/html; charset=utf-8": ',
to_string(bodyBinary)
);
} catch (e) {
window?.log?.warn(
'decodeV4Response - received raw body of type "text/html; charset=utf-8" but not a string'
);
}
break;
default: default:
window?.log?.warn( window?.log?.warn(
'decodeV4Response - No or unknown content-type information for response: ', 'decodeV4Response - No or unknown content-type information for response: ',

@ -17,11 +17,11 @@ export class TaskTimedOutError extends Error {
// one action resolves all // one action resolves all
const oneAtaTimeRecord: Record<string, Promise<any>> = {}; const oneAtaTimeRecord: Record<string, Promise<any>> = {};
export async function allowOnlyOneAtATime( export async function allowOnlyOneAtATime<T>(
name: string, name: string,
process: () => Promise<any>, process: () => Promise<T | undefined>,
timeoutMs?: number timeoutMs?: number
) { ): Promise<T> {
// if currently not in progress // if currently not in progress
if (oneAtaTimeRecord[name] === undefined) { if (oneAtaTimeRecord[name] === undefined) {
// set lock // set lock
@ -37,7 +37,7 @@ export async function allowOnlyOneAtATime(
}, timeoutMs); }, timeoutMs);
} }
// do actual work // do actual work
let innerRetVal; let innerRetVal: T | undefined;
try { try {
innerRetVal = await process(); innerRetVal = await process();
} catch (e) { } catch (e) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -10,6 +10,7 @@ import {
waitForReadableMessageWithText, waitForReadableMessageWithText,
waitForTestIdWithText, waitForTestIdWithText,
} from './utils'; } from './utils';
import { sleepFor } from '../../session/utils/Promise';
let windows: Array<Page> = []; let windows: Array<Page> = [];
test.beforeEach(beforeAllClean); test.beforeEach(beforeAllClean);
@ -50,7 +51,9 @@ test('Disappearing Messages', async () => {
'readable-message', 'readable-message',
'You set the disappearing message timer to 5 seconds' 'You set the disappearing message timer to 5 seconds'
); );
await sleepFor(2000);
// Check top right hand corner indicator // Check top right hand corner indicator
await waitForTestIdWithText(windowA, 'disappearing-messages-indicator', '5 seconds'); await waitForTestIdWithText(windowA, 'disappearing-messages-indicator', '5 seconds');
// Send message // Send message
// Wait for tick of confirmation // Wait for tick of confirmation
@ -87,7 +90,7 @@ test('Disappearing Messages', async () => {
`${userA.userName} set the disappearing message timer to 5 seconds` `${userA.userName} set the disappearing message timer to 5 seconds`
); );
// Wait 5 seconds // Wait 5 seconds
await waitForMatchingText(windowB, `${userA.userName} disabled disappearing messages`); await waitForMatchingText(windowB, `${userA.userName} has turned off disappearing messages.`);
// verify message is deleted in windowB // verify message is deleted in windowB
const errorDesc2 = 'Should not be found'; const errorDesc2 = 'Should not be found';
try { try {

@ -1,4 +1,5 @@
import { _electron, Page, test } from '@playwright/test'; import { _electron, Page, test } from '@playwright/test';
import { sleepFor } from '../../session/utils/Promise';
import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach'; import { beforeAllClean, forceCloseAllWindows } from './setup/beforeEach';
import { newUser } from './setup/new_user'; import { newUser } from './setup/new_user';
import { openAppAndWait } from './setup/open'; import { openAppAndWait } from './setup/open';
@ -36,42 +37,40 @@ test.describe('Password checks', () => {
await clickOnTestIdWithText(window, 'set-password-button'); await clickOnTestIdWithText(window, 'set-password-button');
// Enter password // Enter password
await typeIntoInput(window, 'password-input', testPassword); await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete');
// Confirm password // Confirm password
await typeIntoInput(window, 'password-input-confirm', testPassword); await typeIntoInput(window, 'password-input-confirm', testPassword);
await window.keyboard.press('Delete'); // Click Done
// Click OK await clickOnMatchingText(window, 'Done');
await clickOnMatchingText(window, 'OK');
// await window.keyboard.press('Enter');
// Check toast notification // Check toast notification
await waitForTestIdWithText( await waitForTestIdWithText(
window, window,
'session-toast', 'session-toast',
'Your password has been set. Please keep it safe' 'Your password has been set. Please keep it safe.'
); );
// Click on settings tab
await sleepFor(300);
await clickOnTestIdWithText(window, 'settings-section');
// Type password into input field // Type password into input field
await typeIntoInput(window, 'password-input', testPassword); await typeIntoInput(window, 'password-input', testPassword);
// Click OK
await clickOnMatchingText(window, 'OK'); // Click Done
await clickOnMatchingText(window, 'Done');
await clickOnTestIdWithText(window, 'settings-section');
// Change password // Change password
await clickOnMatchingText(window, 'Change Password'); await clickOnTestIdWithText(window, 'change-password-settings-button', 'Change Password');
console.warn('clicked Change Password');
// Enter old password // Enter old password
await typeIntoInput(window, 'password-input', testPassword); await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete');
// Enter new password // Enter new password
await typeIntoInput(window, 'password-input-confirm', newTestPassword); await typeIntoInput(window, 'password-input-confirm', newTestPassword);
await window.keyboard.press('Delete');
// await window.fill('#password-modal-input-confirm', newTestPassword);
await window.keyboard.press('Tab'); await window.keyboard.press('Tab');
// Confirm new password // Confirm new password
await typeIntoInput(window, 'password-input-reconfirm', newTestPassword); await typeIntoInput(window, 'password-input-reconfirm', newTestPassword);
await window.keyboard.press('Delete');
// await window.fill('#password-modal-input-reconfirm', newTestPassword);
// Press enter on keyboard // Press enter on keyboard
await window.keyboard.press('Enter'); await window.keyboard.press('Enter');
// Select OK
await clickOnMatchingText(window, 'OK');
// Check toast notification for 'changed password' // Check toast notification for 'changed password'
await waitForTestIdWithText( await waitForTestIdWithText(
window, window,
@ -92,36 +91,44 @@ test.describe('Password checks', () => {
await clickOnMatchingText(window, 'Set Password'); await clickOnMatchingText(window, 'Set Password');
// Enter password // Enter password
await typeIntoInput(window, 'password-input', testPassword); await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete');
// Confirm password // Confirm password
await typeIntoInput(window, 'password-input-confirm', testPassword); await typeIntoInput(window, 'password-input-confirm', testPassword);
await window.keyboard.press('Delete'); // Click Done
// Click OK
await window.keyboard.press('Enter'); await window.keyboard.press('Enter');
// // Click on settings tab
await sleepFor(100);
await clickOnTestIdWithText(window, 'settings-section');
// Type password into input field // Type password into input field
await sleepFor(100);
await typeIntoInput(window, 'password-input', testPassword); await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete'); // Click Done
// Click OK await clickOnMatchingText(window, 'Done');
await clickOnMatchingText(window, 'OK'); await sleepFor(100);
// Navigate away from settings tab await window.mouse.click(0, 0);
await clickOnTestIdWithText(window, 'message-section'); await clickOnTestIdWithText(window, 'message-section');
await sleepFor(100);
// // Click on settings tab // // Click on settings tab
await sleepFor(1000);
await clickOnTestIdWithText(window, 'settings-section'); await clickOnTestIdWithText(window, 'settings-section');
// // Try with incorrect password // // Try with incorrect password
await typeIntoInput(window, 'password-input', '0000'); await typeIntoInput(window, 'password-input', '000000');
await window.keyboard.press('Delete');
// Confirm // Confirm
await clickOnMatchingText(window, 'OK'); await clickOnMatchingText(window, 'Done');
// // invalid password banner showing? // // invalid password banner showing?
await waitForMatchingText(window, 'Invalid password'); await waitForMatchingText(window, 'Invalid password');
// // Empty password // // Empty password
// // Navigate away from settings tab // // Navigate away from settings tab
await window.mouse.click(0, 0);
await sleepFor(100);
await clickOnTestIdWithText(window, 'message-section'); await clickOnTestIdWithText(window, 'message-section');
await sleepFor(100);
// // Click on settings tab // // Click on settings tab
await clickOnTestIdWithText(window, 'settings-section'); await clickOnTestIdWithText(window, 'settings-section');
// // No password entered // // No password entered
await clickOnMatchingText(window, 'OK'); await clickOnMatchingText(window, 'Done');
// // Banner should ask for password to be entered // // Banner should ask for password to be entered
await waitForMatchingText(window, 'Please enter your password'); await waitForMatchingText(window, 'Enter password');
}); });
}); });

@ -15,15 +15,17 @@ test('Switch themes', async () => {
windows = windowLoggedIn.windows; windows = windowLoggedIn.windows;
const [windowA] = windows; const [windowA] = windows;
// Check light theme colour is correct // Check light theme colour is correct
const lightThemeColor = windowA.locator('.inbox.index'); const darkThemeColor = windowA.locator('.inbox.index');
await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)'); await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(27, 27, 27)');
// Click theme button and change to dark theme // Click theme button and change to dark theme
await clickOnTestIdWithText(windowA, 'theme-section'); await clickOnTestIdWithText(windowA, 'theme-section');
// Check background colour of background to verify dark theme // Check background colour of background to verify dark theme
const darkThemeColor = windowA.locator('.inbox.index'); const lightThemeColor = windowA.locator('.inbox.index');
await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(23, 23, 23)'); await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)');
// Toggle back to light theme // Toggle back to light theme
await clickOnTestIdWithText(windowA, 'theme-section'); await clickOnTestIdWithText(windowA, 'theme-section');
// Check background colour again // Check background colour again
await expect(lightThemeColor).toHaveCSS('background-color', 'rgb(255, 255, 255)'); await expect(darkThemeColor).toHaveCSS('background-color', 'rgb(27, 27, 27)');
}); });

@ -28,15 +28,17 @@ export async function waitForMatchingText(window: Page, text: string) {
} }
export async function clickOnMatchingText(window: Page, text: string, rightButton = false) { export async function clickOnMatchingText(window: Page, text: string, rightButton = false) {
console.info(`clickOnMatchingText: "${text}"`);
return window.click(`"${text}"`, rightButton ? { button: 'right' } : undefined); return window.click(`"${text}"`, rightButton ? { button: 'right' } : undefined);
} }
export async function clickOnTestIdWithText(window: Page, dataTestId: string, text?: string) { export async function clickOnTestIdWithText(window: Page, dataTestId: string, text?: string) {
if (text) { console.info(`clickOnTestIdWithText with testId:${dataTestId} and text:${text ? text : 'none'}`);
return window.click(`css=[data-testid=${dataTestId}]:has-text("${text}")`);
} const builtSelector = !text
? `css=[data-testid=${dataTestId}]`
: `css=[data-testid=${dataTestId}]:has-text("${text}")`;
const builtSelector = `css=[data-testid=${dataTestId}]`;
await window.waitForSelector(builtSelector); await window.waitForSelector(builtSelector);
return window.click(builtSelector); return window.click(builtSelector);
} }
@ -46,6 +48,7 @@ export function getMessageTextContentNow() {
} }
export async function typeIntoInput(window: Page, dataTestId: string, text: string) { export async function typeIntoInput(window: Page, dataTestId: string, text: string) {
console.info(`typeIntoInput testId: ${dataTestId} : "${text}"`);
const builtSelector = `css=[data-testid=${dataTestId}]`; const builtSelector = `css=[data-testid=${dataTestId}]`;
return window.fill(builtSelector, text); return window.fill(builtSelector, text);
} }

@ -46,7 +46,13 @@ export async function start(
}, 1000 * 60 * 10); // trigger and try to update every 10 minutes to let the file gets downloaded if we are updating }, 1000 * 60 * 10); // trigger and try to update every 10 minutes to let the file gets downloaded if we are updating
stopped = false; stopped = false;
await checkForUpdates(getMainWindow, messages, logger); global.setTimeout(async () => {
try {
await checkForUpdates(getMainWindow, messages, logger);
} catch (error) {
logger.error('auto-update: error:', getPrintableError(error));
}
}, 2 * 60 * 1000); // we do checks from the fileserver every 1 minute.
} }
export function stop() { export function stop() {

Loading…
Cancel
Save