Login:Components:TfaActivationForm: add TfaActivationForm
This commit is contained in:
parent
dd38adfb33
commit
8e7bbdc8ad
@ -0,0 +1,242 @@
|
||||
// (c) Copyright Ascensio System SIA 2009-2024
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||
// any third-party rights.
|
||||
//
|
||||
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
//
|
||||
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||
//
|
||||
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||
//
|
||||
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||
// trademark law for use of our trademarks.
|
||||
//
|
||||
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
"use client";
|
||||
|
||||
import styled from "styled-components";
|
||||
|
||||
import {
|
||||
mobile,
|
||||
tablet,
|
||||
getCorrectFourValuesStyle,
|
||||
} from "@docspace/shared/utils";
|
||||
import { Box } from "@docspace/shared/components/box";
|
||||
|
||||
export const StyledPage = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
max-width: 960px;
|
||||
box-sizing: border-box;
|
||||
|
||||
@media ${tablet} {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
width: 100%;
|
||||
padding: ${({ theme }) =>
|
||||
getCorrectFourValuesStyle("32px 8px 0 16px", theme.interfaceDirection)};
|
||||
|
||||
.language-combo-box {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.password-form {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.language-combo-box {
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
top: 28px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledContent = styled.div`
|
||||
min-height: 100vh;
|
||||
flex: 1 0 auto;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
|
||||
@media ${mobile} {
|
||||
width: 100%;
|
||||
justify-content: start;
|
||||
min-height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledHeader = styled.div`
|
||||
.title {
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.portal-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
margin-top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledBody = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 56px auto;
|
||||
|
||||
@media ${mobile} {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.portal-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.password-field-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-change-form {
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.phone-input {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.delete-profile-confirm {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.phone-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledForm = styled(Box)`
|
||||
margin: 56px auto;
|
||||
display: flex;
|
||||
flex: 1fr 1fr;
|
||||
gap: 80px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
@media ${tablet} {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
margin: 0 auto;
|
||||
flex-direction: column;
|
||||
gap: 0px;
|
||||
|
||||
${({ theme }) =>
|
||||
theme.interfaceDirection === "rtl"
|
||||
? `padding-left: 8px;`
|
||||
: `padding-right: 8px;`}
|
||||
}
|
||||
|
||||
.app-code-wrapper {
|
||||
width: 100%;
|
||||
|
||||
@media ${tablet} {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.portal-logo {
|
||||
padding-bottom: 40px;
|
||||
|
||||
@media ${tablet} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.set-app-description {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.set-app-title {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.set-app-text {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.qrcode-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0px 80px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 32px;
|
||||
|
||||
@media ${mobile} {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.app-code-continue-btn {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
254
packages/login/src/components/TfaActivationForm/index.tsx
Normal file
254
packages/login/src/components/TfaActivationForm/index.tsx
Normal file
@ -0,0 +1,254 @@
|
||||
// (c) Copyright Ascensio System SIA 2009-2024
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||
// any third-party rights.
|
||||
//
|
||||
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
//
|
||||
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||
//
|
||||
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||
//
|
||||
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||
// trademark law for use of our trademarks.
|
||||
//
|
||||
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
|
||||
"use client";
|
||||
|
||||
import { useContext, useState } from "react";
|
||||
import { useTheme } from "styled-components";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
import { Link, LinkTarget } from "@docspace/shared/components/link";
|
||||
import { combineUrl } from "@docspace/shared/utils/combineUrl";
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
import PortalLogo from "@docspace/shared/components/portal-logo/PortalLogo";
|
||||
import { Box } from "@docspace/shared/components/box";
|
||||
import { FormWrapper } from "@docspace/shared/components/form-wrapper";
|
||||
import { FieldContainer } from "@docspace/shared/components/field-container";
|
||||
import {
|
||||
InputSize,
|
||||
InputType,
|
||||
TextInput,
|
||||
} from "@docspace/shared/components/text-input";
|
||||
import { Button, ButtonSize } from "@docspace/shared/components/button";
|
||||
import { toastr } from "@docspace/shared/components/toast";
|
||||
import { TColorScheme } from "@docspace/shared/themes";
|
||||
import { TPasswordHash } from "@docspace/shared/api/settings/types";
|
||||
import { loginWithTfaCode } from "@docspace/shared/api/user";
|
||||
import { validateTfaCode } from "@docspace/shared/api/settings";
|
||||
import { getLogoUrl } from "@docspace/shared/utils";
|
||||
import { WhiteLabelLogoType } from "@docspace/shared/enums";
|
||||
|
||||
import {
|
||||
TFA_ANDROID_APP_URL,
|
||||
TFA_IOS_APP_URL,
|
||||
TFA_WIN_APP_URL,
|
||||
} from "@/utils/constants";
|
||||
import { ConfirmRouteContext } from "@/app/(root)/confirm/confirmRoute";
|
||||
import withLoader from "@/app/(root)/confirm/withLoader";
|
||||
import { TError, WithLoaderProps } from "@/types";
|
||||
|
||||
import {
|
||||
StyledContent,
|
||||
StyledForm,
|
||||
StyledPage,
|
||||
} from "./TfaActivationForm.styled";
|
||||
|
||||
const PROXY_BASE_URL = combineUrl(window.ClientConfig?.proxy?.url, "/profile");
|
||||
|
||||
type TfaActivationFormProps = {
|
||||
secretKey: any;
|
||||
qrCode: any;
|
||||
passwordHash: TPasswordHash;
|
||||
userName?: string;
|
||||
|
||||
currentColorScheme?: TColorScheme;
|
||||
} & WithLoaderProps;
|
||||
|
||||
const TfaActivationForm = (props: TfaActivationFormProps) => {
|
||||
const { secretKey, qrCode, passwordHash, userName, currentColorScheme } =
|
||||
props;
|
||||
const { linkData } = useContext(ConfirmRouteContext);
|
||||
const { t } = useTranslation(["Confirm", "Common"]);
|
||||
|
||||
const [code, setCode] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const { confirmHeader = null } = linkData;
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (userName && passwordHash) {
|
||||
await loginWithTfaCode(userName, passwordHash, code);
|
||||
} else {
|
||||
await validateTfaCode(code, confirmHeader);
|
||||
}
|
||||
|
||||
sessionStorage.setItem("openBackupCodesDialog", "true");
|
||||
window.location.href = PROXY_BASE_URL;
|
||||
} catch (error) {
|
||||
const knownError = error as TError;
|
||||
let errorMessage: string;
|
||||
|
||||
if (typeof knownError === "object") {
|
||||
errorMessage =
|
||||
knownError?.response?.data?.error?.message ||
|
||||
knownError?.statusText ||
|
||||
knownError?.message ||
|
||||
"";
|
||||
} else {
|
||||
errorMessage = knownError;
|
||||
}
|
||||
|
||||
setError(errorMessage);
|
||||
toastr.error(errorMessage);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.code === "Enter" || event.code === "NumpadEnter") onSubmit();
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const logoUrl = getLogoUrl(WhiteLabelLogoType.LoginPage, !theme.isBase);
|
||||
|
||||
return (
|
||||
<StyledPage>
|
||||
<StyledContent>
|
||||
<StyledForm className="set-app-container">
|
||||
<Box className="set-app-description" marginProp="0 0 32px 0">
|
||||
<PortalLogo className="portal-logo" />
|
||||
<Text isBold fontSize="14px" className="set-app-title">
|
||||
{t("SetAppTitle")}
|
||||
</Text>
|
||||
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="SetAppDescription"
|
||||
ns="Confirm"
|
||||
productName={t("Common:ProductName")}
|
||||
>
|
||||
The two-factor authentication is enabled to provide additional
|
||||
portal security. Configure your authenticator application to
|
||||
continue work on the portal. For example you could use Google
|
||||
Authenticator for
|
||||
<Link
|
||||
color={currentColorScheme?.main?.accent}
|
||||
href={TFA_ANDROID_APP_URL}
|
||||
target={LinkTarget.blank}
|
||||
>
|
||||
Android
|
||||
</Link>
|
||||
and{" "}
|
||||
<Link
|
||||
color={currentColorScheme?.main?.accent}
|
||||
href={TFA_IOS_APP_URL}
|
||||
target={LinkTarget.blank}
|
||||
>
|
||||
iOS
|
||||
</Link>{" "}
|
||||
or Authenticator for{" "}
|
||||
<Link
|
||||
color={currentColorScheme?.main?.accent}
|
||||
href={TFA_WIN_APP_URL}
|
||||
target={LinkTarget.blank}
|
||||
>
|
||||
Windows Phone
|
||||
</Link>{" "}
|
||||
.
|
||||
</Trans>
|
||||
|
||||
<Text className="set-app-text">
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="SetAppInstallDescription"
|
||||
ns="Confirm"
|
||||
key={secretKey}
|
||||
>
|
||||
To connect your apllication scan the QR code or manually enter
|
||||
your secret key <strong>{{ secretKey }}</strong> then enter
|
||||
6-digit code from your application in the field below.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Box>
|
||||
<FormWrapper>
|
||||
<Box
|
||||
displayProp="flex"
|
||||
flexDirection="column"
|
||||
className="app-code-wrapper"
|
||||
>
|
||||
<div className="qrcode-wrapper">
|
||||
<img src={qrCode} height="180px" width="180px" alt="QR-code" />
|
||||
</div>
|
||||
<Box className="app-code-input">
|
||||
<FieldContainer
|
||||
labelVisible={false}
|
||||
hasError={error ? true : false}
|
||||
errorMessage={error}
|
||||
>
|
||||
<TextInput
|
||||
id="code"
|
||||
name="code"
|
||||
type={InputType.email}
|
||||
size={InputSize.large}
|
||||
scale
|
||||
isAutoFocussed
|
||||
tabIndex={1}
|
||||
placeholder={t("EnterCodePlaceholder")}
|
||||
isDisabled={isLoading}
|
||||
maxLength={6}
|
||||
onChange={(e) => {
|
||||
setCode(e.target.value);
|
||||
setError("");
|
||||
}}
|
||||
value={code}
|
||||
hasError={error ? true : false}
|
||||
onKeyDown={onKeyPress}
|
||||
/>
|
||||
</FieldContainer>
|
||||
</Box>
|
||||
<Box className="app-code-continue-btn">
|
||||
<Button
|
||||
scale
|
||||
primary
|
||||
size={ButtonSize.medium}
|
||||
tabIndex={3}
|
||||
label={
|
||||
isLoading
|
||||
? t("Common:LoadingProcessing")
|
||||
: t("SetAppButton")
|
||||
}
|
||||
isDisabled={!code.length || isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</FormWrapper>
|
||||
</StyledForm>
|
||||
</StyledContent>
|
||||
</StyledPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default withLoader(TfaActivationForm);
|
Loading…
Reference in New Issue
Block a user