import { useState, useEffect } from "react"; import { Avatar, Box, Button, Card, CardActions, CircularProgress, MenuItem, Select, Tab, Tabs, Typography } from "@mui/material"; import { Form, FormDataConsumer, Notification, required, useLogin, useNotify, useLocaleState, useTranslate, PasswordInput, TextInput, useLocales, } from "react-admin"; import { useFormContext } from "react-hook-form"; import LoginFormBox from "../components/LoginFormBox"; import { useAppContext } from "../App"; import { getServerVersion, getSupportedFeatures, getSupportedLoginFlows, getWellKnownUrl, isValidBaseUrl, splitMxid, } from "../synapse/matrix"; import Footer from "../components/Footer"; 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 allowAnyBaseUrl = !(allowSingleBaseUrl || allowMultipleBaseUrls); const [loading, setLoading] = useState(false); const [supportPassAuth, setSupportPassAuth] = useState(true); const [locale, setLocale] = useLocaleState(); const locales = useLocales(); const translate = useTranslate(); const base_url = allowSingleBaseUrl ? restrictBaseUrl : localStorage.getItem("base_url"); const [ssoBaseUrl, setSSOBaseUrl] = useState(""); const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href); const [loginMethod, setLoginMethod] = useState("credentials"); useEffect(() => { if (!loginToken) { return; } const ssoToken = loginToken[1]; console.log("SSO token is", ssoToken); // Prevent further requests window.history.replaceState({}, "", window.location.href.replace(loginToken[0], "#").split("#")[0]); const baseUrl = localStorage.getItem("sso_base_url"); localStorage.removeItem("sso_base_url"); if (baseUrl) { const auth = { base_url: baseUrl, username: null, password: null, loginToken: ssoToken, }; console.log("Base URL is:", baseUrl); console.log("SSO Token is:", ssoToken); 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 UserData = ({ formData }) => { const form = useFormContext(); const [serverVersion, setServerVersion] = useState(""); const [matrixVersions, setMatrixVersions] = useState(""); const handleUsernameChange = () => { 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) { getWellKnownUrl(domain).then(url => { if (allowAnyBaseUrl || (allowMultipleBaseUrls && restrictBaseUrl.includes(url))) form.setValue("base_url", url); }); } }; useEffect(() => { if (!formData.base_url) { form.setValue("base_url", ""); } if (formData.base_url === "" && allowMultipleBaseUrls) { form.setValue("base_url", restrictBaseUrl[0]); } if (!isValidBaseUrl(formData.base_url)) return; getServerVersion(formData.base_url) .then(serverVersion => setServerVersion(`${translate("synapseadmin.auth.server_version")} ${serverVersion}`)) .catch(() => setServerVersion("")); 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(() => { 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); } }, [window.location.search]); return ( <> setLoginMethod(newValue as LoginMethod)} indicatorColor="primary" textColor="primary" centered > {loginMethod === "credentials" ? ( <> ) : ( )} {allowMultipleBaseUrls && restrictBaseUrl.map(url => ( {url} ))} {serverVersion} {matrixVersions} ); }; return (
{loading ? ( ) : ( )} {translate("synapseadmin.auth.welcome")} {formDataProps => } {loginMethod === "credentials" && } {loginMethod === "accessToken" && }