Merge pull request #585 from etkecc/validate-base-url-on-blur
Do not check homeserver URL during typing in the login form
This commit is contained in:
commit
cc74071933
@ -113,6 +113,7 @@ The following changes are already implemented:
|
|||||||
* 🗂️ [Add Users' Account Data tab](https://github.com/etkecc/synapse-admin/pull/276)
|
* 🗂️ [Add Users' Account Data tab](https://github.com/etkecc/synapse-admin/pull/276)
|
||||||
* 🧾 [Make bulk registration CSV import more user-friendly](https://github.com/etkecc/synapse-admin/pull/411)
|
* 🧾 [Make bulk registration CSV import more user-friendly](https://github.com/etkecc/synapse-admin/pull/411)
|
||||||
* 🌐 [Configurable CORS Credentials](https://github.com/etkecc/synapse-admin/pull/456)
|
* 🌐 [Configurable CORS Credentials](https://github.com/etkecc/synapse-admin/pull/456)
|
||||||
|
* [Do not check homeserver URL during typing in the login form](https://github.com/etkecc/synapse-admin/pull/585)
|
||||||
|
|
||||||
#### exclusive for [etke.cc](https://etke.cc) customers
|
#### exclusive for [etke.cc](https://etke.cc) customers
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
useTranslate,
|
useTranslate,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
SelectInput,
|
||||||
useLocales,
|
useLocales,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
@ -51,16 +52,31 @@ const LoginPage = () => {
|
|||||||
restrictBaseUrl.length > 0 &&
|
restrictBaseUrl.length > 0 &&
|
||||||
restrictBaseUrl[0] !== "" &&
|
restrictBaseUrl[0] !== "" &&
|
||||||
restrictBaseUrl[0] !== null;
|
restrictBaseUrl[0] !== null;
|
||||||
|
const baseUrlChoices = allowMultipleBaseUrls ? restrictBaseUrl.map(url => ({ id: url, name: url })) : [];
|
||||||
const allowAnyBaseUrl = !(allowSingleBaseUrl || allowMultipleBaseUrls);
|
const allowAnyBaseUrl = !(allowSingleBaseUrl || allowMultipleBaseUrls);
|
||||||
|
const localStorageBaseUrl = localStorage.getItem("base_url");
|
||||||
|
let base_url = allowSingleBaseUrl ? restrictBaseUrl : localStorageBaseUrl;
|
||||||
|
if (allowMultipleBaseUrls && localStorageBaseUrl && !restrictBaseUrl.includes(localStorageBaseUrl)) {
|
||||||
|
// don't set base_url if it is not in the restrictBaseUrl array
|
||||||
|
base_url = null;
|
||||||
|
}
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
||||||
const [locale, setLocale] = useLocaleState();
|
const [locale, setLocale] = useLocaleState();
|
||||||
const locales = useLocales();
|
const locales = useLocales();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const base_url = allowSingleBaseUrl ? restrictBaseUrl : localStorage.getItem("base_url");
|
|
||||||
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
||||||
const loginToken = new URLSearchParams(window.location.search).get("loginToken");
|
const loginToken = new URLSearchParams(window.location.search).get("loginToken");
|
||||||
const [loginMethod, setLoginMethod] = useState<LoginMethod>("credentials");
|
const [loginMethod, setLoginMethod] = useState<LoginMethod>("credentials");
|
||||||
|
const [serverVersion, setServerVersion] = useState("");
|
||||||
|
const [matrixVersions, setMatrixVersions] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (base_url) {
|
||||||
|
checkServerInfo(base_url);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loginToken) {
|
if (!loginToken) {
|
||||||
@ -133,54 +149,76 @@ const LoginPage = () => {
|
|||||||
window.location.href = ssoFullUrl;
|
window.location.href = ssoFullUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkServerInfo = async (url: string) => {
|
||||||
|
if (!isValidBaseUrl(url)) {
|
||||||
|
setServerVersion("");
|
||||||
|
setMatrixVersions("");
|
||||||
|
setSupportPassAuth(false);
|
||||||
|
setSSOBaseUrl("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const serverVersion = await getServerVersion(url);
|
||||||
|
setServerVersion(`${translate("synapseadmin.auth.server_version")} ${serverVersion}`);
|
||||||
|
} catch (error) {
|
||||||
|
setServerVersion("");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const features = await getSupportedFeatures(url);
|
||||||
|
setMatrixVersions(`${translate("synapseadmin.auth.supports_specs")} ${features.versions.join(", ")}`);
|
||||||
|
} catch (error) {
|
||||||
|
setMatrixVersions("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set SSO Url
|
||||||
|
try {
|
||||||
|
const loginFlows = await getSupportedLoginFlows(url);
|
||||||
|
const supportPass = loginFlows.find(f => f.type === "m.login.password") !== undefined;
|
||||||
|
const supportSSO = loginFlows.find(f => f.type === "m.login.sso") !== undefined;
|
||||||
|
setSupportPassAuth(supportPass);
|
||||||
|
setSSOBaseUrl(supportSSO ? url : "");
|
||||||
|
} catch (error) {
|
||||||
|
setSupportPassAuth(false);
|
||||||
|
setSSOBaseUrl("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const UserData = ({ formData }) => {
|
const UserData = ({ formData }) => {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const [serverVersion, setServerVersion] = useState("");
|
|
||||||
const [matrixVersions, setMatrixVersions] = useState("");
|
|
||||||
|
|
||||||
const handleUsernameChange = () => {
|
const handleUsernameChange = async () => {
|
||||||
if (formData.base_url || allowSingleBaseUrl) {
|
if (formData.base_url || allowSingleBaseUrl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// check if username is a full qualified userId then set base_url accordingly
|
// check if username is a full qualified userId then set base_url accordingly
|
||||||
const domain = splitMxid(formData.username)?.domain;
|
const domain = splitMxid(formData.username)?.domain;
|
||||||
if (domain) {
|
if (domain) {
|
||||||
getWellKnownUrl(domain).then(url => {
|
const url = await getWellKnownUrl(domain);
|
||||||
if (allowAnyBaseUrl || (allowMultipleBaseUrls && restrictBaseUrl.includes(url)))
|
if (allowAnyBaseUrl || (allowMultipleBaseUrls && restrictBaseUrl.includes(url))) {
|
||||||
form.setValue("base_url", url);
|
form.setValue("base_url", url, {
|
||||||
});
|
shouldValidate: true,
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
checkServerInfo(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleBaseUrlBlurOrChange = (event) => {
|
||||||
if (!formData.base_url) {
|
// Get the value either from the event (onChange) or from formData (onBlur)
|
||||||
form.setValue("base_url", "");
|
const value = event?.target?.value || formData.base_url;
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (formData.base_url === "" && allowMultipleBaseUrls) {
|
|
||||||
form.setValue("base_url", restrictBaseUrl[0]);
|
|
||||||
}
|
|
||||||
if (!isValidBaseUrl(formData.base_url)) return;
|
|
||||||
|
|
||||||
getServerVersion(formData.base_url)
|
// Trigger validation only when user finishes typing/selecting
|
||||||
.then(serverVersion => setServerVersion(`${translate("synapseadmin.auth.server_version")} ${serverVersion}`))
|
form.trigger("base_url");
|
||||||
.catch(() => setServerVersion(""));
|
checkServerInfo(value);
|
||||||
|
};
|
||||||
getSupportedFeatures(formData.base_url)
|
|
||||||
.then(features =>
|
|
||||||
setMatrixVersions(`${translate("synapseadmin.auth.supports_specs")} ${features.versions.join(", ")}`)
|
|
||||||
)
|
|
||||||
.catch(() => setMatrixVersions(""));
|
|
||||||
|
|
||||||
// Set SSO Url
|
|
||||||
getSupportedLoginFlows(formData.base_url)
|
|
||||||
.then(loginFlows => {
|
|
||||||
const supportPass = loginFlows.find(f => f.type === "m.login.password") !== undefined;
|
|
||||||
const supportSSO = loginFlows.find(f => f.type === "m.login.sso") !== undefined;
|
|
||||||
setSupportPassAuth(supportPass);
|
|
||||||
setSSOBaseUrl(supportSSO ? formData.base_url : "");
|
|
||||||
})
|
|
||||||
.catch(() => setSSOBaseUrl(""));
|
|
||||||
}, [formData.base_url, form]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
@ -189,6 +227,7 @@ const LoginPage = () => {
|
|||||||
const password = params.get("password");
|
const password = params.get("password");
|
||||||
const accessToken = params.get("accessToken");
|
const accessToken = params.get("accessToken");
|
||||||
let serverURL = params.get("server");
|
let serverURL = params.get("server");
|
||||||
|
|
||||||
if (username) {
|
if (username) {
|
||||||
form.setValue("username", username);
|
form.setValue("username", username);
|
||||||
}
|
}
|
||||||
@ -202,12 +241,19 @@ const LoginPage = () => {
|
|||||||
form.setValue("accessToken", accessToken);
|
form.setValue("accessToken", accessToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverURL) {
|
if (serverURL) {
|
||||||
const isFullUrl = serverURL.match(/^(http|https):\/\//);
|
const isFullUrl = serverURL.match(/^(http|https):\/\//);
|
||||||
if (!isFullUrl) {
|
if (!isFullUrl) {
|
||||||
serverURL = `https://${serverURL}`;
|
serverURL = `https://${serverURL}`;
|
||||||
}
|
}
|
||||||
form.setValue("base_url", serverURL);
|
|
||||||
|
form.setValue("base_url", serverURL, {
|
||||||
|
shouldValidate: true,
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
checkServerInfo(serverURL);
|
||||||
}
|
}
|
||||||
}, [window.location.search]);
|
}, [window.location.search]);
|
||||||
|
|
||||||
@ -227,7 +273,6 @@ const LoginPage = () => {
|
|||||||
<>
|
<>
|
||||||
<Box>
|
<Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus
|
|
||||||
source="username"
|
source="username"
|
||||||
label="ra.auth.username"
|
label="ra.auth.username"
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
@ -261,23 +306,29 @@ const LoginPage = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box>
|
<Box>
|
||||||
<TextInput
|
{allowMultipleBaseUrls && (
|
||||||
|
<SelectInput
|
||||||
|
source="base_url"
|
||||||
|
label="synapseadmin.auth.base_url"
|
||||||
|
select={allowMultipleBaseUrls}
|
||||||
|
autoComplete="url"
|
||||||
|
{...(loading ? { disabled: true } : {})}
|
||||||
|
onChange={handleBaseUrlBlurOrChange}
|
||||||
|
validate={[required(), validateBaseUrl]}
|
||||||
|
choices={baseUrlChoices}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!allowMultipleBaseUrls && (<TextInput
|
||||||
source="base_url"
|
source="base_url"
|
||||||
label="synapseadmin.auth.base_url"
|
label="synapseadmin.auth.base_url"
|
||||||
select={allowMultipleBaseUrls}
|
|
||||||
autoComplete="url"
|
autoComplete="url"
|
||||||
{...(loading ? { disabled: true } : {})}
|
{...(loading ? { disabled: true } : {})}
|
||||||
readOnly={allowSingleBaseUrl}
|
readOnly={allowSingleBaseUrl}
|
||||||
resettable={allowAnyBaseUrl}
|
resettable={allowAnyBaseUrl}
|
||||||
validate={[required(), validateBaseUrl]}
|
validate={[required(), validateBaseUrl]}
|
||||||
>
|
onBlur={handleBaseUrlBlurOrChange}
|
||||||
{allowMultipleBaseUrls &&
|
/>
|
||||||
restrictBaseUrl.map(url => (
|
)}
|
||||||
<MenuItem key={url} value={url}>
|
|
||||||
{url}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextInput>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Typography className="serverVersion">{serverVersion}</Typography>
|
<Typography className="serverVersion">{serverVersion}</Typography>
|
||||||
<Typography className="matrixVersions">{matrixVersions}</Typography>
|
<Typography className="matrixVersions">{matrixVersions}</Typography>
|
||||||
@ -286,7 +337,7 @@ const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form defaultValues={{ base_url: base_url }} onSubmit={handleSubmit} mode="onTouched">
|
<Form defaultValues={{ base_url: base_url }} onSubmit={handleSubmit} mode="onBlur">
|
||||||
<LoginFormBox>
|
<LoginFormBox>
|
||||||
<Card className="card">
|
<Card className="card">
|
||||||
<Box className="avatar">
|
<Box className="avatar">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user