diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 5c7eda3b..7adfcb38 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -27,6 +27,8 @@ import HCaptcha, { HeaderContainer } from "../components/HCaptcha"; import MFA from "../components/MFA"; import ForgotPasswordModal from "../components/modals/ForgotPasswordModal"; import { AUTH_NO_BRANDING, useAppStore } from "../stores/AppStore"; +import { Globals } from "../utils/Globals"; +import REST from "../utils/REST"; import { IAPILoginRequest, IAPILoginResponse, @@ -38,6 +40,7 @@ import { messageFromFieldError } from "../utils/messageFromFieldError"; type FormValues = { login: string; password: string; + instance: string; captcha_key?: string; }; @@ -49,6 +52,7 @@ function LoginPage() { const [mfaData, setMfaData] = React.useState(); const captchaRef = React.useRef(null); + const [debounce, setDebounce] = React.useState(null); const { openModal } = useModals(); const { @@ -57,6 +61,7 @@ function LoginPage() { formState: { errors }, setError, setValue, + clearErrors, } = useForm(); const resetCaptcha = () => { @@ -64,6 +69,14 @@ function LoginPage() { setValue("captcha_key", undefined); }; + const getValidURL = (url: string) => { + try { + return new URL(url); + } catch (e) { + return undefined; + } + }; + const onSubmit = handleSubmit((data) => { setLoading(true); setCaptchaSiteKey(undefined); @@ -71,7 +84,8 @@ function LoginPage() { app.rest .post(Routes.login(), { - ...data, + login: data.login, + password: data.password, undelete: false, }) .then((r) => { @@ -157,6 +171,30 @@ function LoginPage() { onSubmit(); }; + const handleInstanceChange = (e: React.ChangeEvent) => { + // set as validating + if (debounce) clearTimeout(debounce); + + const doRequest = async () => { + const url = getValidURL(e.target.value); + if (!url) return; + + const endpoints = await REST.getEndpointsFromDomain(url); + if (!endpoints) + return setError("instance", { + type: "manual", + message: "Instance could not be resolved", + }); + + console.debug(`Instance lookup has set routes to`, endpoints); + Globals.routeSettings = endpoints; // hmm + Globals.save(); + clearErrors("instance"); + }; + + setDebounce(setTimeout(doRequest, 500)); + }; + const forgotPassword = () => { openModal(ForgotPasswordModal); }; @@ -196,6 +234,33 @@ function LoginPage() { marginBottom={true} style={{ marginTop: 0 }} > + + Instance + {errors.instance && ( + + <> + - + {errors.instance.message} + + + )} + + + + + + + Email {errors.login && ( diff --git a/src/utils/Globals.ts b/src/utils/Globals.ts index 04c79b10..efa69384 100644 --- a/src/utils/Globals.ts +++ b/src/utils/Globals.ts @@ -1,13 +1,15 @@ -interface RouteSettings { +export interface RouteSettings { api: string; cdn: string; gateway: string; + wellknown: string; } export const DefaultRouteSettings: RouteSettings = { api: "https://api.old.server.spacebar.chat/api", cdn: "https://cdn.old.server.spacebar.chat", gateway: "wss://gateway.old.server.spacebar.chat", + wellknown: "https://spacebar.chat", }; export const Globals: { diff --git a/src/utils/REST.ts b/src/utils/REST.ts index 0248e21b..65a7e57e 100644 --- a/src/utils/REST.ts +++ b/src/utils/REST.ts @@ -3,7 +3,7 @@ // import {DomainStore} from '../stores/DomainStore'; import AppStore from "../stores/AppStore"; -import { Globals } from "./Globals"; +import { Globals, RouteSettings } from "./Globals"; export default class REST { private app: AppStore; @@ -27,6 +27,47 @@ export default class REST { } } + public static async getEndpointsFromDomain( + url: URL, + ): Promise { + let endpoints = await this.getInstanceDomains(url); + if (endpoints) return { ...endpoints, wellknown: url.toString() }; + + // get endpoints from .well-known + let wellKnown; + try { + wellKnown = await fetch(`${url.origin}/.well-known/spacebar`) + .then((x) => x.json()) + .then((x) => new URL(x.api)); + } catch (e) { + return null; + } + + // well-known was found + endpoints = await this.getInstanceDomains(wellKnown); + if (!endpoints) return null; + return { ...endpoints, wellknown: url.toString() }; + } + + static async getInstanceDomains( + url: URL, + ): Promise | null> { + try { + const endpoints = await fetch( + `${url.toString()}${ + url.pathname.includes("api") ? "" : "api" + }/policies/instance/domains`, + ).then((x) => x.json()); + return { + api: endpoints.apiEndpoint, + gateway: endpoints.gateway, + cdn: endpoints.cdn, + }; + } catch (e) { + return null; + } + } + public static makeAPIUrl( path: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any