From 0444b23e34df6e7a6c5299686668a7284b464b2d Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 11 Aug 2023 19:43:31 +0100 Subject: [PATCH] Cypress tests for soft logout --- cypress/e2e/login/login.spec.ts | 30 +----- cypress/e2e/login/soft_logout.spec.ts | 143 ++++++++++++++++++++++++++ cypress/e2e/login/utils.ts | 49 +++++++++ 3 files changed, 194 insertions(+), 28 deletions(-) create mode 100644 cypress/e2e/login/soft_logout.spec.ts create mode 100644 cypress/e2e/login/utils.ts diff --git a/cypress/e2e/login/login.spec.ts b/cypress/e2e/login/login.spec.ts index 29c1f6f16bd..2bb7d4c6a00 100644 --- a/cypress/e2e/login/login.spec.ts +++ b/cypress/e2e/login/login.spec.ts @@ -17,6 +17,7 @@ limitations under the License. /// import { HomeserverInstance } from "../../plugins/utils/homeserver"; +import { doTokenRegistration } from "./utils"; describe("Login", () => { let homeserver: HomeserverInstance; @@ -93,7 +94,6 @@ describe("Login", () => { }) .then((data) => { homeserver = data; - cy.visit("/#/login"); }); }); @@ -108,33 +108,7 @@ describe("Login", () => { // If you are using ufw, try something like: // sudo ufw allow in on docker0 // - cy.findByRole("button", { name: "Edit" }).click(); - cy.findByRole("textbox", { name: "Other homeserver" }).type(homeserver.baseUrl); - cy.findByRole("button", { name: "Continue" }).click(); - // wait for the dialog to go away - cy.get(".mx_ServerPickerDialog").should("not.exist"); - - // click on "Continue with OAuth test" - cy.findByRole("button", { name: "Continue with OAuth test" }).click(); - - // wait for the Test OAuth Page to load - cy.findByText("Test OAuth page"); - - // click the submit button - cy.findByRole("button", { name: "Submit" }).click(); - - // Synapse prompts us to pick a user ID - cy.findByRole("heading", { name: "Create your account" }); - cy.findByRole("textbox", { name: "Username (required)" }).type("alice"); - - // wait for username validation to start, and complete - cy.wait(50); - cy.get("#field-username-output").should("have.value", ""); - cy.findByRole("button", { name: "Continue" }).click(); - - // Synapse prompts us to grant permission to Element - cy.findByRole("heading", { name: "Continue to your account" }); - cy.findByRole("link", { name: "Continue" }).click(); + doTokenRegistration(homeserver.baseUrl); // Eventually, we should end up at the home screen. cy.url().should("contain", "/#/home", { timeout: 30000 }); diff --git a/cypress/e2e/login/soft_logout.spec.ts b/cypress/e2e/login/soft_logout.spec.ts new file mode 100644 index 00000000000..959197b7ebe --- /dev/null +++ b/cypress/e2e/login/soft_logout.spec.ts @@ -0,0 +1,143 @@ +/* +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 { HomeserverInstance } from "../../plugins/utils/homeserver"; +import { UserCredentials } from "../../support/login"; +import { doTokenRegistration } from "./utils"; + +describe("Soft logout", () => { + let homeserver: HomeserverInstance; + + beforeEach(() => { + cy.task("startOAuthServer") + .then((oAuthServerPort: number) => { + return cy.startHomeserver({ template: "default", oAuthServerPort }); + }) + .then((data) => { + homeserver = data; + }); + }); + + afterEach(() => { + cy.stopHomeserver(homeserver); + }); + + describe("with password user", () => { + let testUserCreds: UserCredentials; + + beforeEach(() => { + cy.initTestUser(homeserver, "Alice").then((creds) => { + testUserCreds = creds; + }); + }); + + it("shows the soft-logout page when a request fails, and allows a re-login", () => { + interceptRequestsWithSoftLogout(); + cy.findByText("You're signed out"); + cy.findByPlaceholderText("Password").type(testUserCreds.password).type("{enter}"); + + // back to the welcome page + cy.url().should("contain", "/#/home", { timeout: 30000 }); + cy.findByRole("heading", { name: "Welcome Alice" }); + }); + + it("still shows the soft-logout page when the page is reloaded after a soft-logout", () => { + interceptRequestsWithSoftLogout(); + cy.findByText("You're signed out"); + cy.reload(); + cy.findByText("You're signed out"); + }); + }); + + describe("with SSO user", () => { + beforeEach(() => { + doTokenRegistration(homeserver.baseUrl); + + // Eventually, we should end up at the home screen. + cy.url().should("contain", "/#/home", { timeout: 30000 }); + cy.findByRole("heading", { name: "Welcome Alice" }); + }); + + it("shows the soft-logout page when a request fails, and allows a re-login", () => { + // there is a bug in Element which means this only actually works if there is an app reload between + // the token login and the soft-logout: https://github.com/vector-im/element-web/issues/25957 + cy.reload(); + cy.findByRole("heading", { name: "Welcome Alice" }); + + interceptRequestsWithSoftLogout(); + + cy.findByText("You're signed out"); + cy.findByRole("button", { name: "Continue with OAuth test" }).click(); + + // click the submit button + cy.findByRole("button", { name: "Submit" }).click(); + + // Synapse prompts us to grant permission to Element + cy.findByRole("heading", { name: "Continue to your account" }); + cy.findByRole("link", { name: "Continue" }).click(); + + // back to the welcome page + cy.url().should("contain", "/#/home", { timeout: 30000 }); + cy.findByRole("heading", { name: "Welcome Alice" }); + }); + }); +}); + +/** + * Intercept calls to /sync and have them fail with a soft-logout + * + * Any further requests to /sync with the same access token are blocked. + */ +function interceptRequestsWithSoftLogout(): void { + let expiredAccessToken: string | null = null; + cy.intercept( + { + pathname: "/_matrix/client/*/sync", + }, + (req) => { + const accessToken = req.headers["authorization"] as string; + + // on the first request, record the access token + if (!expiredAccessToken) { + console.log(`Soft-logout on access token ${accessToken}`); + expiredAccessToken = accessToken; + } + + // now, if the access token on this request matches the expired one, block it + if (expiredAccessToken && accessToken === expiredAccessToken) { + console.log(`Intercepting request with soft-logged-out access token`); + req.reply({ + statusCode: 401, + body: { + errcode: "M_UNKNOWN_TOKEN", + error: "Soft logout", + soft_logout: true, + }, + }); + return; + } + + // otherwise, pass through as normal + req.continue(); + }, + ); + + // do something to make the active /sync return: create a new room + cy.getClient().then((client) => { + // don't wait for this to complete: it probably won't, because of the broken sync + return client.createRoom({}); + }); +} diff --git a/cypress/e2e/login/utils.ts b/cypress/e2e/login/utils.ts new file mode 100644 index 00000000000..39d87b42756 --- /dev/null +++ b/cypress/e2e/login/utils.ts @@ -0,0 +1,49 @@ +/* +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. +*/ + +/** Visit the login page, choose to log in with "OAuth test", register a new account, and redirect back to Element + */ +export function doTokenRegistration(homeserverUrl: string) { + cy.visit("/#/login"); + + 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"); + + // click on "Continue with OAuth test" + cy.findByRole("button", { name: "Continue with OAuth test" }).click(); + + // wait for the Test OAuth Page to load + cy.findByText("Test OAuth page"); + + // click the submit button + cy.findByRole("button", { name: "Submit" }).click(); + + // Synapse prompts us to pick a user ID + cy.findByRole("heading", { name: "Create your account" }); + cy.findByRole("textbox", { name: "Username (required)" }).type("alice"); + + // wait for username validation to start, and complete + cy.wait(50); + cy.get("#field-username-output").should("have.value", ""); + cy.findByRole("button", { name: "Continue" }).click(); + + // Synapse prompts us to grant permission to Element + cy.findByRole("heading", { name: "Continue to your account" }); + cy.findByRole("link", { name: "Continue" }).click(); +}