mirror of https://github.com/vector-im/riot-web
				
				
				
			
		
			
				
	
	
		
			142 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			142 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
/*
 | 
						|
Copyright 2023 The Matrix.org Foundation C.I.C.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
import type { ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS";
 | 
						|
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
 | 
						|
import type { VerificationRequest, Verifier } from "matrix-js-sdk/src/crypto-api";
 | 
						|
 | 
						|
export type EmojiMapping = [emoji: string, name: string];
 | 
						|
 | 
						|
/**
 | 
						|
 * wait for the given client to receive an incoming verification request, and automatically accept it
 | 
						|
 *
 | 
						|
 * @param cli - matrix client we expect to receive a request
 | 
						|
 */
 | 
						|
export function waitForVerificationRequest(cli: MatrixClient): Promise<VerificationRequest> {
 | 
						|
    return new Promise<VerificationRequest>((resolve) => {
 | 
						|
        const onVerificationRequestEvent = async (request: VerificationRequest) => {
 | 
						|
            await request.accept();
 | 
						|
            resolve(request);
 | 
						|
        };
 | 
						|
        // @ts-ignore CryptoEvent is not exported to window.matrixcs; using the string value here
 | 
						|
        cli.once("crypto.verificationRequestReceived", onVerificationRequestEvent);
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Automatically handle a SAS verification
 | 
						|
 *
 | 
						|
 * Given a verifier which has already been started, wait for the emojis to be received, blindly confirm they
 | 
						|
 * match, and return them
 | 
						|
 *
 | 
						|
 * @param verifier - verifier
 | 
						|
 * @returns A promise that resolves, with the emoji list, once we confirm the emojis
 | 
						|
 */
 | 
						|
export function handleSasVerification(verifier: Verifier): Promise<EmojiMapping[]> {
 | 
						|
    return new Promise<EmojiMapping[]>((resolve) => {
 | 
						|
        const onShowSas = (event: ISasEvent) => {
 | 
						|
            // @ts-ignore VerifierEvent is a pain to get at here as we don't have a reference to matrixcs;
 | 
						|
            // using the string value here
 | 
						|
            verifier.off("show_sas", onShowSas);
 | 
						|
            event.confirm();
 | 
						|
            resolve(event.sas.emoji);
 | 
						|
        };
 | 
						|
 | 
						|
        // @ts-ignore as above, avoiding reference to VerifierEvent
 | 
						|
        verifier.on("show_sas", onShowSas);
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Check that the user has published cross-signing keys, and that the user's device has been cross-signed.
 | 
						|
 */
 | 
						|
export function checkDeviceIsCrossSigned(): void {
 | 
						|
    let userId: string;
 | 
						|
    let myDeviceId: string;
 | 
						|
    cy.window({ log: false })
 | 
						|
        .then((win) => {
 | 
						|
            // Get the userId and deviceId of the current user
 | 
						|
            const cli = win.mxMatrixClientPeg.get();
 | 
						|
            const accessToken = cli.getAccessToken()!;
 | 
						|
            const homeserverUrl = cli.getHomeserverUrl();
 | 
						|
            myDeviceId = cli.getDeviceId();
 | 
						|
            userId = cli.getUserId();
 | 
						|
            return cy.request({
 | 
						|
                method: "POST",
 | 
						|
                url: `${homeserverUrl}/_matrix/client/v3/keys/query`,
 | 
						|
                headers: { Authorization: `Bearer ${accessToken}` },
 | 
						|
                body: { device_keys: { [userId]: [] } },
 | 
						|
            });
 | 
						|
        })
 | 
						|
        .then((res) => {
 | 
						|
            // there should be three cross-signing keys
 | 
						|
            expect(res.body.master_keys[userId]).to.have.property("keys");
 | 
						|
            expect(res.body.self_signing_keys[userId]).to.have.property("keys");
 | 
						|
            expect(res.body.user_signing_keys[userId]).to.have.property("keys");
 | 
						|
 | 
						|
            // and the device should be signed by the self-signing key
 | 
						|
            const selfSigningKeyId = Object.keys(res.body.self_signing_keys[userId].keys)[0];
 | 
						|
 | 
						|
            expect(res.body.device_keys[userId][myDeviceId]).to.exist;
 | 
						|
 | 
						|
            const myDeviceSignatures = res.body.device_keys[userId][myDeviceId].signatures[userId];
 | 
						|
            expect(myDeviceSignatures[selfSigningKeyId]).to.exist;
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Fill in the login form in element with the given creds
 | 
						|
 */
 | 
						|
export function logIntoElement(homeserverUrl: string, username: string, password: string) {
 | 
						|
    cy.visit("/#/login");
 | 
						|
 | 
						|
    // select homeserver
 | 
						|
    cy.findByRole("button", { name: "Edit" }).click();
 | 
						|
    cy.findByRole("textbox", { name: "Other homeserver" }).type(homeserverUrl);
 | 
						|
    cy.findByRole("button", { name: "Continue" }).click();
 | 
						|
 | 
						|
    // wait for the dialog to go away
 | 
						|
    cy.get(".mx_ServerPickerDialog").should("not.exist");
 | 
						|
 | 
						|
    cy.findByRole("textbox", { name: "Username" }).type(username);
 | 
						|
    cy.findByPlaceholderText("Password").type(password);
 | 
						|
    cy.findByRole("button", { name: "Sign in" }).click();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Given a SAS verifier for a bot client, add cypress commands to:
 | 
						|
 *   - wait for the bot to receive the emojis
 | 
						|
 *   - check that the bot sees the same emoji as the application
 | 
						|
 *
 | 
						|
 * @param botVerificationRequest - a verification request in a bot client
 | 
						|
 */
 | 
						|
export function doTwoWaySasVerification(verifier: Verifier): void {
 | 
						|
    // on the bot side, wait for the emojis, confirm they match, and return them
 | 
						|
    const emojiPromise = handleSasVerification(verifier);
 | 
						|
 | 
						|
    // then, check that our application shows an emoji panel with the same emojis.
 | 
						|
    cy.wrap(emojiPromise).then((emojis: EmojiMapping[]) => {
 | 
						|
        cy.get(".mx_VerificationShowSas_emojiSas_block").then((emojiBlocks) => {
 | 
						|
            emojis.forEach((emoji: EmojiMapping, index: number) => {
 | 
						|
                // VerificationShowSas munges the case of the emoji descriptions returned by the js-sdk before
 | 
						|
                // displaying them. Once we drop support for legacy crypto, that code can go away, and so can the
 | 
						|
                // case-munging here.
 | 
						|
                expect(emojiBlocks[index].textContent.toLowerCase()).to.eq(emoji[0] + emoji[1].toLowerCase());
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
}
 |