From dfb84e99328f5f3287f89e4b542d0421dd1cee6c Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 27 Apr 2023 23:10:27 +0800 Subject: [PATCH] developed initial version of new login page --- backend/src/routes/v1/auth.ts | 2 +- frontend/src/components/login/LoginStep.tsx | 5 +- .../components/login/PasswordInputStep.tsx | 138 ++++++++++++++++++ .../src/components/utilities/attemptLogin.ts | 30 +++- frontend/src/pages/api/auth/Login1.ts | 11 +- frontend/src/pages/api/auth/Login2.ts | 11 +- frontend/src/pages/login.tsx | 86 +++++++---- 7 files changed, 239 insertions(+), 44 deletions(-) create mode 100644 frontend/src/components/login/PasswordInputStep.tsx diff --git a/backend/src/routes/v1/auth.ts b/backend/src/routes/v1/auth.ts index 0999e9a2d..2ff0e6d27 100644 --- a/backend/src/routes/v1/auth.ts +++ b/backend/src/routes/v1/auth.ts @@ -84,7 +84,7 @@ router.post( ); router.get( - '/login/federated/google', + '/login/google', authLimiter, passport.authenticate('google', { scope: ['profile', 'email'], diff --git a/frontend/src/components/login/LoginStep.tsx b/frontend/src/components/login/LoginStep.tsx index b19bafdd9..a45ebe3e5 100644 --- a/frontend/src/components/login/LoginStep.tsx +++ b/frontend/src/components/login/LoginStep.tsx @@ -47,7 +47,10 @@ export default function LoginStep ({ } setIsLoading(true); - const isLoginSuccessful = await attemptLogin(email, password); + const isLoginSuccessful = await attemptLogin({ + email, + password, + }); if (isLoginSuccessful && isLoginSuccessful.success) { // case: login was successful diff --git a/frontend/src/components/login/PasswordInputStep.tsx b/frontend/src/components/login/PasswordInputStep.tsx new file mode 100644 index 000000000..b3c881a03 --- /dev/null +++ b/frontend/src/components/login/PasswordInputStep.tsx @@ -0,0 +1,138 @@ +import React, { useState } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { useTranslation } from 'next-i18next'; + +import Button from '@app/components/basic/buttons/Button'; +import Error from '@app/components/basic/Error'; +import InputField from '@app/components/basic/InputField'; +import attemptLogin from '@app/components/utilities/attemptLogin'; +import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; + +import SecurityClient from '../utilities/SecurityClient'; + +export default function PasswordInputStep({ + userId, + email, + password, + setPassword, + setProviderAuthToken, + setStep +}: { + email: string; + userId: string; + password: string; + setPassword: (password: string) => void; + setProviderAuthToken: (value: string) => void; + setStep: (step: number) => void; +}) { + const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); + const [loginError, setLoginError] = useState(false); + + const { t } = useTranslation(); + + const handleLogin = async () => { + try { + if (!userId || !password) { + return; + } + + setIsLoading(true); + const isLoginSuccessful = await attemptLogin({ + userId, + email, + password + }); + + if (isLoginSuccessful && isLoginSuccessful.success) { + // case: login was successful + + if (isLoginSuccessful.mfaEnabled) { + // case: login requires MFA step + setStep(2); + setIsLoading(false); + return; + } + + // case: login does not require MFA step + router.push(`/dashboard/${localStorage.getItem('projectData.id')}`); + } + } catch (err) { + setLoginError(true); + } + + setIsLoading(false); + }; + + return ( +
e.preventDefault()}> +
+

+ {t('login:login')} +

+
+ +
+ + + +
+
+ {!isLoading && loginError && } +
+
+
+
+
+
+

{t('login:need-account')}

+ + + +
+ +
+ +
+
+ ); +} + +export const getStaticProps = getTranslatedStaticProps(['auth', 'login']); diff --git a/frontend/src/components/utilities/attemptLogin.ts b/frontend/src/components/utilities/attemptLogin.ts index e92a65e77..08bef1441 100644 --- a/frontend/src/components/utilities/attemptLogin.ts +++ b/frontend/src/components/utilities/attemptLogin.ts @@ -26,20 +26,33 @@ interface IsLoginSuccessful { * @param {string} password - password of user to log in */ const attemptLogin = async ( - email: string, - password: string + { + email, + password, + userId, + }: { + email: string; + userId?: string; + password: string; + } ): Promise => { + + const username = userId ?? email; const telemetry = new Telemetry().getInstance(); return new Promise((resolve, reject) => { client.init( { - username: email, + username, password }, async () => { try { const clientPublicKey = client.getPublicKey(); - const { serverPublicKey, salt } = await login1(email, clientPublicKey); + const { serverPublicKey, salt } = await login1({ + email, + clientPublicKey, + userId, + }); client.setSalt(salt); client.setServerPublicKey(serverPublicKey); @@ -57,8 +70,11 @@ const attemptLogin = async ( iv, tag } = await login2( - email, - clientProof + { + email, + userId, + clientProof, + } ); if (mfaEnabled) { @@ -137,4 +153,4 @@ const attemptLogin = async ( }); }; -export default attemptLogin; \ No newline at end of file +export default attemptLogin; diff --git a/frontend/src/pages/api/auth/Login1.ts b/frontend/src/pages/api/auth/Login1.ts index bb7afc82b..2685a770c 100644 --- a/frontend/src/pages/api/auth/Login1.ts +++ b/frontend/src/pages/api/auth/Login1.ts @@ -9,16 +9,17 @@ interface Login1 { * @param {*} clientPublicKey * @returns */ -const login1 = async (email: string, clientPublicKey: string) => { +const login1 = async (loginDetails: { + email: string; + clientPublicKey: string; + userId?: string; +}) => { const response = await fetch("/api/v2/auth/login1", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - email, - clientPublicKey, - }), + body: JSON.stringify(loginDetails), }); // need precise error handling about the status code if (response?.status === 200) { diff --git a/frontend/src/pages/api/auth/Login2.ts b/frontend/src/pages/api/auth/Login2.ts index e3d625950..724a8b1d4 100644 --- a/frontend/src/pages/api/auth/Login2.ts +++ b/frontend/src/pages/api/auth/Login2.ts @@ -17,16 +17,17 @@ interface Login2Response { * @param {*} clientPublicKey * @returns */ -const login2 = async (email: string, clientProof: string) => { +const login2 = async (loginDetails: { + email: string; + clientProof: string; + userId?: string; +}) => { const response = await fetch('/api/v2/auth/login2', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - email, - clientProof - }), + body: JSON.stringify(loginDetails), credentials: 'include' }); // need precise error handling about the status code diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 64f1caca5..1794b834b 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -8,7 +8,9 @@ import { useTranslation } from 'next-i18next'; import ListBox from '@app/components/basic/Listbox'; import LoginStep from '@app/components/login/LoginStep'; import MFAStep from '@app/components/login/MFAStep'; +import PasswordInputStep from '@app/components/login/PasswordInputStep'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; +import { useProviderAuth } from '@app/hooks/useProviderAuth'; import { isLoggedIn } from '@app/reactQuery'; import getWorkspaces from './api/workspace/getWorkspaces'; @@ -20,8 +22,14 @@ export default function Login() { const [step, setStep] = useState(1); const { t } = useTranslation(); const lang = router.locale ?? 'en'; + const [isLoginWithEmail, setIsLoginWithEmail] = useState(false); + const { + providerAuthToken, + userId, + email: providerEmail, + setProviderAuthToken + } = useProviderAuth(); - const setLanguage = async (to: string) => { router.push('/login', '/login', { locale: to }); localStorage.setItem('lang', to); @@ -44,30 +52,58 @@ export default function Login() { } }, []); - const renderStep = (loginStep: number) => { - // TODO: add MFA step - switch (loginStep) { - case 1: - return ( - - ); - case 2: - // TODO: add MFA step - return ( - - ); - default: - return
+ const renderView = (loginStep: number) => { + + if (providerAuthToken && step === 1) { + return ( + + ) } + + if (isLoginWithEmail && loginStep === 1) { + return ( + + ) + } + + if (!isLoginWithEmail && loginStep === 1) { + return ( + <> + + + + ) + } + + if (step === 2) { + + } + + return
} return ( @@ -84,7 +120,7 @@ export default function Login() { long logo
- {renderStep(step)} + {renderView(step)}