Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 0842559

Browse files
authored
Simplify registration with email validation (#11398)
1 parent beafe68 commit 0842559

File tree

17 files changed

+437
-55
lines changed

17 files changed

+437
-55
lines changed

cypress/e2e/register/email.spec.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/// <reference types="cypress" />
18+
19+
import { HomeserverInstance } from "../../plugins/utils/homeserver";
20+
import { Mailhog } from "../../support/mailhog";
21+
22+
describe("Email Registration", () => {
23+
let homeserver: HomeserverInstance;
24+
let mailhog: Mailhog;
25+
26+
beforeEach(() => {
27+
cy.startMailhog().then((_mailhog) => {
28+
mailhog = _mailhog;
29+
cy.startHomeserver({
30+
template: "email",
31+
variables: {
32+
SMTP_HOST: "host.docker.internal",
33+
SMTP_PORT: _mailhog.instance.smtpPort,
34+
},
35+
}).then((_homeserver) => {
36+
homeserver = _homeserver;
37+
38+
cy.intercept(
39+
{ method: "GET", pathname: "/config.json" },
40+
{
41+
body: {
42+
default_server_config: {
43+
"m.homeserver": {
44+
base_url: homeserver.baseUrl,
45+
},
46+
"m.identity_server": {
47+
base_url: "https://server.invalid",
48+
},
49+
},
50+
},
51+
},
52+
);
53+
cy.visit("/#/register");
54+
cy.injectAxe();
55+
});
56+
});
57+
});
58+
59+
afterEach(() => {
60+
cy.stopHomeserver(homeserver);
61+
cy.stopMailhog(mailhog);
62+
});
63+
64+
it("registers an account and lands on the use case selection screen", () => {
65+
cy.findByRole("textbox", { name: "Username" }).should("be.visible");
66+
// Hide the server text as it contains the randomly allocated Homeserver port
67+
const percyCSS = ".mx_ServerPicker_server { visibility: hidden !important; }";
68+
69+
cy.findByRole("textbox", { name: "Username" }).type("alice");
70+
cy.findByPlaceholderText("Password").type("totally a great password");
71+
cy.findByPlaceholderText("Confirm password").type("totally a great password");
72+
cy.findByPlaceholderText("Email").type("[email protected]");
73+
cy.findByRole("button", { name: "Register" }).click();
74+
75+
cy.findByText("Check your email to continue").should("be.visible");
76+
cy.percySnapshot("Registration check your email", { percyCSS });
77+
cy.checkA11y();
78+
79+
cy.findByText("An error was encountered when sending the email").should("not.exist");
80+
81+
cy.waitForPromise(async () => {
82+
const messages = await mailhog.api.messages();
83+
expect(messages.items).to.have.length(1);
84+
expect(messages.items[0].to).to.eq("[email protected]");
85+
const [link] = messages.items[0].text.match(/http.+/);
86+
return link;
87+
}).as("emailLink");
88+
89+
cy.get<string>("@emailLink").then((link) => cy.request(link));
90+
91+
cy.get(".mx_UseCaseSelection_skip", { timeout: 30000 }).should("exist");
92+
});
93+
});

cypress/plugins/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { webserver } from "./webserver";
2626
import { docker } from "./docker";
2727
import { log } from "./log";
2828
import { oAuthServer } from "./oauth_server";
29+
import { mailhogDocker } from "./mailhog";
2930

3031
/**
3132
* @type {Cypress.PluginConfig}
@@ -41,4 +42,5 @@ export default function (on: PluginEvents, config: PluginConfigOptions) {
4142
installLogsPrinter(on, {
4243
// printLogsToConsole: "always",
4344
});
45+
mailhogDocker(on, config);
4446
}

cypress/plugins/mailhog/index.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/// <reference types="cypress" />
18+
19+
import PluginEvents = Cypress.PluginEvents;
20+
import PluginConfigOptions = Cypress.PluginConfigOptions;
21+
import { getFreePort } from "../utils/port";
22+
import { dockerIp, dockerRun, dockerStop } from "../docker";
23+
24+
// A cypress plugins to add command to manage an instance of Mailhog in Docker
25+
26+
export interface Instance {
27+
host: string;
28+
smtpPort: number;
29+
httpPort: number;
30+
containerId: string;
31+
}
32+
33+
const instances = new Map<string, Instance>();
34+
35+
// Start a synapse instance: the template must be the name of
36+
// one of the templates in the cypress/plugins/synapsedocker/templates
37+
// directory
38+
async function mailhogStart(): Promise<Instance> {
39+
const smtpPort = await getFreePort();
40+
const httpPort = await getFreePort();
41+
42+
console.log(`Starting mailhog...`);
43+
44+
const containerId = await dockerRun({
45+
image: "mailhog/mailhog:latest",
46+
containerName: `react-sdk-cypress-mailhog`,
47+
params: ["--rm", "-p", `${smtpPort}:1025/tcp`, "-p", `${httpPort}:8025/tcp`],
48+
});
49+
50+
console.log(`Started mailhog on ports smtp=${smtpPort} http=${httpPort}.`);
51+
52+
const host = await dockerIp({ containerId });
53+
const instance: Instance = { smtpPort, httpPort, containerId, host };
54+
instances.set(containerId, instance);
55+
return instance;
56+
}
57+
58+
async function mailhogStop(id: string): Promise<void> {
59+
const synCfg = instances.get(id);
60+
61+
if (!synCfg) throw new Error("Unknown mailhog ID");
62+
63+
await dockerStop({
64+
containerId: id,
65+
});
66+
67+
instances.delete(id);
68+
69+
console.log(`Stopped mailhog id ${id}.`);
70+
// cypress deliberately fails if you return 'undefined', so
71+
// return null to signal all is well, and we've handled the task.
72+
return null;
73+
}
74+
75+
/**
76+
* @type {Cypress.PluginConfig}
77+
*/
78+
export function mailhogDocker(on: PluginEvents, config: PluginConfigOptions) {
79+
on("task", {
80+
mailhogStart,
81+
mailhogStop,
82+
});
83+
84+
on("after:spec", async (spec) => {
85+
// Cleans up any remaining instances after a spec run
86+
for (const synId of instances.keys()) {
87+
console.warn(`Cleaning up synapse ID ${synId} after ${spec.name}`);
88+
await mailhogStop(synId);
89+
}
90+
});
91+
}

cypress/plugins/synapsedocker/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Homeserver
6565
hsYaml = hsYaml.replace(/{{FORM_SECRET}}/g, formSecret);
6666
hsYaml = hsYaml.replace(/{{PUBLIC_BASEURL}}/g, baseUrl);
6767
hsYaml = hsYaml.replace(/{{OAUTH_SERVER_PORT}}/g, opts.oAuthServerPort?.toString());
68+
if (opts.variables) {
69+
for (const key in opts.variables) {
70+
hsYaml = hsYaml.replace(new RegExp("%" + key + "%", "g"), String(opts.variables[key]));
71+
}
72+
}
73+
6874
await fse.writeFile(path.join(tempDir, "homeserver.yaml"), hsYaml);
6975

7076
// now generate a signing key (we could use synapse's config generation for
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A synapse configured to require an email for registration
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
server_name: "localhost"
2+
pid_file: /data/homeserver.pid
3+
public_baseurl: "{{PUBLIC_BASEURL}}"
4+
listeners:
5+
- port: 8008
6+
tls: false
7+
bind_addresses: ["::"]
8+
type: http
9+
x_forwarded: true
10+
11+
resources:
12+
- names: [client]
13+
compress: false
14+
15+
database:
16+
name: "sqlite3"
17+
args:
18+
database: ":memory:"
19+
20+
log_config: "/data/log.config"
21+
22+
media_store_path: "/data/media_store"
23+
uploads_path: "/data/uploads"
24+
enable_registration: true
25+
registrations_require_3pid:
26+
- email
27+
registration_shared_secret: "{{REGISTRATION_SECRET}}"
28+
report_stats: false
29+
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
30+
form_secret: "{{FORM_SECRET}}"
31+
signing_key_path: "/data/localhost.signing.key"
32+
33+
trusted_key_servers:
34+
- server_name: "matrix.org"
35+
suppress_key_server_warning: true
36+
37+
ui_auth:
38+
session_timeout: "300s"
39+
40+
email:
41+
smtp_host: "%SMTP_HOST%"
42+
smtp_port: %SMTP_PORT%
43+
notif_from: "Your Friendly %(app)s homeserver <[email protected]>"
44+
app_name: my_branded_matrix_server
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Log configuration for Synapse.
2+
#
3+
# This is a YAML file containing a standard Python logging configuration
4+
# dictionary. See [1] for details on the valid settings.
5+
#
6+
# Synapse also supports structured logging for machine readable logs which can
7+
# be ingested by ELK stacks. See [2] for details.
8+
#
9+
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
10+
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
11+
12+
version: 1
13+
14+
formatters:
15+
precise:
16+
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
17+
18+
handlers:
19+
# A handler that writes logs to stderr. Unused by default, but can be used
20+
# instead of "buffer" and "file" in the logger handlers.
21+
console:
22+
class: logging.StreamHandler
23+
formatter: precise
24+
25+
loggers:
26+
synapse.storage.SQL:
27+
# beware: increasing this to DEBUG will make synapse log sensitive
28+
# information such as access tokens.
29+
level: INFO
30+
31+
twisted:
32+
# We send the twisted logging directly to the file handler,
33+
# to work around https://github.com/matrix-org/synapse/issues/3471
34+
# when using "buffer" logger. Use "console" to log to stderr instead.
35+
handlers: [console]
36+
propagate: false
37+
38+
root:
39+
level: INFO
40+
41+
# Write logs to the `buffer` handler, which will buffer them together in memory,
42+
# then write them to a file.
43+
#
44+
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
45+
# also need to update the configuration for the `twisted` logger above, in
46+
# this case.)
47+
#
48+
handlers: [console]
49+
50+
disable_existing_loggers: false

cypress/support/e2e.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import "./network";
4040
import "./composer";
4141
import "./proxy";
4242
import "./axe";
43+
import "./mailhog";
44+
import "./promise";
4345

4446
installLogsCollector({
4547
// specify the types of logs to collect (and report to the node console at the end of the test)

cypress/support/homeserver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export interface StartHomeserverOpts {
2828

2929
/** Port of an OAuth server to configure the homeserver to use */
3030
oAuthServerPort?: number;
31+
32+
/** Additional variables to inject into the configuration template **/
33+
variables?: Record<string, string | number>;
3134
}
3235

3336
declare global {

cypress/support/mailhog.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/// <reference types="cypress" />
18+
19+
import mailhog from "mailhog";
20+
21+
import Chainable = Cypress.Chainable;
22+
import { Instance } from "../plugins/mailhog";
23+
24+
export interface Mailhog {
25+
api: mailhog.API;
26+
instance: Instance;
27+
}
28+
29+
declare global {
30+
// eslint-disable-next-line @typescript-eslint/no-namespace
31+
namespace Cypress {
32+
interface Chainable {
33+
startMailhog(): Chainable<Mailhog>;
34+
stopMailhog(instance: Mailhog): Chainable<void>;
35+
}
36+
}
37+
}
38+
39+
Cypress.Commands.add("startMailhog", (): Chainable<Mailhog> => {
40+
return cy.task<Instance>("mailhogStart", { log: false }).then((x) => {
41+
Cypress.log({ name: "startHomeserver", message: `Started mailhog instance ${x.containerId}` });
42+
return {
43+
api: mailhog({
44+
host: "localhost",
45+
port: x.httpPort,
46+
}),
47+
instance: x,
48+
};
49+
});
50+
});
51+
52+
Cypress.Commands.add("stopMailhog", (mailhog: Mailhog): Chainable<void> => {
53+
return cy.task("mailhogStop", mailhog.instance.containerId);
54+
});

0 commit comments

Comments
 (0)