Add config option to force verification (#29)

* Add config option to force verification

If this is set, users will not have the option to skip verification
on login (they will still be able to reload and continue unverified,
currently). Default off.

* Test for complete security dialog

* I hadn't set up prettier
This commit is contained in:
David Baker 2024-09-11 21:55:00 +01:00 committed by GitHub
parent 75918f5b18
commit a701e3afd7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 87 additions and 1 deletions

View file

@ -52,6 +52,8 @@ export interface IConfigOptions {
auth_footer_links?: { text: string; url: string }[]; auth_footer_links?: { text: string; url: string }[];
}; };
force_verification?: boolean; // if true, users must verify new logins
map_style_url?: string; // for location-shared maps map_style_url?: string; // for location-shared maps
embedded_pages?: { embedded_pages?: {

View file

@ -14,6 +14,7 @@ import SetupEncryptionBody from "./SetupEncryptionBody";
import AccessibleButton from "../../views/elements/AccessibleButton"; import AccessibleButton from "../../views/elements/AccessibleButton";
import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody"; import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
import AuthPage from "../../views/auth/AuthPage"; import AuthPage from "../../views/auth/AuthPage";
import SdkConfig from "../../../SdkConfig";
interface IProps { interface IProps {
onFinished: () => void; onFinished: () => void;
@ -82,8 +83,10 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
throw new Error(`Unknown phase ${phase}`); throw new Error(`Unknown phase ${phase}`);
} }
const forceVerification = SdkConfig.get("force_verification") ?? false;
let skipButton; let skipButton;
if (phase === Phase.Intro || phase === Phase.ConfirmReset) { if (!forceVerification && (phase === Phase.Intro || phase === Phase.ConfirmReset)) {
skipButton = ( skipButton = (
<AccessibleButton <AccessibleButton
onClick={this.onSkipClick} onClick={this.onSkipClick}

View file

@ -0,0 +1,79 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render, screen } from "@testing-library/react";
import { mocked } from "jest-mock";
import EventEmitter from "events";
import CompleteSecurity from "../../../../src/components/structures/auth/CompleteSecurity";
import { stubClient } from "../../../test-utils";
import { Phase, SetupEncryptionStore } from "../../../../src/stores/SetupEncryptionStore";
import SdkConfig from "../../../../src/SdkConfig";
class MockSetupEncryptionStore extends EventEmitter {
public phase: Phase = Phase.Intro;
public lostKeys(): boolean {
return false;
}
public start: () => void = jest.fn();
public stop: () => void = jest.fn();
}
describe("CompleteSecurity", () => {
beforeEach(() => {
const client = stubClient();
const deviceIdToDevice = new Map();
deviceIdToDevice.set("DEVICE_ID", {
deviceId: "DEVICE_ID",
userId: "USER_ID",
});
const userIdToDevices = new Map();
userIdToDevices.set("USER_ID", deviceIdToDevice);
mocked(client.getCrypto()!.getUserDeviceInfo).mockResolvedValue(userIdToDevices);
const mockSetupEncryptionStore = new MockSetupEncryptionStore();
jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(
mockSetupEncryptionStore as SetupEncryptionStore,
);
});
afterEach(() => {
jest.restoreAllMocks();
});
it("Renders with a cancel button by default", () => {
render(<CompleteSecurity onFinished={() => {}} />);
expect(screen.getByRole("button", { name: "Skip verification for now" })).toBeInTheDocument();
});
it("Renders with a cancel button if forceVerification false", () => {
jest.spyOn(SdkConfig, "get").mockImplementation((key: string) => {
if (key === "forceVerification") {
return false;
}
});
render(<CompleteSecurity onFinished={() => {}} />);
expect(screen.getByRole("button", { name: "Skip verification for now" })).toBeInTheDocument();
});
it("Renders without a cancel button if forceVerification true", () => {
jest.spyOn(SdkConfig, "get").mockImplementation((key: string) => {
if (key === "force_verification") {
return true;
}
});
render(<CompleteSecurity onFinished={() => {}} />);
expect(screen.queryByRole("button", { name: "Skip verification for now" })).not.toBeInTheDocument();
});
});

View file

@ -108,6 +108,7 @@ export function createTestClient(): MatrixClient {
secretStorage: { secretStorage: {
get: jest.fn(), get: jest.fn(),
isStored: jest.fn().mockReturnValue(false),
}, },
store: { store: {
@ -128,6 +129,7 @@ export function createTestClient(): MatrixClient {
getDeviceVerificationStatus: jest.fn(), getDeviceVerificationStatus: jest.fn(),
resetKeyBackup: jest.fn(), resetKeyBackup: jest.fn(),
isEncryptionEnabledInRoom: jest.fn(), isEncryptionEnabledInRoom: jest.fn(),
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
}), }),
getPushActionsForEvent: jest.fn(), getPushActionsForEvent: jest.fn(),