From 0842559fb2b303ea53ce19e0e2d0848598164ee9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 15 Aug 2023 16:14:53 +0100
Subject: [PATCH] Simplify registration with email validation (#11398)
---
cypress/e2e/register/email.spec.ts | 93 +++++++++++++++++++
cypress/plugins/index.ts | 2 +
cypress/plugins/mailhog/index.ts | 91 ++++++++++++++++++
cypress/plugins/synapsedocker/index.ts | 6 ++
.../synapsedocker/templates/email/README.md | 1 +
.../templates/email/homeserver.yaml | 44 +++++++++
.../synapsedocker/templates/email/log.config | 50 ++++++++++
cypress/support/e2e.ts | 2 +
cypress/support/homeserver.ts | 3 +
cypress/support/mailhog.ts | 54 +++++++++++
cypress/support/promise.ts | 58 ++++++++++++
package.json | 1 +
src/components/structures/MatrixChat.tsx | 10 --
.../structures/auth/Registration.tsx | 26 +-----
.../components/structures/MatrixChat-test.tsx | 1 -
.../structures/auth/Registration-test.tsx | 1 -
yarn.lock | 49 ++++++----
17 files changed, 437 insertions(+), 55 deletions(-)
create mode 100644 cypress/e2e/register/email.spec.ts
create mode 100644 cypress/plugins/mailhog/index.ts
create mode 100644 cypress/plugins/synapsedocker/templates/email/README.md
create mode 100644 cypress/plugins/synapsedocker/templates/email/homeserver.yaml
create mode 100644 cypress/plugins/synapsedocker/templates/email/log.config
create mode 100644 cypress/support/mailhog.ts
create mode 100644 cypress/support/promise.ts
diff --git a/cypress/e2e/register/email.spec.ts b/cypress/e2e/register/email.spec.ts
new file mode 100644
index 0000000000..a93c05c6ee
--- /dev/null
+++ b/cypress/e2e/register/email.spec.ts
@@ -0,0 +1,93 @@
+/*
+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 { Mailhog } from "../../support/mailhog";
+
+describe("Email Registration", () => {
+ let homeserver: HomeserverInstance;
+ let mailhog: Mailhog;
+
+ beforeEach(() => {
+ cy.startMailhog().then((_mailhog) => {
+ mailhog = _mailhog;
+ cy.startHomeserver({
+ template: "email",
+ variables: {
+ SMTP_HOST: "host.docker.internal",
+ SMTP_PORT: _mailhog.instance.smtpPort,
+ },
+ }).then((_homeserver) => {
+ homeserver = _homeserver;
+
+ cy.intercept(
+ { method: "GET", pathname: "/config.json" },
+ {
+ body: {
+ default_server_config: {
+ "m.homeserver": {
+ base_url: homeserver.baseUrl,
+ },
+ "m.identity_server": {
+ base_url: "https://server.invalid",
+ },
+ },
+ },
+ },
+ );
+ cy.visit("/#/register");
+ cy.injectAxe();
+ });
+ });
+ });
+
+ afterEach(() => {
+ cy.stopHomeserver(homeserver);
+ cy.stopMailhog(mailhog);
+ });
+
+ it("registers an account and lands on the use case selection screen", () => {
+ cy.findByRole("textbox", { name: "Username" }).should("be.visible");
+ // Hide the server text as it contains the randomly allocated Homeserver port
+ const percyCSS = ".mx_ServerPicker_server { visibility: hidden !important; }";
+
+ cy.findByRole("textbox", { name: "Username" }).type("alice");
+ cy.findByPlaceholderText("Password").type("totally a great password");
+ cy.findByPlaceholderText("Confirm password").type("totally a great password");
+ cy.findByPlaceholderText("Email").type("alice@email.com");
+ cy.findByRole("button", { name: "Register" }).click();
+
+ cy.findByText("Check your email to continue").should("be.visible");
+ cy.percySnapshot("Registration check your email", { percyCSS });
+ cy.checkA11y();
+
+ cy.findByText("An error was encountered when sending the email").should("not.exist");
+
+ cy.waitForPromise(async () => {
+ const messages = await mailhog.api.messages();
+ expect(messages.items).to.have.length(1);
+ expect(messages.items[0].to).to.eq("alice@email.com");
+ const [link] = messages.items[0].text.match(/http.+/);
+ return link;
+ }).as("emailLink");
+
+ cy.get("@emailLink").then((link) => cy.request(link));
+
+ cy.get(".mx_UseCaseSelection_skip", { timeout: 30000 }).should("exist");
+ });
+});
diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts
index cb6d819dcd..412057cf54 100644
--- a/cypress/plugins/index.ts
+++ b/cypress/plugins/index.ts
@@ -26,6 +26,7 @@ import { webserver } from "./webserver";
import { docker } from "./docker";
import { log } from "./log";
import { oAuthServer } from "./oauth_server";
+import { mailhogDocker } from "./mailhog";
/**
* @type {Cypress.PluginConfig}
@@ -41,4 +42,5 @@ export default function (on: PluginEvents, config: PluginConfigOptions) {
installLogsPrinter(on, {
// printLogsToConsole: "always",
});
+ mailhogDocker(on, config);
}
diff --git a/cypress/plugins/mailhog/index.ts b/cypress/plugins/mailhog/index.ts
new file mode 100644
index 0000000000..a156e93981
--- /dev/null
+++ b/cypress/plugins/mailhog/index.ts
@@ -0,0 +1,91 @@
+/*
+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 PluginEvents = Cypress.PluginEvents;
+import PluginConfigOptions = Cypress.PluginConfigOptions;
+import { getFreePort } from "../utils/port";
+import { dockerIp, dockerRun, dockerStop } from "../docker";
+
+// A cypress plugins to add command to manage an instance of Mailhog in Docker
+
+export interface Instance {
+ host: string;
+ smtpPort: number;
+ httpPort: number;
+ containerId: string;
+}
+
+const instances = new Map();
+
+// Start a synapse instance: the template must be the name of
+// one of the templates in the cypress/plugins/synapsedocker/templates
+// directory
+async function mailhogStart(): Promise {
+ const smtpPort = await getFreePort();
+ const httpPort = await getFreePort();
+
+ console.log(`Starting mailhog...`);
+
+ const containerId = await dockerRun({
+ image: "mailhog/mailhog:latest",
+ containerName: `react-sdk-cypress-mailhog`,
+ params: ["--rm", "-p", `${smtpPort}:1025/tcp`, "-p", `${httpPort}:8025/tcp`],
+ });
+
+ console.log(`Started mailhog on ports smtp=${smtpPort} http=${httpPort}.`);
+
+ const host = await dockerIp({ containerId });
+ const instance: Instance = { smtpPort, httpPort, containerId, host };
+ instances.set(containerId, instance);
+ return instance;
+}
+
+async function mailhogStop(id: string): Promise {
+ const synCfg = instances.get(id);
+
+ if (!synCfg) throw new Error("Unknown mailhog ID");
+
+ await dockerStop({
+ containerId: id,
+ });
+
+ instances.delete(id);
+
+ console.log(`Stopped mailhog id ${id}.`);
+ // cypress deliberately fails if you return 'undefined', so
+ // return null to signal all is well, and we've handled the task.
+ return null;
+}
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+export function mailhogDocker(on: PluginEvents, config: PluginConfigOptions) {
+ on("task", {
+ mailhogStart,
+ mailhogStop,
+ });
+
+ on("after:spec", async (spec) => {
+ // Cleans up any remaining instances after a spec run
+ for (const synId of instances.keys()) {
+ console.warn(`Cleaning up synapse ID ${synId} after ${spec.name}`);
+ await mailhogStop(synId);
+ }
+ });
+}
diff --git a/cypress/plugins/synapsedocker/index.ts b/cypress/plugins/synapsedocker/index.ts
index 9773849e03..a802c03939 100644
--- a/cypress/plugins/synapsedocker/index.ts
+++ b/cypress/plugins/synapsedocker/index.ts
@@ -65,6 +65,12 @@ async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise"
+ app_name: my_branded_matrix_server
diff --git a/cypress/plugins/synapsedocker/templates/email/log.config b/cypress/plugins/synapsedocker/templates/email/log.config
new file mode 100644
index 0000000000..ac232762da
--- /dev/null
+++ b/cypress/plugins/synapsedocker/templates/email/log.config
@@ -0,0 +1,50 @@
+# Log configuration for Synapse.
+#
+# This is a YAML file containing a standard Python logging configuration
+# dictionary. See [1] for details on the valid settings.
+#
+# Synapse also supports structured logging for machine readable logs which can
+# be ingested by ELK stacks. See [2] for details.
+#
+# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
+# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
+
+version: 1
+
+formatters:
+ precise:
+ format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
+
+handlers:
+ # A handler that writes logs to stderr. Unused by default, but can be used
+ # instead of "buffer" and "file" in the logger handlers.
+ console:
+ class: logging.StreamHandler
+ formatter: precise
+
+loggers:
+ synapse.storage.SQL:
+ # beware: increasing this to DEBUG will make synapse log sensitive
+ # information such as access tokens.
+ level: INFO
+
+ twisted:
+ # We send the twisted logging directly to the file handler,
+ # to work around https://github.com/matrix-org/synapse/issues/3471
+ # when using "buffer" logger. Use "console" to log to stderr instead.
+ handlers: [console]
+ propagate: false
+
+root:
+ level: INFO
+
+ # Write logs to the `buffer` handler, which will buffer them together in memory,
+ # then write them to a file.
+ #
+ # Replace "buffer" with "console" to log to stderr instead. (Note that you'll
+ # also need to update the configuration for the `twisted` logger above, in
+ # this case.)
+ #
+ handlers: [console]
+
+disable_existing_loggers: false
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
index 2ff0197ba6..11eae401f9 100644
--- a/cypress/support/e2e.ts
+++ b/cypress/support/e2e.ts
@@ -40,6 +40,8 @@ import "./network";
import "./composer";
import "./proxy";
import "./axe";
+import "./mailhog";
+import "./promise";
installLogsCollector({
// specify the types of logs to collect (and report to the node console at the end of the test)
diff --git a/cypress/support/homeserver.ts b/cypress/support/homeserver.ts
index 15fce60350..9f5e46efb0 100644
--- a/cypress/support/homeserver.ts
+++ b/cypress/support/homeserver.ts
@@ -28,6 +28,9 @@ export interface StartHomeserverOpts {
/** Port of an OAuth server to configure the homeserver to use */
oAuthServerPort?: number;
+
+ /** Additional variables to inject into the configuration template **/
+ variables?: Record;
}
declare global {
diff --git a/cypress/support/mailhog.ts b/cypress/support/mailhog.ts
new file mode 100644
index 0000000000..86efc5e0f6
--- /dev/null
+++ b/cypress/support/mailhog.ts
@@ -0,0 +1,54 @@
+/*
+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 mailhog from "mailhog";
+
+import Chainable = Cypress.Chainable;
+import { Instance } from "../plugins/mailhog";
+
+export interface Mailhog {
+ api: mailhog.API;
+ instance: Instance;
+}
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Cypress {
+ interface Chainable {
+ startMailhog(): Chainable;
+ stopMailhog(instance: Mailhog): Chainable;
+ }
+ }
+}
+
+Cypress.Commands.add("startMailhog", (): Chainable => {
+ return cy.task("mailhogStart", { log: false }).then((x) => {
+ Cypress.log({ name: "startHomeserver", message: `Started mailhog instance ${x.containerId}` });
+ return {
+ api: mailhog({
+ host: "localhost",
+ port: x.httpPort,
+ }),
+ instance: x,
+ };
+ });
+});
+
+Cypress.Commands.add("stopMailhog", (mailhog: Mailhog): Chainable => {
+ return cy.task("mailhogStop", mailhog.instance.containerId);
+});
diff --git a/cypress/support/promise.ts b/cypress/support/promise.ts
new file mode 100644
index 0000000000..4baaf75e8e
--- /dev/null
+++ b/cypress/support/promise.ts
@@ -0,0 +1,58 @@
+/*
+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 Chainable = Cypress.Chainable;
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Cypress {
+ interface Chainable {
+ /**
+ * Utility wrapper around promises to help control flow in tests
+ * Calls `fn` function `tries` times, with a sleep of `interval` between calls.
+ * Ensure you do not rely on any effects of calling any `cy.*` functions within the body of `fn`
+ * as the calls will not happen until after waitForPromise returns.
+ * @param fn the function to retry
+ * @param tries the number of tries to call it
+ * @param interval the time interval between tries
+ */
+ waitForPromise(fn: () => Promise, tries?: number, interval?: number): Chainable;
+ }
+ }
+}
+
+function waitForPromise(fn: () => Promise, tries = 10, interval = 1000): Chainable {
+ return cy.then(
+ () =>
+ new Cypress.Promise(async (resolve, reject) => {
+ for (let i = 0; i < tries; i++) {
+ try {
+ const v = await fn();
+ resolve(v);
+ } catch {
+ await new Cypress.Promise((resolve) => setTimeout(resolve, interval));
+ }
+ }
+ reject();
+ }),
+ );
+}
+
+Cypress.Commands.add("waitForPromise", waitForPromise);
+
+export {};
diff --git a/package.json b/package.json
index 9dfbdbf53b..8c8b3b4b07 100644
--- a/package.json
+++ b/package.json
@@ -208,6 +208,7 @@
"jest-mock": "^29.2.2",
"jest-raw-loader": "^1.0.1",
"jsqr": "^1.4.0",
+ "mailhog": "^4.16.0",
"matrix-mock-request": "^2.5.0",
"matrix-web-i18n": "^1.4.0",
"mocha-junit-reporter": "^2.2.0",
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 49981b781a..563e228088 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -176,8 +176,6 @@ interface IProps {
initialScreenAfterLogin?: IScreen;
// displayname, if any, to set on the device when logging in/registering.
defaultDeviceDisplayName?: string;
- // A function that makes a registration URL
- makeRegistrationUrl: (params: QueryDict) => string;
}
interface IState {
@@ -2004,13 +2002,6 @@ export default class MatrixChat extends React.PureComponent {
this.setState({ serverConfig });
};
- private makeRegistrationUrl = (params: QueryDict): string => {
- if (this.props.startingFragmentQueryParams?.referrer) {
- params.referrer = this.props.startingFragmentQueryParams.referrer;
- }
- return this.props.makeRegistrationUrl(params);
- };
-
/**
* After registration or login, we run various post-auth steps before entering the app
* proper, such setting up cross-signing or verifying the new session.
@@ -2121,7 +2112,6 @@ export default class MatrixChat extends React.PureComponent {
idSid={this.state.register_id_sid}
email={email}
brand={this.props.config.brand}
- makeRegistrationUrl={this.makeRegistrationUrl}
onLoggedIn={this.onRegisterFlowComplete}
onLoginClick={this.onLoginClick}
onServerConfigChange={this.onServerConfigChange}
diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx
index 2a8f89a5cd..71b34462fd 100644
--- a/src/components/structures/auth/Registration.tsx
+++ b/src/components/structures/auth/Registration.tsx
@@ -73,15 +73,7 @@ interface IProps {
// - The user's password, if available and applicable (may be cached in memory
// for a short time so the user is not required to re-enter their password
// for operations like uploading cross-signing keys).
- onLoggedIn(params: IMatrixClientCreds, password: string): void;
- makeRegistrationUrl(params: {
- /* eslint-disable camelcase */
- client_secret: string;
- hs_url: string;
- is_url?: string;
- session_id: string;
- /* eslint-enable camelcase */
- }): string;
+ onLoggedIn(params: IMatrixClientCreds, password: string): Promise;
// registration shouldn't know or care how login is done.
onLoginClick(): void;
onServerConfigChange(config: ValidatedServerConfig): void;
@@ -302,17 +294,7 @@ export default class Registration extends React.Component {
sessionId: string,
): Promise => {
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");
- return this.state.matrixClient.requestRegisterEmailToken(
- emailAddress,
- clientSecret,
- sendAttempt,
- this.props.makeRegistrationUrl({
- client_secret: clientSecret,
- hs_url: this.state.matrixClient.getHomeserverUrl(),
- is_url: this.state.matrixClient.getIdentityServerUrl(),
- session_id: sessionId,
- }),
- );
+ return this.state.matrixClient.requestRegisterEmailToken(emailAddress, clientSecret, sendAttempt);
};
private onUIAuthFinished: InteractiveAuthCallback = async (success, response): Promise => {
@@ -401,9 +383,7 @@ export default class Registration extends React.Component {
const hasAccessToken = Boolean(accessToken);
debuglog("Registration: ui auth finished:", { hasEmail, hasAccessToken });
// don’t log in if we found a session for a different user
- if (!hasEmail && hasAccessToken && !newState.differentLoggedInUserId) {
- // we'll only try logging in if we either have no email to verify at all or we're the client that verified
- // the email, not the client that started the registration flow
+ if (hasAccessToken && !newState.differentLoggedInUserId) {
await this.props.onLoggedIn(
{
userId,
diff --git a/test/components/structures/MatrixChat-test.tsx b/test/components/structures/MatrixChat-test.tsx
index 0dc2fc569d..7684b267bc 100644
--- a/test/components/structures/MatrixChat-test.tsx
+++ b/test/components/structures/MatrixChat-test.tsx
@@ -131,7 +131,6 @@ describe("", () => {
},
onNewScreen: jest.fn(),
onTokenLoginCompleted: jest.fn(),
- makeRegistrationUrl: jest.fn(),
realQueryParams: {},
};
const getComponent = (props: Partial> = {}) =>
diff --git a/test/components/structures/auth/Registration-test.tsx b/test/components/structures/auth/Registration-test.tsx
index be5afff827..0123d6867e 100644
--- a/test/components/structures/auth/Registration-test.tsx
+++ b/test/components/structures/auth/Registration-test.tsx
@@ -75,7 +75,6 @@ describe("Registration", function () {
const defaultProps = {
defaultDeviceDisplayName: "test-device-display-name",
- makeRegistrationUrl: jest.fn(),
onLoggedIn: jest.fn(),
onLoginClick: jest.fn(),
onServerConfigChange: jest.fn(),
diff --git a/yarn.lock b/yarn.lock
index 3ed1113975..0bcf7d20a9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1389,9 +1389,9 @@
integrity sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==
"@cypress/request@^2.88.11":
- version "2.88.11"
- resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.11.tgz#5a4c7399bc2d7e7ed56e92ce5acb620c8b187047"
- integrity sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==
+ version "2.88.12"
+ resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.12.tgz#ba4911431738494a85e93fb04498cb38bc55d590"
+ integrity sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@@ -1408,7 +1408,7 @@
performance-now "^2.1.0"
qs "~6.10.3"
safe-buffer "^5.1.2"
- tough-cookie "~2.5.0"
+ tough-cookie "^4.1.3"
tunnel-agent "^0.6.0"
uuid "^8.3.2"
@@ -2615,16 +2615,16 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.8.tgz#b5dda19adaa473a9bf0ab5cbd8f30ec7d43f5c85"
integrity sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==
-"@types/node@^14.14.31":
- version "14.18.54"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.54.tgz#fc304bd66419030141fa997dc5a9e0e374029ae8"
- integrity sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw==
-
"@types/node@^16":
version "16.18.39"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.39.tgz#aa39a1a87a40ef6098ee69689a1acb0c1b034832"
integrity sha512-8q9ZexmdYYyc5/cfujaXb4YOucpQxAV4RMG0himLyDUOEr8Mr79VrqsFI+cQ2M2h89YIuy95lbxuYjxT4Hk4kQ==
+"@types/node@^16.18.39":
+ version "16.18.40"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.40.tgz#968d64746d20cac747a18ca982c0f1fe518c031c"
+ integrity sha512-+yno3ItTEwGxXiS/75Q/aHaa5srkpnJaH+kdkTVJ3DtJEwv92itpKbxU+FjPoh2m/5G9zmUQfrL4A4C13c+iGA==
+
"@types/normalize-package-data@^2.4.0":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
@@ -4039,13 +4039,13 @@ cypress-terminal-report@^5.3.2:
tv4 "^1.3.0"
cypress@^12.0.0:
- version "12.17.2"
- resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.2.tgz#040ac55de1e811f6e037d231a2869d5ab8c29c85"
- integrity sha512-hxWAaWbqQBzzMuadSGSuQg5PDvIGOovm6xm0hIfpCVcORsCAj/gF2p0EvfnJ4f+jK2PCiDgP6D2eeE9/FK4Mjg==
+ version "12.17.3"
+ resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.3.tgz#1e2b19037236fc60e4a4b3a14f0846be17a1fc0e"
+ integrity sha512-/R4+xdIDjUSLYkiQfwJd630S81KIgicmQOLXotFxVXkl+eTeVO+3bHXxdi5KBh/OgC33HWN33kHX+0tQR/ZWpg==
dependencies:
"@cypress/request" "^2.88.11"
"@cypress/xvfb" "^1.2.4"
- "@types/node" "^14.14.31"
+ "@types/node" "^16.18.39"
"@types/sinonjs__fake-timers" "8.1.1"
"@types/sizzle" "^2.3.2"
arch "^2.2.0"
@@ -5775,7 +5775,7 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
-iconv-lite@0.6.3:
+iconv-lite@0.6.3, iconv-lite@^0.6:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@@ -7078,6 +7078,13 @@ lz-string@^1.4.4:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
+mailhog@^4.16.0:
+ version "4.16.0"
+ resolved "https://registry.yarnpkg.com/mailhog/-/mailhog-4.16.0.tgz#1ad4dda104505399f3f17824737a962696e7d240"
+ integrity sha512-wXrGik+0MaAy4dbYTImxa8niX9a4aRpZTzC/b1GzCvQs09khhs0aKZgHjgScakI4Y18WInDvvF48hhEz9ifN4g==
+ optionalDependencies:
+ iconv-lite "^0.6"
+
make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@@ -8051,7 +8058,7 @@ proxy-from-env@1.0.0:
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==
-psl@^1.1.28, psl@^1.1.33:
+psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
@@ -9269,13 +9276,15 @@ tough-cookie@^4.1.2:
universalify "^0.2.0"
url-parse "^1.5.3"
-tough-cookie@~2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
- integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+tough-cookie@^4.1.3:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
+ integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
dependencies:
- psl "^1.1.28"
+ psl "^1.1.33"
punycode "^2.1.1"
+ universalify "^0.2.0"
+ url-parse "^1.5.3"
tr46@^1.0.1:
version "1.0.1"