Login: delete old files

This commit is contained in:
Timofey Boyko 2024-05-30 13:08:50 +03:00
parent 9c986d3bc7
commit a3af4a49cc
7 changed files with 2 additions and 1344 deletions

View File

@ -1,50 +0,0 @@
import React from "react";
import Login from "./components/Login";
import { Routes, Route } from "react-router-dom";
import InvalidRoute from "./components/Invalid";
import CodeLogin from "./components/CodeLogin";
import initLoginStore from "../store";
import { Provider as MobxProvider } from "mobx-react";
import SimpleNav from "../client/components/sub-components/SimpleNav";
import { WRONG_PORTAL_NAME_URL } from "@docspace/shared/constants";
interface ILoginProps extends IInitialState {
isDesktopEditor?: boolean;
theme: IUserTheme;
setTheme: (theme: IUserTheme) => void;
}
const App: React.FC<ILoginProps> = (props) => {
const loginStore = initLoginStore(props?.currentColorScheme || {});
React.useEffect(() => {
if (window && props.error) {
const { status, standalone, message } = props.error;
if (status === 404 && !standalone) {
const url = new URL(WRONG_PORTAL_NAME_URL);
url.searchParams.append("url", window.location.hostname);
window.location.replace(url);
}
throw new Error(message);
}
}, []);
return (
<MobxProvider {...loginStore}>
<SimpleNav {...props} />
<Routes>
<Route
path="/login/consent"
element={<Login isConsent={props.isAuth} {...props} />}
/>
<Route path="/login/error" element={<InvalidRoute {...props} />} />
{/*<Route path="/login/code" element={<CodeLogin {...props} />} />*/}
<Route path="/login" element={<Login {...props} />} />
</Routes>
</MobxProvider>
);
};
export default App;

View File

