import { Avatar, Box, Button, Card, CardActions, CircularProgress, MenuItem, Select, Tab, Tabs, Typography, } from "@mui/material"; import { useState, useEffect } from "react"; import { Form, FormDataConsumer, Notification, required, useLogin, useNotify, useLocaleState, useTranslate, PasswordInput, TextInput, SelectInput, useLocales, } from "react-admin"; import { useFormContext } from "react-hook-form"; import { useAppContext } from "../Context"; import Footer from "../components/Footer"; import LoginFormBox from "../components/LoginFormBox"; import { getServerVersion, getSupportedFeatures, getSupportedLoginFlows, getWellKnownUrl, isValidBaseUrl, splitMxid, } from "../synapse/matrix"; export type LoginMethod = "credentials" | "accessToken"; const LoginPage = () => { const login = useLogin(); const notify = useNotify(); const { restrictBaseUrl } = useAppContext(); const allowSingleBaseUrl = typeof restrictBaseUrl === "string" && restrictBaseUrl !== ""; const allowMultipleBaseUrls = Array.isArray(restrictBaseUrl) && restrictBaseUrl.length > 0 && restrictBaseUrl[0] !== "" && restrictBaseUrl[0] !== null; const baseUrlChoices = allowMultipleBaseUrls ? restrictBaseUrl.map(url => ({ id: url, name: url })) : []; 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 [supportPassAuth, setSupportPassAuth] = useState(true); const [locale, setLocale] = useLocaleState(); const locales = useLocales(); const translate = useTranslate(); const [ssoBaseUrl, setSSOBaseUrl] = useState(""); const loginToken = new URLSearchParams(window.location.search).get("loginToken"); const [loginMethod, setLoginMethod] = useState("credentials"); const [serverVersion, setServerVersion] = useState(""); const [matrixVersions, setMatrixVersions] = useState(""); useEffect(() => { if (base_url) { checkServerInfo(base_url); } }, []); useEffect(() => { if (!loginToken) { return; } console.log("SSO token is", loginToken); // Prevent further requests const previousUrl = new URL(window.location.toString()); previousUrl.searchParams.delete("loginToken"); window.history.replaceState({}, "", previousUrl.toString()); const baseUrl = localStorage.getItem("sso_base_url"); localStorage.removeItem("sso_base_url"); if (baseUrl) { const auth = { base_url: baseUrl, username: null, password: null, loginToken, }; console.log("Base URL is:", baseUrl); console.log("SSO Token is:", loginToken); console.log("Let's try token login..."); login(auth).catch(error => { alert( typeof error === "string" ? error : typeof error === "undefined" || !error.message ? "ra.auth.sign_in_error" : error.message ); console.error(error); }); } }, [loginToken]); const validateBaseUrl = value => { if (!value.match(/^(http|https):\/\//)) { return translate("synapseadmin.auth.protocol_error"); } else if (!value.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/)) { return translate("synapseadmin.auth.url_error"); } else { return undefined; } }; const handleSubmit = auth => { setLoading(true); const cleanUrl = window.location.href.replace(window.location.search, ""); window.history.replaceState({}, "", cleanUrl); login(auth).catch(error => { setLoading(false); notify( typeof error === "string" ? error : typeof error === "undefined" || !error.message ? "ra.auth.sign_in_error" : error.message, { type: "warning" } ); }); }; const handleSSO = () => { localStorage.setItem("sso_base_url", ssoBaseUrl); const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent( window.location.href )}`; 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 form = useFormContext(); const handleUsernameChange = async () => { if (formData.base_url || allowSingleBaseUrl) { return; } // check if username is a full qualified userId then set base_url accordingly const domain = splitMxid(formData.username)?.domain; if (domain) { const url = await getWellKnownUrl(domain); if (allowAnyBaseUrl || (allowMultipleBaseUrls && restrictBaseUrl.includes(url))) { form.setValue("base_url", url, { shouldValidate: true, shouldDirty: true, }); checkServerInfo(url); } } }; const handleBaseUrlBlurOrChange = event => { // Get the value either from the event (onChange) or from formData (onBlur) const value = event?.target?.value || formData.base_url; if (!value) { return; } // Trigger validation only when user finishes typing/selecting form.trigger("base_url"); checkServerInfo(value); }; useEffect(() => { const params = new URLSearchParams(window.location.search); const hostname = window.location.hostname; const username = params.get("username"); const password = params.get("password"); const accessToken = params.get("accessToken"); let serverURL = params.get("server"); if (username) { form.setValue("username", username); } if (hostname === "localhost" || hostname === "127.0.0.1") { if (password) { form.setValue("password", password); } if (accessToken) { setLoginMethod("accessToken"); form.setValue("accessToken", accessToken); } } if (serverURL) { const isFullUrl = serverURL.match(/^(http|https):\/\//); if (!isFullUrl) { serverURL = `https://${serverURL}`; } form.setValue("base_url", serverURL, { shouldValidate: true, shouldDirty: true, }); checkServerInfo(serverURL); } }, [window.location.search]); return ( <> setLoginMethod(newValue as LoginMethod)} indicatorColor="primary" textColor="primary" centered > {loginMethod === "credentials" ? ( <> ) : ( )} {allowMultipleBaseUrls && ( )} {!allowMultipleBaseUrls && ( )} {serverVersion} {matrixVersions} ); }; return (
{loading ? ( ) : ( )} {translate("synapseadmin.auth.welcome")} {formDataProps => } {loginMethod === "credentials" && ( )} {loginMethod === "accessToken" && ( )}