diff --git a/cypress/plugins/dex/index.ts b/cypress/plugins/dex/index.ts
new file mode 100644
index 00000000..b07a78e8
--- /dev/null
+++ b/cypress/plugins/dex/index.ts
@@ -0,0 +1,132 @@
+/*
+Copyright 2022 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 * as path from "path";
+import * as os from "os";
+import * as fse from "fs-extra";
+
+import PluginEvents = Cypress.PluginEvents;
+import PluginConfigOptions = Cypress.PluginConfigOptions;
+import {dockerRun, dockerStop } from "../docker";
+
+// A cypress plugins to add command to start & stop dex instances
+
+interface DexConfig {
+ configDir: string;
+ baseUrl: string;
+ port: number;
+ host: string;
+}
+
+export interface DexInstance extends DexConfig {
+ dexId: string;
+}
+
+const dexConfigs = new Map();
+let env;
+
+async function produceConfigWithSynapseURLAdded(): Promise {
+ const templateDir = path.join(__dirname, "template");
+
+ const stats = await fse.stat(templateDir);
+ if (!stats?.isDirectory) {
+ throw new Error(`Template directory at ${templateDir} not found!`);
+ }
+ const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), 'hydrogen-testing-dex-'));
+
+ // copy the contents of the template dir, omitting config.yaml as we'll template that
+ console.log(`Copy ${templateDir} -> ${tempDir}`);
+ await fse.copy(templateDir, tempDir, { filter: f => path.basename(f) !== 'config.yaml' });
+
+ // now copy config.yaml, applying substitutions
+ console.log(`Gen ${path.join(templateDir, "config.yaml")}`);
+ let hsYaml = await fse.readFile(path.join(templateDir, "config.yaml"), "utf8");
+ const synapseAddress = `${env.SYNAPSE_IP_ADDRESS}:${env.SYNAPSE_PORT}`;
+ hsYaml = hsYaml.replace(/{{SYNAPSE_ADDRESS}}/g, synapseAddress);
+ const host = env.DEX_IP_ADDRESS;
+ const port = env.DEX_PORT;
+ const dexAddress = `${host}:${port}`;
+ hsYaml = hsYaml.replace(/{{DEX_ADDRESS}}/g, dexAddress);
+ await fse.writeFile(path.join(tempDir, "config.yaml"), hsYaml);
+
+ const baseUrl = `http://${host}:${port}`;
+ return {
+ host,
+ port,
+ baseUrl,
+ configDir: tempDir,
+ };
+}
+
+async function dexStart(): Promise {
+ const dexCfg = await produceConfigWithSynapseURLAdded();
+ console.log(`Starting dex with config dir ${dexCfg.configDir}...`);
+ const dexId = await dockerRun({
+ image: "bitnami/dex:latest",
+ containerName: "dex",
+ dockerParams: [
+ "--rm",
+ "-v", `${dexCfg.configDir}:/data`,
+ `--ip=${dexCfg.host}`,
+ "-p", `${dexCfg.port}:5556/tcp`,
+ "--network=hydrogen"
+ ],
+ applicationParams: [
+ "serve",
+ "data/config.yaml",
+ ]
+ });
+
+ console.log(`Started dex with id ${dexId} on port ${dexCfg.port}.`);
+
+ const dex: DexInstance = { dexId, ...dexCfg };
+ dexConfigs.set(dexId, dex);
+ return dex;
+}
+
+async function dexStop(id: string): Promise {
+ const dexCfg = dexConfigs.get(id);
+ if (!dexCfg) throw new Error("Unknown dex ID");
+ await dockerStop({ containerId: id, });
+ await fse.remove(dexCfg.configDir);
+ dexConfigs.delete(id);
+ console.log(`Stopped dex 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 dexDocker(on: PluginEvents, config: PluginConfigOptions) {
+ env = config.env;
+
+ on("task", {
+ dexStart,
+ dexStop,
+ });
+
+ on("after:spec", async (spec) => {
+ for (const dexId of dexConfigs.keys()) {
+ console.warn(`Cleaning up dex ID ${dexId} after ${spec.name}`);
+ await dexStop(dexId);
+ }
+ });
+}
+
diff --git a/cypress/plugins/dex/template/config.yaml b/cypress/plugins/dex/template/config.yaml
new file mode 100755
index 00000000..f773eb76
--- /dev/null
+++ b/cypress/plugins/dex/template/config.yaml
@@ -0,0 +1,56 @@
+issuer: http://{{DEX_ADDRESS}}/dex
+
+storage:
+ type: sqlite3
+ config:
+ file: data/dev.db
+
+
+# Configuration for the HTTP endpoints.
+web:
+ http: 0.0.0.0:5556
+ # Uncomment for HTTPS options.
+ # https: 127.0.0.1:5554
+ # tlsCert: /etc/dex/tls.crt
+ # tlsKey: /etc/dex/tls.key
+
+# Configuration for telemetry
+telemetry:
+ http: 0.0.0.0:5558
+ # enableProfiling: true
+
+staticClients:
+- id: synapse
+ secret: secret
+ redirectURIs:
+ - 'http://{{SYNAPSE_ADDRESS}}/_synapse/client/oidc/callback'
+ name: 'Synapse'
+connectors:
+- type: mockCallback
+ id: mock
+ name: Example
+# - type: google
+# id: google
+# name: Google
+# config:
+# issuer: https://accounts.google.com
+# # Connector config values starting with a "$" will read from the environment.
+# clientID: $GOOGLE_CLIENT_ID
+# clientSecret: $GOOGLE_CLIENT_SECRET
+# redirectURI: http://127.0.0.1:5556/dex/callback
+# hostedDomains:
+# - $GOOGLE_HOSTED_DOMAIN
+
+# Let dex keep a list of passwords which can be used to login to dex.
+enablePasswordDB: true
+
+# A static list of passwords to login the end user. By identifying here, dex
+# won't look in its underlying storage for passwords.
+#
+# If this option isn't chosen users may be added through the gRPC API.
+staticPasswords:
+- email: "admin@example.com"
+ # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
+ username: "admin"
+ userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
diff --git a/cypress/plugins/dex/template/dev.db b/cypress/plugins/dex/template/dev.db
new file mode 100755
index 00000000..95a42aed
Binary files /dev/null and b/cypress/plugins/dex/template/dev.db differ
diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts
index 44dd93b8..27c8b9a3 100644
--- a/cypress/plugins/index.ts
+++ b/cypress/plugins/index.ts
@@ -20,6 +20,7 @@ import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { performance } from "./performance";
import { synapseDocker } from "./synapsedocker";
+import { dexDocker } from "./dex";
import { webserver } from "./webserver";
import { docker } from "./docker";
import { log } from "./log";
@@ -31,6 +32,7 @@ export default function(on: PluginEvents, config: PluginConfigOptions) {
docker(on, config);
performance(on, config);
synapseDocker(on, config);
+ dexDocker(on, config);
webserver(on, config);
log(on, config);
}
diff --git a/cypress/support/dex.ts b/cypress/support/dex.ts
new file mode 100644
index 00000000..599eee26
--- /dev/null
+++ b/cypress/support/dex.ts
@@ -0,0 +1,51 @@
+/*
+Copyright 2022 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;
+import AUTWindow = Cypress.AUTWindow;
+import { DexInstance } from "../plugins/dex";
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Cypress {
+ interface Chainable {
+ /**
+ * Start the dex server
+ */
+ startDex(): Chainable;
+
+ /**
+ * Stop the dex server
+ * @param dex the dex instance returned by startSynapse
+ */
+ stopDex(dex: DexInstance): Chainable;
+ }
+ }
+}
+
+function startDex(): Chainable {
+ return cy.task("dexStart");
+}
+
+function stopDex(dex?: DexInstance): Chainable {
+ if (!dex) return;
+ cy.task("dexStop", dex.dexId);
+}
+
+Cypress.Commands.add("startDex", startDex);
+Cypress.Commands.add("stopDex", stopDex);
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
index 289319fe..d186c5a0 100644
--- a/cypress/support/e2e.ts
+++ b/cypress/support/e2e.ts
@@ -17,3 +17,4 @@ limitations under the License.
///
import "./synapse";
+import "./dex";