@ -1,360 +0,0 @@
import React, { useState, useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import { ButtonsWrapper, LoginFormWrapper, LoginContent } from "./StyledLogin";
import { Text } from "@docspace/shared/components/text";
import { SocialButton } from "@docspace/shared/components/social-button";
import {
getProviderTranslation,
getOAuthToken,
getLoginLink,
} from "@docspace/shared/utils/common";
import { checkIsSSR } from "@docspace/shared/utils";
import { PROVIDERS_DATA } from "@docspace/shared/constants";
import { Link } from "@docspace/shared/components/link";
import { Toast } from "@docspace/shared/components/toast";
import LoginForm from "./sub-components/LoginForm";
import RecoverAccessModalDialog from "@docspace/shared/components/recover-access-modal-dialog/RecoverAccessModalDialog";
import MoreLoginModal from "@docspace/shared/components/more-login-modal";
import { FormWrapper } from "@docspace/shared/components/form-wrapper";
import Register from "./sub-components/register-container";
import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme";
import SSOIcon from "PUBLIC_DIR/images/sso.react.svg";
import { Dark, Base } from "@docspace/shared/themes";
import { useMounted } from "../helpers/useMounted";
import { getBgPattern, frameCallCommand } from "@docspace/shared/utils/common";
import useIsomorphicLayoutEffect from "../hooks/useIsomorphicLayoutEffect";
import { getLogoFromPath, getSystemTheme } from "@docspace/shared/utils";
import { TenantStatus } from "@docspace/shared/enums";
import Consent from "./sub-components/Consent";
import { IClientProps, IScope } from "@docspace/common/utils/oauth/interfaces";
const themes = {
Dark: Dark,
Base: Base,
};
interface ILoginProps extends IInitialState {
isDesktopEditor?: boolean;
theme: IUserTheme;
setTheme: (theme: IUserTheme) => void;
isBaseTheme: boolean;
isConsent?: boolean;
}
const Login: React.FC<ILoginProps> = ({
portalSettings,
providers,
capabilities,
isDesktopEditor,
match,
currentColorScheme,
theme,
setTheme,
logoUrls,
isBaseTheme,
oauth,
isConsent,
}) => {
const isOAuthPage = !!oauth?.client.name;
const isRestoringPortal =
portalSettings?.tenantStatus === TenantStatus.PortalRestore;
useEffect(() => {
isRestoringPortal && window.location.replace("/preparation-portal");
}, []);
const [isLoading, setIsLoading] = useState(false);
const [moreAuthVisible, setMoreAuthVisible] = useState(false);
const [recoverDialogVisible, setRecoverDialogVisible] = useState(false);
const [isConsentPage, setIsConsentPage] = useState(
isConsent || oauth?.isConsent
);
const [scopes, setScopes] = useState(oauth?.scopes || ([] as IScope[]));
const [oauthClient, setOAuthClient] = useState(
oauth?.client || ({} as IClientProps)
);
const [self, setSelf] = useState(oauth?.self || ({} as ISelf));
const [hashSettings, setHashSettings] = useState<null | PasswordHashType>(
null
);
const {
enabledJoin,
greetingSettings,
enableAdmMess,
cookieSettingsEnabled,
} = portalSettings || {
enabledJoin: false,
greetingSettings: false,
enableAdmMess: false,
cookieSettingsEnabled: false,
};
const ssoLabel = capabilities?.ssoLabel || "";
const ssoUrl = capabilities?.ssoUrl || "";
const { t } = useTranslation(["Login", "Common"]);
const mounted = useMounted();
useIsomorphicLayoutEffect(() => {
const systemTheme = getSystemTheme();
const theme = themes[systemTheme];
setTheme(theme);
frameCallCommand("setIsLoaded");
}, []);
const ssoExists = () => {
if (ssoUrl) return true;
else return false;
};
const ssoButton = () => {
const onClick = () => (window.location.href = ssoUrl);
return (
<div className="buttonWrapper">
<SocialButton
IconComponent={SSOIcon}
className="socialButton"
label={ssoLabel || getProviderTranslation("sso", t)}
onClick={onClick}
isDisabled={isLoading}
/>
</div>
);
};
const oauthDataExists = () => {
if (!capabilities?.oauthEnabled) return false;
let existProviders = 0;
providers && providers.length > 0;
providers?.map((item) => {
if (!PROVIDERS_DATA[item.provider]) return;
existProviders++;
});
return !!existProviders;
};
const onSocialButtonClick = useCallback(
async (e: HTMLElementEvent<HTMLButtonElement | HTMLElement>) => {
const { target } = e;
let targetElement = target;
if (
!(targetElement instanceof HTMLButtonElement) &&
target.parentElement
) {
targetElement = target.parentElement;
}
const providerName = targetElement.dataset.providername;
let url = targetElement.dataset.url || "";
try {
//Lifehack for Twitter
if (providerName == "twitter") {
url += "authCallback";
}
const tokenGetterWin = isDesktopEditor
? (window.location.href = url)
: window.open(
url,
"login",
"width=800,height=500,status=no,toolbar=no,menubar=no,resizable=yes,scrollbars=no"
);
const code: string = await getOAuthToken(tokenGetterWin);
const token = window.btoa(
JSON.stringify({
auth: providerName,
mode: "popup",
callback: "authCallback",
})
);
if (tokenGetterWin && typeof tokenGetterWin !== "string")
tokenGetterWin.location.href = getLoginLink(token, code);
} catch (err) {
console.log(err);
}
},
[]
);
const providerButtons = () => {
const providerButtons =
providers &&
providers.map((item, index) => {
if (!PROVIDERS_DATA[item.provider]) return;
if (index > 1) return;
const { icon, label, iconOptions, className } =
PROVIDERS_DATA[item.provider];
return (
<div className="buttonWrapper" key={`${item.provider}ProviderItem`}>
<SocialButton
iconName={icon}
label={getProviderTranslation(label, t)}
className={`socialButton ${className ? className : ""}`}
$iconOptions={iconOptions}
data-url={item.url}
data-providername={item.provider}
onClick={onSocialButtonClick}
isDisabled={isLoading}
/>
</div>
);
});
return providerButtons;
};
const moreAuthOpen = () => {
setMoreAuthVisible(true);
};
const moreAuthClose = () => {
setMoreAuthVisible(false);
};
const openRecoverDialog = () => {
setRecoverDialogVisible(true);
};
const closeRecoverDialog = () => {
setRecoverDialogVisible(false);
};
const bgPattern = getBgPattern(currentColorScheme?.id);
const logo = logoUrls && Object.values(logoUrls)[1];
const logoUrl = !logo
? undefined
: !theme?.isBase
? getLogoFromPath(logo.path.dark)
: getLogoFromPath(logo.path.light);
if (!mounted) return <></>;
if (isRestoringPortal) return <></>;
return (
<LoginFormWrapper
id="login-page"
enabledJoin={enabledJoin}
isDesktop={isDesktopEditor}
bgPattern={bgPattern}
>
<div className="bg-cover"></div>
<LoginContent enabledJoin={enabledJoin}>
<ColorTheme themeId={ThemeId.LinkForgotPassword}>
<img src={logoUrl} className="logo-wrapper" />
<Text
fontSize="23px"
fontWeight={700}
textAlign="center"
className="greeting-title"
>
{greetingSettings}
</Text>
{isConsentPage && isOAuthPage ? (
<Consent
oauth={{ ...oauth, scopes, client: oauthClient, self }}
theme={theme}
hashSettings={portalSettings?.passwordHash || hashSettings}
setHashSettings={setHashSettings}
setIsConsentScreen={setIsConsentPage}
/>
) : (
<>
<FormWrapper id="login-form" theme={theme}>
{ssoExists() && !isOAuthPage && (
<ButtonsWrapper>{ssoButton()}</ButtonsWrapper>
)}
{oauthDataExists() && !isOAuthPage && (
<>
<ButtonsWrapper>{providerButtons()}</ButtonsWrapper>
{providers && providers.length > 2 && (
<Link
isHovered
type="action"
fontSize="13px"
fontWeight="600"
color={currentColorScheme?.main?.accent}
className="more-label"
onClick={moreAuthOpen}
>
{t("Common:ShowMore")}
</Link>
)}
</>
)}
{(oauthDataExists() || ssoExists()) && !isOAuthPage && (
<div className="line">
<Text className="or-label">{t("Or")}</Text>
</div>
)}
<LoginForm
isBaseTheme={isBaseTheme}
recaptchaPublicKey={portalSettings?.recaptchaPublicKey}
isDesktop={!!isDesktopEditor}
isLoading={isLoading}
hashSettings={portalSettings?.passwordHash || hashSettings}
setIsLoading={setIsLoading}
openRecoverDialog={openRecoverDialog}
match={match}
enableAdmMess={enableAdmMess}
cookieSettingsEnabled={cookieSettingsEnabled}
isOAuthPage={isOAuthPage}
oauth={oauth}
setIsConsentPage={setIsConsentPage}
setScopes={setScopes}
setOAuthClient={setOAuthClient}
setSelf={setSelf}
/>
</FormWrapper>
<Toast />
<MoreLoginModal
visible={moreAuthVisible}
onClose={moreAuthClose}
providers={providers}
onSocialLoginClick={onSocialButtonClick}
ssoLabel={ssoLabel}
ssoUrl={ssoUrl}
t={t}
/>
<RecoverAccessModalDialog
visible={recoverDialogVisible}
onClose={closeRecoverDialog}
textBody={t("RecoverTextBody")}
emailPlaceholderText={t("RecoverContactEmailPlaceholder")}
id="recover-access-modal"
/>
</>
)}
</ColorTheme>
</LoginContent>
{!checkIsSSR() && !oauth?.self && enabledJoin && (
<Register
id="login_register"
enabledJoin={enabledJoin}
currentColorScheme={currentColorScheme}
trustedDomains={portalSettings?.trustedDomains}
trustedDomainsType={portalSettings?.trustedDomainsType}
/>
)}
</LoginFormWrapper>
);
};
export default inject(({ loginStore }) => {
return {
theme: loginStore.theme,
setTheme: loginStore.setTheme,
isBaseTheme: loginStore.theme.isBase,
};
})(observer(Login));

View File

@ -1,527 +0,0 @@
import React, { useState, useRef, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { FieldContainer } from "@docspace/shared/components/field-container";
import { EmailInput } from "@docspace/shared/components/email-input";
import { PasswordInput } from "@docspace/shared/components/password-input";
import { Checkbox } from "@docspace/shared/components/checkbox";
import { HelpButton } from "@docspace/shared/components/help-button";
import { Text } from "@docspace/shared/components/text";
import { Link, LinkType } from "@docspace/shared/components/link";
import { useTranslation } from "react-i18next";
import ForgotPasswordModalDialog from "./forgot-password-modal-dialog";
import { Button, ButtonSize } from "@docspace/shared/components/button";
import { createPasswordHash } from "@docspace/shared/utils/common";
import { checkIsSSR } from "@docspace/shared/utils";
import { checkPwd } from "@docspace/shared/utils/desktop";
import { login } from "@docspace/shared/utils/loginUtils";
import { toastr } from "@docspace/shared/components/toast";
import { thirdPartyLogin } from "@docspace/shared/api/user";
import { setWithCredentialsStatus } from "@docspace/shared/api/client";
import { isMobileOnly } from "react-device-detect";
import ReCAPTCHA from "react-google-recaptcha";
import { OAuthLinksContainer, StyledCaptcha } from "../StyledLogin";
import OAuthClientInfo from "./oauth-client-info";
import SelectUser from "./SelectUser";
import { getClient, getScopeList } from "@docspace/shared/api/oauth";
import { getUser } from "@docspace/shared/api/people";
import { onOAuthLogin } from "@docspace/shared/api/oauth";
import { IClientProps, IScope } from "@docspace/shared/utils/oauth/interfaces";
import { setCookie } from "@docspace/shared/utils/cookie";
import { InputSize } from "@docspace/shared/components/text-input";
interface ILoginFormProps {
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
hashSettings: PasswordHashType | null;
isDesktop: boolean;
match: MatchType;
openRecoverDialog: () => void;
enableAdmMess: boolean;
recaptchaPublicKey: CaptchaPublicKeyType;
isBaseTheme: boolean;
oauth?: IOAuthState;
isOAuthPage?: boolean;
setIsConsentPage?: (val: boolean) => void;
setScopes: (val: IScope[]) => void;
setOAuthClient: (val: IClientProps) => void;
setSelf: (val: ISelf) => void;
}
const settings = {
minLength: 6,
upperCase: false,
digits: false,
specSymbols: false,
};
const LoginForm: React.FC<ILoginFormProps> = ({
isLoading,
hashSettings,
isDesktop,
match,
setIsLoading,
openRecoverDialog,
enableAdmMess,
cookieSettingsEnabled,
recaptchaPublicKey,
isBaseTheme,
oauth,
isOAuthPage,
setIsConsentPage,
setScopes,
setOAuthClient,
setSelf,
}) => {
const navigate = useNavigate();
const captchaRef = useRef(null);
const [isEmailErrorShow, setIsEmailErrorShow] = useState(false);
const [errorText, setErrorText] = useState("");
const [identifier, setIdentifier] = useState("");
const [passwordValid, setPasswordValid] = useState(true);
const [identifierValid, setIdentifierValid] = useState(true);
const [password, setPassword] = useState("");
const [isDisabled, setIsDisabled] = useState(false);
const [isChecked, setIsChecked] = useState(false);
const [isDialogVisible, setIsDialogVisible] = useState(false);
const [isCaptcha, setIsCaptcha] = useState(false);
const [isWithoutPasswordLogin, setIsWithoutPasswordLogin] =
useState(IS_ROOMS_MODE);
const [isCaptchaSuccessful, setIsCaptchaSuccess] = useState(false);
const [isCaptchaError, setIsCaptchaError] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const { t, ready } = useTranslation(["Login", "Common", "Consent"]);
const { message, confirmedEmail, authError } = match || {
message: "",
confirmedEmail: "",
authError: "",
};
const authCallback = (profile: string) => {
localStorage.removeItem("profile");
localStorage.removeItem("code");
thirdPartyLogin(profile)
.then((response) => {
if (!(response || response.token || response.confirmUrl))
throw new Error("Empty API response");
setWithCredentialsStatus(true);
if (response.confirmUrl) {
return window.location.replace(response.confirmUrl);
}
const redirectPath = sessionStorage.getItem("referenceUrl");
if (redirectPath) {
sessionStorage.removeItem("referenceUrl");
window.location.href = redirectPath;
} else {
window.location.replace("/");
}
})
.catch(() => {
toastr.error(
t("Common:ProviderNotConnected"),
t("Common:ProviderLoginError")
);
});
};
useEffect(() => {
const profile = localStorage.getItem("profile");
if (!profile) return;
authCallback(profile);
}, []);
useEffect(() => {
message && setErrorText(message);
confirmedEmail && setIdentifier(confirmedEmail);
const messageEmailConfirmed = t("MessageEmailConfirmed");
const messageAuthorize = t("MessageAuthorize");
const text = `${messageEmailConfirmed} ${messageAuthorize}`;
confirmedEmail && ready && toastr.success(text);
authError && ready && toastr.error(t("Common:ProviderLoginError"));
focusInput();
window.authCallback = authCallback;
}, [message, confirmedEmail]);
const onChangeLogin = (e: React.ChangeEvent<HTMLInputElement>) => {
//console.log("onChangeLogin", e.target.value);
setIdentifier(e.target.value);
if (!IS_ROOMS_MODE) setIsEmailErrorShow(false);
onClearErrors();
};
const onClearErrors = () => {
if (IS_ROOMS_MODE) {
!identifierValid && setIdentifierValid(true);
errorText && setErrorText("");
setIsEmailErrorShow(false);
} else {
!passwordValid && setPasswordValid(true);
}
};
const onSubmit = () => {
//errorText && setErrorText("");
let captchaToken = "";
if (recaptchaPublicKey && isCaptcha) {
if (!isCaptchaSuccessful) {
setIsCaptchaError(true);
return;
}
captchaToken = captchaRef.current.getValue();
}
let hasError = false;
const user = identifier.trim();
if (!user) {
hasError = true;
setIdentifierValid(false);
setIsEmailErrorShow(true);
}
if (IS_ROOMS_MODE && identifierValid) {
window.location.replace("/login/code"); //TODO: confirm link?
return;
}
const pass = password.trim();
if (!pass) {
hasError = true;
setPasswordValid(false);
}
if (!identifierValid) hasError = true;
if (hasError) return;
setIsLoading(true);
const hash = createPasswordHash(pass, hashSettings);
isDesktop && checkPwd();
const session = !isChecked;
login(user, hash, session, captchaToken)
.then((res: string | object) => {
if (isOAuthPage) {
const requests = [];
setCookie("client_id", oauth?.clientId || "");
requests.push(getClient(oauth?.clientId || ""));
requests.push(getScopeList());
requests.push(getUser());
requests.push(onOAuthLogin());
Promise.all(requests).then(([client, scopes, self]) => {
setOAuthClient(client);
setScopes(scopes);
setSelf(self);
setIsConsentPage && setIsConsentPage(true);
setIsLoading(false);
});
} else {
const isConfirm = typeof res === "string" && res.includes("confirm");
const redirectPath = sessionStorage.getItem("referenceUrl");
if (redirectPath && !isConfirm) {
sessionStorage.removeItem("referenceUrl");
window.location.href = redirectPath;
return;
}
if (typeof res === "string") window.location.replace(res);
else window.location.replace("/"); //TODO: save { user, hash } for tfa
}
})
.catch((error) => {
let errorMessage = "";
if (typeof error === "object") {
errorMessage =
error?.response?.data?.error?.message ||
error?.statusText ||
error?.message ||
"";
} else {
errorMessage = error;
}
if (recaptchaPublicKey && error?.response?.status === 403) {
setIsCaptcha(true);
}
if (isCaptcha) {
captchaRef.current.reset();
}
setIsEmailErrorShow(true);
setErrorText(errorMessage);
setPasswordValid(!errorMessage);
setIsLoading(false);
focusInput();
});
};
const onLoginWithPasswordClick = () => {
setIsWithoutPasswordLogin(false);
};
const onBlurEmail = () => {
!identifierValid && setIsEmailErrorShow(true);
};
const onValidateEmail = (res: IEmailValid) => {
setIdentifierValid(res.isValid);
setErrorText(res.errors[0]);
};
const focusInput = () => {
if (inputRef && inputRef.current) {
inputRef.current.focus();
}
};
const onChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
onClearErrors();
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Enter") {
onClearErrors();
!isDisabled && onSubmit();
e.preventDefault();
}
};
const onChangeCheckbox = () => setIsChecked(!isChecked);
const onClick = () => {
setIsDialogVisible(true);
setIsDisabled(true);
};
const onDialogClose = () => {
setIsDialogVisible(false);
setIsDisabled(false);
setIsLoading(false);
};
const onSuccessfullyComplete = () => {
setIsCaptchaSuccess(true);
};
return (
<form className="auth-form-container">
{oauth?.client && isOAuthPage && (
<OAuthClientInfo
t={t}
name={oauth.client.name}
logo={oauth.client.logo}
websiteUrl={oauth.client.websiteUrl}
/>
)}
<>
<FieldContainer
isVertical
labelVisible={false}
hasError={isEmailErrorShow}
errorMessage={
errorText ? t(`Common:${errorText}`) : t("Common:RequiredField")
} //TODO: Add wrong login server error
>
<EmailInput
id="login_username"
name="login"
type="email"
hasError={isEmailErrorShow}
value={identifier}
placeholder={t("RegistrationEmailWatermark")}
size={InputSize.large}
scale
isAutoFocussed
tabIndex={1}
isDisabled={isLoading}
autoComplete="username"
onChange={onChangeLogin}
onBlur={onBlurEmail}
onValidateInput={onValidateEmail}
forwardedRef={inputRef}
/>
</FieldContainer>
{(!IS_ROOMS_MODE || !isWithoutPasswordLogin) && (
<>
<FieldContainer
isVertical
labelVisible={false}
hasError={!passwordValid}
errorMessage={!password.trim() ? t("Common:RequiredField") : ""} //TODO: Add wrong password server error
>
<PasswordInput
className="password-input"
simpleView
passwordSettings={settings}
id="login_password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
hasError={!passwordValid}
inputValue={password}
size={InputSize.large}
scale
tabIndex={1}
isDisabled={isLoading}
autoComplete="current-password"
onChange={onChangePassword}
onKeyDown={onKeyDown}
/>
</FieldContainer>
<div className="login-forgot-wrapper">
<div className="login-checkbox-wrapper">
<div className="remember-wrapper">
{!cookieSettingsEnabled && (
<Checkbox
id="login_remember"
className="login-checkbox"
isChecked={isChecked}
onChange={onChangeCheckbox}
label={t("Common:Remember")}
helpButton={
!checkIsSSR() && (
<HelpButton
id="login_remember-hint"
className="help-button"
offsetRight={0}
helpButtonHeaderContent={t("CookieSettingsTitle")}
tooltipContent={
<Text fontSize="12px">{t("RememberHelper")}</Text>
}
tooltipMaxWidth={isMobileOnly ? "240px" : "340px"}
/>
)
}
/>
)}
</div>
<Link
fontSize="13px"
className="login-link"
type="page"
isHovered={false}
onClick={onClick}
id="login_forgot-password-link"
>
{t("ForgotPassword")}
</Link>
</div>
</div>
{isDialogVisible && (
<ForgotPasswordModalDialog
isVisible={isDialogVisible}
userEmail={identifier}
onDialogClose={onDialogClose}
/>
)}
{recaptchaPublicKey && isCaptcha && (
<StyledCaptcha isCaptchaError={isCaptchaError}>
<div className="captcha-wrapper">
<ReCAPTCHA
sitekey={recaptchaPublicKey}
ref={captchaRef}
theme={isBaseTheme ? "light" : "dark"}
onChange={onSuccessfullyComplete}
/>
</div>
{isCaptchaError && (
<Text>{t("Errors:LoginWithBruteForceCaptcha")}</Text>
)}
</StyledCaptcha>
)}
</>
)}
<Button
id="login_submit"
className="login-button"
primary
size={ButtonSize.medium}
scale
label={
isLoading ? t("Common:LoadingProcessing") : t("Common:LoginButton")
}
tabIndex={1}
isDisabled={isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
</>
{/*Uncomment when add api*/}
{(!IS_ROOMS_MODE || !isWithoutPasswordLogin) && !isOAuthPage && (
<div className="login-or-access">
{/*<Link
fontWeight="600"
fontSize="13px"
type={LinkType.action}
isHovered
onClick={onLoginWithCodeClick}
>
{t("SignInWithCode")}
</Link>*/}
{enableAdmMess && (
<>
<Text className="login-or-access-text">{t("Or")}</Text>
<Link
id="login_recover-link"
fontWeight="600"
fontSize="13px"
type={LinkType.action}
isHovered
className="login-link recover-link"
onClick={openRecoverDialog}
>
{t("RecoverAccess")}
</Link>
</>
)}
</div>
)}
{IS_ROOMS_MODE && isWithoutPasswordLogin && !isOAuthPage && (
<div className="login-link">
<Link
fontWeight="600"
fontSize="13px"
type={LinkType.action}
isHovered
onClick={onLoginWithPasswordClick}
>
{t("SignInWithPassword")}
</Link>
</div>
)}
</form>
);
};
export default LoginForm;

View File

@ -37,7 +37,7 @@ import React, {
import { useTranslation } from "react-i18next";
import ReCAPTCHA from "react-google-recaptcha";
import { useTheme } from "styled-components";
import { useSearchParams } from "next/navigation";
import { useSearchParams, useRouter } from "next/navigation";
import { Text } from "@docspace/shared/components/text";
import { Button, ButtonSize } from "@docspace/shared/components/button";
@ -60,7 +60,6 @@ import { StyledCaptcha } from "./LoginForm.styled";
import { LoginDispatchContext, LoginValueContext } from "../Login";
import OAuthClientInfo from "../ConsentInfo";
import api from "@docspace/shared/api";
import { useRouter } from "next/router";
const LoginForm = ({
hashSettings,
@ -298,6 +297,7 @@ const LoginForm = ({
isCaptchaSuccessful,
clientId,
referenceUrl,
router,
]);
const onBlurEmail = () => {

View File

@ -1,39 +0,0 @@
import i18next from "i18next";
import { translations } from "SRC_DIR/autoGeneratedTranslations";
const fallbackLng = "en";
const resources: {
[key: string]: { [key: string]: {} };
} = {};
translations.forEach((lngCol: Map<string, {}>, key: string) => {
resources[key] = {};
lngCol.forEach((data, ns: string) => {
if (resources[key]) {
resources[key][ns] = data;
}
});
});
i18next.init({
fallbackLng: fallbackLng,
load: "currentOnly",
saveMissing: true,
ns: ["Login", "Errors", "Consent", "Common"],
defaultNS: "Login",
resources,
interpolation: {
escapeValue: false,
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
});
export default i18next;

View File

@ -1,235 +0,0 @@
import { getAppearanceTheme } from "@docspace/shared/api/settings";
import express, { Response } from "express";
import template from "./lib/template";
import path from "path";
import compression from "compression";
import ws from "./lib/websocket";
import fs from "fs";
import logger from "morgan";
import winston, { stream } from "./lib/logger";
import { getAssets, getInitialState, getOAuthState } from "./lib/helpers";
import renderApp from "./lib/helpers/render-app";
import i18nextMiddleware from "i18next-express-middleware";
import i18next from "./i18n";
import cookieParser from "cookie-parser";
import { LANGUAGE, COOKIE_EXPIRATION_YEAR } from "@docspace/shared/constants";
import { getLanguage } from "@docspace/shared/utils";
import { initSSR } from "@docspace/shared/api/client";
import { checkIsAuthenticated } from "@docspace/shared/api/user";
import dns from "dns";
import { xss } from "express-xss-sanitizer";
let port = PORT;
dns.setDefaultResultOrder("ipv4first");
const config = fs.readFileSync(path.join(__dirname, "config.json"), "utf-8");
const parsedConfig: IParsedConfig = JSON.parse(config);
if (parsedConfig.PORT) {
port = parsedConfig.PORT;
}
const app = express();
app.use(i18nextMiddleware.handle(i18next));
app.use(compression());
app.use(cookieParser());
app.use(xss());
app.use(
"/login",
express.static(path.resolve(path.join(__dirname, "client")), {
// don`t delete
// https://github.com/pillarjs/send/issues/110
cacheControl: false,
})
);
app.use(
logger("dev", {
stream: stream,
skip: function (req, res) {
if (req.url == "/health") {
return true;
} else {
return false;
}
},
})
);
app.get("*", async (req: ILoginRequest, res: Response, next) => {
const { i18n, cookies, headers, query, t, url } = req;
let initialState: IInitialState = {};
let assets: assetsType;
let standalone = false;
if (url === "/health") {
return res.send({ status: "Healthy" });
}
initSSR(headers);
try {
const oauthClientId =
(query.client_id as string) || (query.clientId as string) || "";
const isOAuth = query.type === "oauth2" && !!oauthClientId;
if (isOAuth && oauthClientId === "error") {
res.redirect("/login/error");
return next();
}
const isAuth = await checkIsAuthenticated();
if (isAuth && !isOAuth && url !== "/login/error") {
res.redirect("/");
return next();
}
initialState = await getInitialState(query, isAuth);
const hideAuthPage = initialState?.ssoSettings?.hideAuthPage;
const ssoUrl = initialState?.capabilities?.ssoUrl;
if (hideAuthPage && ssoUrl && query.skipssoredirect !== "true") {
res.redirect(ssoUrl);
return next();
}
let isCorrectOAuth = false;
if (isOAuth) {
const oauthState: IOAuthState = await getOAuthState(
oauthClientId,
isAuth
);
const isConsent = isAuth;
oauthState.isConsent = !!isConsent;
isCorrectOAuth = !!oauthState?.client.name;
if (isCorrectOAuth) {
initialState.oauth = oauthState;
}
}
if (initialState?.portalSettings?.wizardToken) {
res.redirect("/wizard");
return next();
}
let currentLanguage: string = initialState?.portalSettings?.culture || "en";
standalone = initialState?.portalSettings?.standalone ? true : false;
if (cookies && cookies[LANGUAGE]) {
currentLanguage = cookies[LANGUAGE];
} else {
res.cookie(LANGUAGE, currentLanguage, {
maxAge: COOKIE_EXPIRATION_YEAR,
});
}
currentLanguage = getLanguage(currentLanguage);
if (i18n) await i18n.changeLanguage(currentLanguage);
let initialI18nStore: {
[key: string]: { [key: string]: {} };
} = {};
if (i18n && i18n?.services?.resourceStore?.data) {
for (let key in i18n?.services?.resourceStore?.data) {
if (key === "en" || key === currentLanguage) {
initialI18nStore[key] = i18n.services.resourceStore.data[key];
}
}
}
assets = await getAssets();
const { component, styleTags } = renderApp(i18n, initialState, url);
const htmlString = template(
initialState,
component,
styleTags,
initialI18nStore,
currentLanguage,
assets,
t
);
return res.send(htmlString);
} catch (e) {
let message: string | unknown = e;
if (e instanceof Error) {
message = e.message;
}
const status = e?.response?.status === 404 ? 404 : 520;
if (status !== 404 && !initialState.currentColorScheme) {
const availableThemes: IThemes = await getAppearanceTheme();
const currentColorScheme = availableThemes.themes.find((theme) => {
return availableThemes.selected === theme.id;
});
initialState.currentColorScheme = currentColorScheme;
}
initialState.error = {
status,
standalone,
message,
};
const { component, styleTags } = renderApp(i18n, initialState, url);
assets = await getAssets();
const htmlString = template(
initialState,
component,
styleTags,
{},
"en",
assets,
t
);
winston.error(message);
return res.send(htmlString);
}
});
const server = app.listen(port, () => {
winston.info(`Server is listening on port ${port}`);
});
if (IS_DEVELOPMENT) {
const wss = ws(server);
const manifestFile = path.resolve(
path.join(__dirname, "client/manifest.json")
);
let fsWait = false;
let waitTimeout: timeoutType;
fs.watch(manifestFile, (event, filename) => {
if (filename && event === "change") {
if (fsWait) return;
fsWait = true;
waitTimeout = setTimeout(() => {
fsWait = false;
clearTimeout(waitTimeout);
wss.broadcast && wss.broadcast("reload");
}, 100);
}
});
}

View File

@ -1,131 +0,0 @@
import { translations } from "../../../autoGeneratedTranslations";
import path from "path";
import fs from "fs";
import {
getSettings,
getBuildVersion,
getAuthProviders,
getCapabilities,
getAppearanceTheme,
getLogoUrls,
getCurrentSsoSettings,
} from "@docspace/shared/api/settings";
import { getUser } from "@docspace/shared/api/people";
import { getClient, getScopeList } from "@docspace/shared/api/oauth";
import { IScope } from "@docspace/shared/utils/oauth/interfaces";
import { TenantStatus } from "@docspace/shared/enums";
export const getAssets = (): assetsType => {
const manifest = fs.readFileSync(
path.join(__dirname, "client/manifest.json"),
"utf-8"
);
const assets = JSON.parse(manifest);
return assets;
};
export const getScripts = (assets: assetsType): string[] | void => {
if (!assets || typeof assets !== "object") return;
const regTest = /static\/js\/.*/;
const keys = [];
for (let key in assets) {
if (assets.hasOwnProperty(key) && regTest.test(key)) {
keys.push(key);
}
}
return keys;
};
export const loadPath = (language: string, nameSpace: string): string => {
const path = translations?.get(language)?.get(nameSpace);
return path;
};
export const getInitialState = async (
query: MatchType,
isAuth: boolean
): Promise<IInitialState> => {
let portalSettings: IPortalSettings,
buildInfo: IBuildInfo,
providers: ProvidersType,
capabilities: ICapabilities,
availableThemes: IThemes,
logoUrls: ILogoUrl[],
ssoSettings: ISSOSettings;
const baseSettings = [
getSettings(),
getBuildVersion(),
getAppearanceTheme(),
getLogoUrls(),
];
const settings = [getAuthProviders(), getCapabilities()];
if (!isAuth) settings.push(getCurrentSsoSettings());
[portalSettings, buildInfo, availableThemes, logoUrls] =
await Promise.all(baseSettings);
if (portalSettings.tenantStatus !== TenantStatus.PortalRestore)
[providers, capabilities, ssoSettings] = await Promise.all(settings);
const currentColorScheme = availableThemes.themes.find((theme) => {
return availableThemes.selected === theme.id;
});
const initialState: IInitialState = {
portalSettings,
buildInfo,
providers,
capabilities,
match: query,
currentColorScheme,
logoUrls,
ssoSettings,
};
return initialState;
};
//TODO: get client by id for links
export const getOAuthState = async (
clientId: string,
isAuth: boolean
): Promise<IOAuthState> => {
const requests = [];
requests.push(getClient(clientId, isAuth));
if (isAuth) {
requests.push(getUser());
requests.push(getScopeList());
}
const [client, ...rest] = await Promise.all(requests);
const state: IOAuthState = {
clientId,
state: "",
isConsent: false,
client,
self: undefined,
scopes: undefined,
};
if (isAuth) {
state.self = rest[0] as ISelf;
state.scopes = rest[1] as IScope[];
}
return state;
};