Use synapse API to register instead doing via UI
This commit is contained in:
@@ -50,6 +50,9 @@ max_event_delay_duration: 24h
|
|||||||
enable_registration: true
|
enable_registration: true
|
||||||
enable_registration_without_verification: true
|
enable_registration_without_verification: true
|
||||||
|
|
||||||
|
# Shared secret for admin user registration via API (for testing only!)
|
||||||
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
serve_server_wellknown: true
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ max_event_delay_duration: 24h
|
|||||||
enable_registration: true
|
enable_registration: true
|
||||||
enable_registration_without_verification: true
|
enable_registration_without_verification: true
|
||||||
|
|
||||||
|
# Shared secret for admin user registration via API (for testing only!)
|
||||||
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
serve_server_wellknown: true
|
||||||
|
|
||||||
|
|||||||
+18
-3
@@ -28,11 +28,18 @@ server {
|
|||||||
# Reason: the lk-jwt-service uses the federation API for the openid token
|
# Reason: the lk-jwt-service uses the federation API for the openid token
|
||||||
# verification, which requires TLS
|
# verification, which requires TLS
|
||||||
location ~ ^(/_matrix|/_synapse/client) {
|
location ~ ^(/_matrix|/_synapse/client) {
|
||||||
proxy_pass "http://homeserver:8008";
|
proxy_pass "http://homeserver:8008";
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
location ~ ^(/_matrix|/_synapse/admin) {
|
||||||
|
proxy_pass "http://homeserver:8008";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
@@ -73,7 +80,15 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^(/_matrix|/_synapse/admin) {
|
||||||
|
proxy_pass "http://homeserver-1:18008";
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ max_event_delay_duration: 24h
|
|||||||
enable_registration: true
|
enable_registration: true
|
||||||
enable_registration_without_verification: true
|
enable_registration_without_verification: true
|
||||||
|
|
||||||
|
# Shared secret for admin user registration via API (for testing only!)
|
||||||
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
serve_server_wellknown: true
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ max_event_delay_duration: 24h
|
|||||||
enable_registration: true
|
enable_registration: true
|
||||||
enable_registration_without_verification: true
|
enable_registration_without_verification: true
|
||||||
|
|
||||||
|
# Shared secret for admin user registration via API (for testing only!)
|
||||||
|
registration_shared_secret: "test_shared_secret_for_local_dev_only"
|
||||||
|
|
||||||
report_stats: false
|
report_stats: false
|
||||||
serve_server_wellknown: true
|
serve_server_wellknown: true
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,22 @@ Please see LICENSE in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { defineConfig, devices } from "@playwright/test";
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
import { join } from "path";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const baseURL = process.env.USE_DOCKER
|
const baseURL = process.env.USE_DOCKER
|
||||||
? "http://localhost:8080"
|
? "http://localhost:8080"
|
||||||
: "https://localhost:3000";
|
: "https://localhost:3000";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
// Needed by the synapse admin API called in fixtures
|
||||||
|
process.env.NODE_EXTRA_CA_CERTS = join(
|
||||||
|
__dirname,
|
||||||
|
"backend/dev_tls_local-ca.crt",
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createHmac } from "crypto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response from Synapse registration API
|
||||||
|
*/
|
||||||
|
export interface SynapseRegistrationResponse {
|
||||||
|
access_token: string;
|
||||||
|
user_id: string;
|
||||||
|
home_server: string;
|
||||||
|
device_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for interacting with Synapse Admin API
|
||||||
|
* This provides fast user registration without going through the UI
|
||||||
|
*
|
||||||
|
* @see https://matrix-org.github.io/synapse/latest/admin_api/register_api.html
|
||||||
|
*/
|
||||||
|
export class SynapseAdmin {
|
||||||
|
public constructor(
|
||||||
|
private baseUrl: string = "https://synapse.m.localhost",
|
||||||
|
private sharedSecret: string = "test_shared_secret_for_local_dev_only",
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a user using the Synapse Admin API
|
||||||
|
* This is much faster than going through the UI registration flow
|
||||||
|
*
|
||||||
|
* @param username - The username (localpart) for the new user
|
||||||
|
* @param password - The password for the new user
|
||||||
|
* @param displayName - Optional display name (defaults to username)
|
||||||
|
* @param admin - Whether the user should be an admin (defaults to false)
|
||||||
|
* @returns Registration response containing access token and user ID
|
||||||
|
*/
|
||||||
|
public async registerUser(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
displayName?: string,
|
||||||
|
admin: boolean = false,
|
||||||
|
): Promise<SynapseRegistrationResponse> {
|
||||||
|
// Get a nonce first
|
||||||
|
const nonce = await this.getNonce();
|
||||||
|
|
||||||
|
// Generate the HMAC
|
||||||
|
const mac = this.generateMac(username, password, admin, nonce);
|
||||||
|
|
||||||
|
// Make the registration request
|
||||||
|
const response = await fetch(`${this.baseUrl}/_synapse/admin/v1/register`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nonce,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
displayname: displayName || username,
|
||||||
|
admin,
|
||||||
|
mac,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
throw new Error(
|
||||||
|
`Failed to register user ${username}: ${response.status} ${error}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a nonce for registration
|
||||||
|
* The nonce is required for the HMAC calculation
|
||||||
|
*
|
||||||
|
* @returns A nonce string
|
||||||
|
*/
|
||||||
|
private async getNonce(): Promise<string> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/_synapse/admin/v1/register`, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get nonce: ${response.status} ${await response.text()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate HMAC for shared secret registration
|
||||||
|
* This is the authentication mechanism for the admin API
|
||||||
|
*
|
||||||
|
* @param username - The username
|
||||||
|
* @param password - The password
|
||||||
|
* @param admin - Whether the user is an admin
|
||||||
|
* @param nonce - The nonce from the server
|
||||||
|
* @returns The HMAC hex string
|
||||||
|
*/
|
||||||
|
private generateMac(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
admin: boolean,
|
||||||
|
nonce: string,
|
||||||
|
): string {
|
||||||
|
const mac = createHmac("sha1", this.sharedSecret);
|
||||||
|
mac.update(nonce);
|
||||||
|
mac.update("\x00");
|
||||||
|
mac.update(username);
|
||||||
|
mac.update("\x00");
|
||||||
|
mac.update(password);
|
||||||
|
mac.update("\x00");
|
||||||
|
mac.update(admin ? "admin" : "notadmin");
|
||||||
|
|
||||||
|
return mac.digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new SynapseAdmin instance for a different homeserver
|
||||||
|
*
|
||||||
|
* @param baseUrl - The base URL of the homeserver
|
||||||
|
* @param sharedSecret - The shared secret (defaults to test secret)
|
||||||
|
* @returns A new SynapseAdmin instance
|
||||||
|
*/
|
||||||
|
public static forHomeserver(
|
||||||
|
baseUrl: string,
|
||||||
|
sharedSecret: string = "test_shared_secret_for_local_dev_only",
|
||||||
|
): SynapseAdmin {
|
||||||
|
return new SynapseAdmin(baseUrl, sharedSecret);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
} from "@playwright/test";
|
} from "@playwright/test";
|
||||||
import { type MatrixClient } from "matrix-js-sdk";
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
|
|
||||||
|
import { SynapseAdmin } from "../utils/synapse-admin.ts";
|
||||||
|
|
||||||
const PASSWORD = "foobarbaz1!";
|
const PASSWORD = "foobarbaz1!";
|
||||||
|
|
||||||
export const HOST1 = "https://app.m.localhost/#/welcome";
|
export const HOST1 = "https://app.m.localhost/#/welcome";
|
||||||
@@ -74,29 +76,41 @@ export class TestHelpers {
|
|||||||
clientHandle: JSHandle<MatrixClient>;
|
clientHandle: JSHandle<MatrixClient>;
|
||||||
mxId: string;
|
mxId: string;
|
||||||
}> {
|
}> {
|
||||||
|
// Determine which homeserver to use based on the host
|
||||||
|
const synapseBaseUrl =
|
||||||
|
host === HOST2
|
||||||
|
? "https://synapse.othersite.m.localhost"
|
||||||
|
: "https://synapse.m.localhost";
|
||||||
|
|
||||||
|
// Register user via Synapse Admin API to speed things up
|
||||||
|
const synapseAdmin = SynapseAdmin.forHomeserver(synapseBaseUrl);
|
||||||
|
const credentials = await synapseAdmin.registerUser(
|
||||||
|
username,
|
||||||
|
PASSWORD,
|
||||||
|
username,
|
||||||
|
);
|
||||||
|
|
||||||
|
// STEP 2: Open browser and login
|
||||||
const userContext = await browser.newContext({
|
const userContext = await browser.newContext({
|
||||||
reducedMotion: "reduce",
|
reducedMotion: "reduce",
|
||||||
});
|
});
|
||||||
const page = await userContext.newPage();
|
const page = await userContext.newPage();
|
||||||
await page.goto(host);
|
await page.goto(host + "/#/login");
|
||||||
await page.getByRole("link", { name: "Create Account" }).click();
|
|
||||||
|
await page.getByRole("link", { name: "Sign in" }).click();
|
||||||
|
|
||||||
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
await page.getByRole("textbox", { name: "Username" }).fill(username);
|
||||||
await page.getByRole("textbox", { name: "Password", exact: true }).click();
|
await page.getByRole("textbox", { name: "Password" }).fill(PASSWORD);
|
||||||
await page
|
await page.getByRole("button", { name: "Sign in" }).click();
|
||||||
.getByRole("textbox", { name: "Password", exact: true })
|
|
||||||
.fill(PASSWORD);
|
// 😤For reasons web is staying on an infinite loading page after login, so we reload the page
|
||||||
await page.getByRole("textbox", { name: "Confirm password" }).click();
|
// Super annoying to have to wait...
|
||||||
await page
|
await page.waitForTimeout(2000);
|
||||||
.getByRole("textbox", { name: "Confirm password" })
|
await page.reload();
|
||||||
.fill(PASSWORD);
|
|
||||||
await page.getByRole("button", { name: "Register" }).click();
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole("heading", { name: `Welcome ${username}` }),
|
page.getByRole("heading", { name: `Welcome ${username}` }),
|
||||||
).toBeVisible({
|
).toBeVisible();
|
||||||
// Increase timeout as registration can be slow :/
|
|
||||||
timeout: 15_000,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.maybeDismissBrowserNotSupportedToast(page);
|
await this.maybeDismissBrowserNotSupportedToast(page);
|
||||||
await this.maybeDismissServiceWorkerWarningToast(page);
|
await this.maybeDismissServiceWorkerWarningToast(page);
|
||||||
@@ -106,11 +120,7 @@ export class TestHelpers {
|
|||||||
const clientHandle = await page.evaluateHandle(() =>
|
const clientHandle = await page.evaluateHandle(() =>
|
||||||
window.mxMatrixClientPeg.get(),
|
window.mxMatrixClientPeg.get(),
|
||||||
);
|
);
|
||||||
const mxId = (await clientHandle.evaluate(
|
const mxId = credentials.user_id;
|
||||||
(cli: MatrixClient) => cli.getUserId(),
|
|
||||||
clientHandle,
|
|
||||||
))!;
|
|
||||||
|
|
||||||
return { page, clientHandle, mxId };
|
return { page, clientHandle, mxId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user