mirror of https://github.com/vector-im/riot-web
				
				
				
			Fix logout devices on password reset (#9925)
							parent
							
								
									62913218d2
								
							
						
					
					
						commit
						70d3d03c15
					
				| 
						 | 
				
			
			@ -104,6 +104,10 @@ export default class PasswordReset {
 | 
			
		|||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public setLogoutDevices(logoutDevices: boolean): void {
 | 
			
		||||
        this.logoutDevices = logoutDevices;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async setNewPassword(password: string): Promise<void> {
 | 
			
		||||
        this.password = password;
 | 
			
		||||
        await this.checkEmailLinkClicked();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -258,6 +258,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        this.phase = Phase.ResettingPassword;
 | 
			
		||||
        this.reset.setLogoutDevices(this.state.logoutDevices);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.reset.setNewPassword(this.state.password);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,12 +49,17 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const clickButton = async (label: string): Promise<void> => {
 | 
			
		||||
    const click = async (element: Element): Promise<void> => {
 | 
			
		||||
        await act(async () => {
 | 
			
		||||
            await userEvent.click(screen.getByText(label), { delay: null });
 | 
			
		||||
            await userEvent.click(element, { delay: null });
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const waitForDialog = async (): Promise<void> => {
 | 
			
		||||
        await flushPromisesWithFakeTimers();
 | 
			
		||||
        await flushPromisesWithFakeTimers();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const itShouldCloseTheDialogAndShowThePasswordInput = (): void => {
 | 
			
		||||
        it("should close the dialog and show the password input", () => {
 | 
			
		||||
            expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument();
 | 
			
		||||
| 
						 | 
				
			
			@ -121,9 +126,9 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("when clicking »Sign in instead«", () => {
 | 
			
		||||
        describe("and clicking »Sign in instead«", () => {
 | 
			
		||||
            beforeEach(async () => {
 | 
			
		||||
                await clickButton("Sign in instead");
 | 
			
		||||
                await click(screen.getByText("Sign in instead"));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("should call onLoginClick()", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +136,7 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("when entering a non-email value", () => {
 | 
			
		||||
        describe("and entering a non-email value", () => {
 | 
			
		||||
            beforeEach(async () => {
 | 
			
		||||
                await typeIntoField("Email address", "not en email");
 | 
			
		||||
            });
 | 
			
		||||
| 
						 | 
				
			
			@ -141,13 +146,13 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("when submitting an unknown email", () => {
 | 
			
		||||
        describe("and submitting an unknown email", () => {
 | 
			
		||||
            beforeEach(async () => {
 | 
			
		||||
                await typeIntoField("Email address", testEmail);
 | 
			
		||||
                mocked(client).requestPasswordEmailToken.mockRejectedValue({
 | 
			
		||||
                    errcode: "M_THREEPID_NOT_FOUND",
 | 
			
		||||
                });
 | 
			
		||||
                await clickButton("Send email");
 | 
			
		||||
                await click(screen.getByText("Send email"));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("should show an email not found message", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -155,13 +160,13 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("when a connection error occurs", () => {
 | 
			
		||||
        describe("and a connection error occurs", () => {
 | 
			
		||||
            beforeEach(async () => {
 | 
			
		||||
                await typeIntoField("Email address", testEmail);
 | 
			
		||||
                mocked(client).requestPasswordEmailToken.mockRejectedValue({
 | 
			
		||||
                    name: "ConnectionError",
 | 
			
		||||
                });
 | 
			
		||||
                await clickButton("Send email");
 | 
			
		||||
                await click(screen.getByText("Send email"));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("should show an info about that", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +179,7 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("when the server liveness check fails", () => {
 | 
			
		||||
        describe("and the server liveness check fails", () => {
 | 
			
		||||
            beforeEach(async () => {
 | 
			
		||||
                await typeIntoField("Email address", testEmail);
 | 
			
		||||
                mocked(AutoDiscoveryUtils.validateServerConfigWithStaticUrls).mockRejectedValue({});
 | 
			
		||||
| 
						 | 
				
			
			@ -183,7 +188,7 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                    serverIsAlive: false,
 | 
			
		||||
                    serverDeadError: "server down",
 | 
			
		||||
                });
 | 
			
		||||
                await clickButton("Send email");
 | 
			
		||||
                await click(screen.getByText("Send email"));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("should show the server error", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -191,13 +196,13 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("when submitting an known email", () => {
 | 
			
		||||
        describe("and submitting an known email", () => {
 | 
			
		||||
            beforeEach(async () => {
 | 
			
		||||
                await typeIntoField("Email address", testEmail);
 | 
			
		||||
                mocked(client).requestPasswordEmailToken.mockResolvedValue({
 | 
			
		||||
                    sid: testSid,
 | 
			
		||||
                });
 | 
			
		||||
                await clickButton("Send email");
 | 
			
		||||
                await click(screen.getByText("Send email"));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("should send the mail and show the check email view", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -210,9 +215,9 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                expect(screen.getByText(testEmail)).toBeInTheDocument();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when clicking re-enter email", () => {
 | 
			
		||||
            describe("and clicking »Re-enter email address«", () => {
 | 
			
		||||
                beforeEach(async () => {
 | 
			
		||||
                    await clickButton("Re-enter email address");
 | 
			
		||||
                    await click(screen.getByText("Re-enter email address"));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("go back to the email input", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -220,9 +225,9 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when clicking resend email", () => {
 | 
			
		||||
            describe("and clicking »Resend«", () => {
 | 
			
		||||
                beforeEach(async () => {
 | 
			
		||||
                    await userEvent.click(screen.getByText("Resend"), { delay: null });
 | 
			
		||||
                    await click(screen.getByText("Resend"));
 | 
			
		||||
                    // the message is shown after some time
 | 
			
		||||
                    jest.advanceTimersByTime(500);
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			@ -237,16 +242,16 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when clicking next", () => {
 | 
			
		||||
            describe("and clicking »Next«", () => {
 | 
			
		||||
                beforeEach(async () => {
 | 
			
		||||
                    await clickButton("Next");
 | 
			
		||||
                    await click(screen.getByText("Next"));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("should show the password input view", () => {
 | 
			
		||||
                    expect(screen.getByText("Reset your password")).toBeInTheDocument();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("when entering different passwords", () => {
 | 
			
		||||
                describe("and entering different passwords", () => {
 | 
			
		||||
                    beforeEach(async () => {
 | 
			
		||||
                        await typeIntoField("New Password", testPassword);
 | 
			
		||||
                        await typeIntoField("Confirm new password", testPassword + "asd");
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +262,7 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("when entering a new password", () => {
 | 
			
		||||
                describe("and entering a new password", () => {
 | 
			
		||||
                    beforeEach(async () => {
 | 
			
		||||
                        mocked(client.setPassword).mockRejectedValue({ httpStatus: 401 });
 | 
			
		||||
                        await typeIntoField("New Password", testPassword);
 | 
			
		||||
| 
						 | 
				
			
			@ -273,7 +278,7 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                                    retry_after_ms: (13 * 60 + 37) * 1000,
 | 
			
		||||
                                },
 | 
			
		||||
                            });
 | 
			
		||||
                            await clickButton("Reset password");
 | 
			
		||||
                            await click(screen.getByText("Reset password"));
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it("should show the rate limit error message", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -285,10 +290,8 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
 | 
			
		||||
                    describe("and submitting it", () => {
 | 
			
		||||
                        beforeEach(async () => {
 | 
			
		||||
                            await clickButton("Reset password");
 | 
			
		||||
                            // double flush promises for the modal to appear
 | 
			
		||||
                            await flushPromisesWithFakeTimers();
 | 
			
		||||
                            await flushPromisesWithFakeTimers();
 | 
			
		||||
                            await click(screen.getByText("Reset password"));
 | 
			
		||||
                            await waitForDialog();
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it("should send the new password and show the click validation link dialog", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -316,9 +319,7 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                                await act(async () => {
 | 
			
		||||
                                    await userEvent.click(screen.getByTestId("dialog-background"), { delay: null });
 | 
			
		||||
                                });
 | 
			
		||||
                                // double flush promises for the modal to disappear
 | 
			
		||||
                                await flushPromisesWithFakeTimers();
 | 
			
		||||
                                await flushPromisesWithFakeTimers();
 | 
			
		||||
                                await waitForDialog();
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            itShouldCloseTheDialogAndShowThePasswordInput();
 | 
			
		||||
| 
						 | 
				
			
			@ -326,23 +327,17 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
 | 
			
		||||
                        describe("and dismissing the dialog", () => {
 | 
			
		||||
                            beforeEach(async () => {
 | 
			
		||||
                                await act(async () => {
 | 
			
		||||
                                    await userEvent.click(screen.getByLabelText("Close dialog"), { delay: null });
 | 
			
		||||
                                });
 | 
			
		||||
                                // double flush promises for the modal to disappear
 | 
			
		||||
                                await flushPromisesWithFakeTimers();
 | 
			
		||||
                                await flushPromisesWithFakeTimers();
 | 
			
		||||
                                await click(screen.getByLabelText("Close dialog"));
 | 
			
		||||
                                await waitForDialog();
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            itShouldCloseTheDialogAndShowThePasswordInput();
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        describe("when clicking re-enter email", () => {
 | 
			
		||||
                        describe("and clicking »Re-enter email address«", () => {
 | 
			
		||||
                            beforeEach(async () => {
 | 
			
		||||
                                await clickButton("Re-enter email address");
 | 
			
		||||
                                // double flush promises for the modal to disappear
 | 
			
		||||
                                await flushPromisesWithFakeTimers();
 | 
			
		||||
                                await flushPromisesWithFakeTimers();
 | 
			
		||||
                                await click(screen.getByText("Re-enter email address"));
 | 
			
		||||
                                await waitForDialog();
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            it("should close the dialog and go back to the email input", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -351,7 +346,7 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        describe("when validating the link from the mail", () => {
 | 
			
		||||
                        describe("and validating the link from the mail", () => {
 | 
			
		||||
                            beforeEach(async () => {
 | 
			
		||||
                                mocked(client.setPassword).mockResolvedValue({});
 | 
			
		||||
                                // be sure the next set password attempt was sent
 | 
			
		||||
| 
						 | 
				
			
			@ -369,6 +364,42 @@ describe("<ForgotPassword>", () => {
 | 
			
		|||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    describe("and clicking »Sign out of all devices« and »Reset password«", () => {
 | 
			
		||||
                        beforeEach(async () => {
 | 
			
		||||
                            await click(screen.getByText("Sign out of all devices"));
 | 
			
		||||
                            await click(screen.getByText("Reset password"));
 | 
			
		||||
                            await waitForDialog();
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it("should show the sign out warning dialog", async () => {
 | 
			
		||||
                            expect(
 | 
			
		||||
                                screen.getByText(
 | 
			
		||||
                                    "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.",
 | 
			
		||||
                                ),
 | 
			
		||||
                            ).toBeInTheDocument();
 | 
			
		||||
 | 
			
		||||
                            // confirm dialog
 | 
			
		||||
                            await click(screen.getByText("Continue"));
 | 
			
		||||
 | 
			
		||||
                            // expect setPassword with logoutDevices = true
 | 
			
		||||
                            expect(client.setPassword).toHaveBeenCalledWith(
 | 
			
		||||
                                {
 | 
			
		||||
                                    type: "m.login.email.identity",
 | 
			
		||||
                                    threepid_creds: {
 | 
			
		||||
                                        client_secret: expect.any(String),
 | 
			
		||||
                                        sid: testSid,
 | 
			
		||||
                                    },
 | 
			
		||||
                                    threepidCreds: {
 | 
			
		||||
                                        client_secret: expect.any(String),
 | 
			
		||||
                                        sid: testSid,
 | 
			
		||||
                                    },
 | 
			
		||||
                                },
 | 
			
		||||
                                testPassword,
 | 
			
		||||
                                true,
 | 
			
		||||
                            );
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue