Extract helper functions from LoginPage

Change-Id: I507e223d0eff00bac3963d0b71f9bd648b9ab7b1
This commit is contained in:
Manuel Stahl 2024-02-06 11:00:39 +01:00
parent 5b8882bd80
commit 64a89f6552
3 changed files with 110 additions and 70 deletions

View file

@ -1,6 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { import {
fetchUtils,
Form, Form,
FormDataConsumer, FormDataConsumer,
Notification, Notification,
@ -27,6 +26,13 @@ import {
} from "@mui/material"; } from "@mui/material";
import { styled } from "@mui/material/styles"; import { styled } from "@mui/material/styles";
import LockIcon from "@mui/icons-material/Lock"; import LockIcon from "@mui/icons-material/Lock";
import {
getServerVersion,
getSupportedLoginFlows,
getWellKnownUrl,
isValidBaseUrl,
splitMxid,
} from "../synapse/synapse";
const FormBox = styled(Box)(({ theme }) => ({ const FormBox = styled(Box)(({ theme }) => ({
display: "flex", display: "flex",
@ -170,87 +176,42 @@ const LoginPage = () => {
window.location.href = ssoFullUrl; window.location.href = ssoFullUrl;
}; };
const extractHomeServer = username => {
const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/;
if (!username) return null;
const res = username.match(usernameRegex);
if (res) return res[1];
return null;
};
const UserData = ({ formData }) => { const UserData = ({ formData }) => {
const form = useFormContext(); const form = useFormContext();
const [serverVersion, setServerVersion] = useState(""); const [serverVersion, setServerVersion] = useState("");
const handleUsernameChange = _ => { const handleUsernameChange = _ => {
if (formData.base_url || cfg_base_url) return; if (formData.base_url || cfg_base_url) return;
// check if username is a full qualified userId then set base_url accordially // check if username is a full qualified userId then set base_url accordingly
const home_server = extractHomeServer(formData.username); const domain = splitMxid(formData.username)?.domain;
const wellKnownUrl = `https://${home_server}/.well-known/matrix/client`; if (domain) {
if (home_server) { getWellKnownUrl(domain).then(url => form.setValue("base_url", url));
// fetch .well-known entry to get base_url
fetchUtils
.fetchJson(wellKnownUrl, { method: "GET" })
.then(({ json }) => {
form.setValue("base_url", json["m.homeserver"].base_url);
})
.catch(_ => {
// if there is no .well-known entry, try the home server name
form.setValue("base_url", `https://${home_server}`);
});
} }
}; };
useEffect( useEffect(() => {
_ => { if (!isValidBaseUrl(formData.base_url)) return;
if (
!formData.base_url || getServerVersion(formData.base_url)
!formData.base_url.match( .then(serverVersion =>
/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/ setServerVersion(
`${translate("synapseadmin.auth.server_version")} ${serverVersion}`
) )
) )
return; .catch(() => setServerVersion(""));
const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
fetchUtils
.fetchJson(versionUrl, { method: "GET" })
.then(({ json }) => {
setServerVersion(
`${translate("synapseadmin.auth.server_version")} ${
json["server_version"]
}`
);
})
.catch(_ => {
setServerVersion("");
});
// Set SSO Url // Set SSO Url
const authMethodUrl = `${formData.base_url}/_matrix/client/r0/login`; getSupportedLoginFlows(formData.base_url)
let supportPass = false, .then(loginFlows => {
supportSSO = false; const supportPass =
fetchUtils loginFlows.find(f => f.type === "m.login.password") !== undefined;
.fetchJson(authMethodUrl, { method: "GET" }) const supportSSO =
.then(({ json }) => { loginFlows.find(f => f.type === "m.login.sso") !== undefined;
json.flows.forEach(f => { setSupportPassAuth(supportPass);
if (f.type === "m.login.password") { setSSOBaseUrl(supportSSO ? formData.base_url : "");
supportPass = true; })
} else if (f.type === "m.login.sso") { .catch(() => setSSOBaseUrl(""));
supportSSO = true; }, [formData.base_url]);
}
});
setSupportPassAuth(supportPass);
if (supportSSO) {
setSSOBaseUrl(formData.base_url);
} else {
setSSOBaseUrl("");
}
})
.catch(_ => {
setSSOBaseUrl("");
});
},
[formData.base_url]
);
return ( return (
<> <>

48
src/synapse/synapse.js Normal file
View file

@ -0,0 +1,48 @@
import { fetchUtils } from "react-admin";
export const splitMxid = mxid => {
const re =
/^@(?<name>[a-zA-Z0-9._=\-/]+):(?<domain>[a-zA-Z0-9\-.]+\.[a-zA-Z]+)$/;
return re.exec(mxid)?.groups;
};
export const isValidBaseUrl = baseUrl =>
/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/.test(baseUrl);
/**
* Resolve the homeserver URL using the well-known lookup
* @param domain the domain part of an MXID
* @returns homeserver base URL
*/
export const getWellKnownUrl = async domain => {
const wellKnownUrl = `https://${domain}/.well-known/matrix/client`;
try {
const json = await fetchUtils.fetchJson(wellKnownUrl, { method: "GET" });
return json["m.homeserver"].base_url;
} catch {
// if there is no .well-known entry, return the domain itself
return `https://${domain}`;
}
};
/**
* Get synapse server version
* @param base_url the base URL of the homeserver
* @returns server version
*/
export const getServerVersion = async baseUrl => {
const versionUrl = `${baseUrl}/_synapse/admin/v1/server_version`;
const response = await fetchUtils.fetchJson(versionUrl, { method: "GET" });
return response.json.server_version;
};
/**
* Get supported login flows
* @param baseUrl the base URL of the homeserver
* @returns array of supported login flows
*/
export const getSupportedLoginFlows = async baseUrl => {
const loginFlowsUrl = `${baseUrl}/_matrix/client/r0/login`;
const response = await fetchUtils.fetchJson(loginFlowsUrl, { method: "GET" });
return response.json.flows;
};

View file

@ -0,0 +1,31 @@
import { isValidBaseUrl, splitMxid } from "./synapse";
describe("splitMxid", () => {
it("splits valid MXIDs", () =>
expect(splitMxid("@name:domain.tld")).toEqual({
name: "name",
domain: "domain.tld",
}));
it("rejects invalid MXIDs", () => expect(splitMxid("foo")).toBeUndefined());
});
describe("isValidBaseUrl", () => {
it("accepts a http URL", () =>
expect(isValidBaseUrl("http://foo.bar")).toBeTruthy());
it("accepts a https URL", () =>
expect(isValidBaseUrl("https://foo.bar")).toBeTruthy());
it("accepts a valid URL with port", () =>
expect(isValidBaseUrl("https://foo.bar:1234")).toBeTruthy());
it("rejects undefined base URLs", () =>
expect(isValidBaseUrl(undefined)).toBeFalsy());
it("rejects null base URLs", () => expect(isValidBaseUrl(null)).toBeFalsy());
it("rejects empty base URLs", () => expect(isValidBaseUrl("")).toBeFalsy());
it("rejects non-string base URLs", () =>
expect(isValidBaseUrl({})).toBeFalsy());
it("rejects base URLs without protocol", () =>
expect(isValidBaseUrl("foo.bar")).toBeFalsy());
it("rejects base URLs with path", () =>
expect(isValidBaseUrl("http://foo.bar/path")).toBeFalsy());
it("rejects invalid base URLs", () =>
expect(isValidBaseUrl("http:/foo.bar")).toBeFalsy());
});