mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 22:51:53 +00:00
Merge pull request #535 from sheensantoscapadngan/feature/google-signin-signup-integration
Feature/google signin signup integration
This commit is contained in:
@@ -61,22 +61,6 @@ const InputField = ({
|
||||
<div className="flex-col w-full">
|
||||
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
||||
<p className="text-sm font-semibold mr-1">{label}</p>
|
||||
{/* {label === "Password" && router.asPath !== "/login" && (
|
||||
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleExclamation}
|
||||
className={`text-sm peer ${
|
||||
error && "text-red"
|
||||
}`}
|
||||
/>
|
||||
<span className="absolute hidden peer-hover:block duration-200 w-60 -left-28 -top-2 -translate-y-full px-2 py-2 bg-gray-700 rounded-md text-center text-gray-200 text-sm after:content-[''] after:absolute after:left-1/2 after:top-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-b-transparent after:border-t-gray-700">
|
||||
The password should contain at least 8
|
||||
characters including at least 1 lowercase
|
||||
character, uppercase character, number, and a
|
||||
special character.
|
||||
</span>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
||||
@@ -95,7 +79,7 @@ const InputField = ({
|
||||
: ''
|
||||
} ${
|
||||
error ? 'focus:ring-red/50' : 'focus:ring-primary/50'
|
||||
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
|
||||
} relative peer bg-mineshaft-900 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
|
||||
name={name}
|
||||
spellCheck="false"
|
||||
autoComplete={autoComplete}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
// import { faGoogle } from '@fortawesome/free-brands-svg-icons';
|
||||
// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Button } from '../v2';
|
||||
|
||||
export default function InitialLoginStep({
|
||||
setIsLoginWithEmail,
|
||||
}: {
|
||||
setIsLoginWithEmail: (value: boolean) => void;
|
||||
}) {
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <div className='flex flex-col mx-auto w-full justify-center items-center'>
|
||||
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
|
||||
{/* <div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
onClick={() => {
|
||||
window.open('/api/v1/auth/redirect/google')
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-1" />}
|
||||
className="h-14 w-full mx-0"
|
||||
>
|
||||
{t('login.continue-with-google')}
|
||||
</Button>
|
||||
</div> */}
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
onClick={() => {
|
||||
setIsLoginWithEmail(true);
|
||||
}}
|
||||
isFullWidth
|
||||
className="h-14 w-full mx-0"
|
||||
>
|
||||
{t('login.continue-with-email')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={() => router.push("/saml-sso")}
|
||||
isFullWidth
|
||||
className="h-14 w-full mx-0"
|
||||
>
|
||||
Continue with SAML SSO
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-bunker-400 text-sm flex flex-row">
|
||||
<span className="mr-1">Don't have an acount yet?</span>
|
||||
<Link href="/signup">
|
||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>{t('login.create-account')}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import { useRouter } from 'next/router';
|
||||
import { faWarning } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
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 { Button, Input } from '../v2';
|
||||
|
||||
/**
|
||||
* 1st step of login - user enters their username and password
|
||||
* @param {Object} obj
|
||||
@@ -46,7 +46,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
|
||||
|
||||
@@ -60,82 +63,71 @@ export default function LoginStep({
|
||||
// case: login does not require MFA step
|
||||
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
setLoginError(true);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker py-4 px-6 pt-8 drop-shadow-xl">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-3xl font-semibold text-bunker-100">
|
||||
<div className="w-full mx-auto h-full px-6">
|
||||
<p className="text-xl w-max mx-auto flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 mb-6">
|
||||
{t('login.login')}
|
||||
</p>
|
||||
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
||||
<InputField
|
||||
label={t('common.email')}
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
<div className="flex items-center justify-center lg:w-1/6 w-1/4 min-w-[22rem] mx-auto w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={email}
|
||||
placeholder=""
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
type="email"
|
||||
placeholder="Enter your email..."
|
||||
isRequired
|
||||
autoComplete="username"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative mt-6 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-2 md:max-h-28 md:p-2">
|
||||
<InputField
|
||||
label={t('common.password')}
|
||||
onChangeHandler={setPassword}
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder=""
|
||||
isRequired
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
/>
|
||||
<div className="absolute top-2 right-3 cursor-pointer text-sm text-primary-700 duration-200 hover:text-primary">
|
||||
<Link href="/verify-email">
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1.5 text-sm font-normal text-primary-700 underline-offset-4 duration-200 hover:text-primary"
|
||||
>
|
||||
{t('login.forgot-password')}
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{!isLoading && loginError && <Error text={t('login.error-login') ?? ''} />}
|
||||
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||
<Button
|
||||
type="submit"
|
||||
text={t('login.login') ?? ''}
|
||||
onButtonPressed={async () => handleLogin()}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
<div className="relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type="password"
|
||||
placeholder="Enter your password..."
|
||||
isRequired
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isLoading && loginError && <Error text={t('login.error-login') ?? ''} />}
|
||||
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[22rem] px-2 mt-4 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<Button
|
||||
onClick={async () => handleLogin()}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-14'
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isLoading={isLoading}
|
||||
> {String(t('login.login'))} </Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-bunker-400 text-sm flex flex-row w-max mx-auto">
|
||||
<Link href="/verify-email">
|
||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>{t('login.forgot-password')}</span>
|
||||
</Link>
|
||||
</div>
|
||||
{false && (
|
||||
<div className="mx-auto mt-4 flex w-full max-w-md flex-row items-center rounded-md bg-white/10 p-2 text-gray-300">
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
|
||||
{t('common.maintenance-alert')}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 flex flex-row items-center justify-center md:pb-4">
|
||||
<p className="flex w-max justify-center text-sm text-gray-400">{t('login.need-account')}</p>
|
||||
<Link href="/signup">
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1.5 text-sm font-normal text-primary-700 underline-offset-4 duration-200 hover:text-primary"
|
||||
>
|
||||
{t('login.create-account')}
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { useRouter } from 'next/router';
|
||||
import attemptLoginMfa from '@app/components/utilities/attemptLoginMfa';
|
||||
import { useSendMfaToken } from '@app/hooks/api/auth';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import Error from '../basic/Error';
|
||||
import { Button } from '../v2';
|
||||
|
||||
// The style for the verification code input
|
||||
const props = {
|
||||
@@ -52,10 +52,12 @@ interface VerifyMfaTokenError {
|
||||
*/
|
||||
export default function MFAStep({
|
||||
email,
|
||||
password
|
||||
password,
|
||||
providerAuthToken,
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -77,6 +79,7 @@ export default function MFAStep({
|
||||
const isLoginSuccessful = await attemptLoginMfa({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken: mfaCode
|
||||
});
|
||||
|
||||
@@ -112,10 +115,10 @@ export default function MFAStep({
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="h-7/12 mx-auto mb-64 w-max rounded-xl bg-bunker px-8 pt-10 pb-4 drop-shadow-xl md:mb-16">
|
||||
<form className="mx-auto w-max px-8 pb-4 pt-4 md:mb-16">
|
||||
<p className="text-l flex justify-center text-bunker-300">{t('mfa.step2-message')}</p>
|
||||
<p className="text-l my-2 flex justify-center font-semibold text-bunker-300">{email} </p>
|
||||
<div className="hidden md:block">
|
||||
<p className="text-l my-1 flex justify-center font-semibold text-bunker-300">{email} </p>
|
||||
<div className="hidden md:block w-max min-w-[20rem] mx-auto">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
@@ -126,28 +129,46 @@ export default function MFAStep({
|
||||
className="mt-6 mb-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="block md:hidden w-max mt-4 mx-auto">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
type="text"
|
||||
fields={6}
|
||||
onChange={setMfaCode}
|
||||
{...props}
|
||||
className="mt-2 mb-2"
|
||||
/>
|
||||
</div>
|
||||
{typeof triesLeft === 'number' && (
|
||||
<Error text={`${t('mfa.step2-code-error')} ${triesLeft}`} />
|
||||
)}
|
||||
<div className="min-w-28 mx-auto mt-4 mb-2 flex max-h-24 max-w-max flex-col items-center justify-center px-4 text-lg md:p-2">
|
||||
<Button text={t('mfa.verify') ?? ''} onButtonPressed={() => handleLoginMfa()} size="lg" />
|
||||
</div>
|
||||
<div className="mx-auto flex max-h-24 w-full max-w-md flex-col items-center justify-center pt-2">
|
||||
<div className="flex flex-row items-baseline gap-1 text-sm">
|
||||
<span className="text-bunker-400">{t('mfa.step2-resend-alert')}</span>
|
||||
<u
|
||||
className={`font-normal ${
|
||||
isLoadingResend
|
||||
? 'text-bunker-400'
|
||||
: 'text-primary-700 duration-200 hover:text-primary'
|
||||
}`}
|
||||
>
|
||||
<button disabled={isLoading} onClick={() => handleResendMfaCode()} type="button">
|
||||
{isLoadingResend ? t('mfa.step2-resend-progress') : t('mfa.step2-resend-submit')}
|
||||
</button>
|
||||
</u>
|
||||
<div className="flex flex-col mt-6 items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<Button
|
||||
onClick={() => handleLoginMfa()}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-14'
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
> {String(t('mfa.verify'))} </Button>
|
||||
</div>
|
||||
<p className="pb-2 text-sm text-bunker-400">{t('mfa.step2-spam-alert')}</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
|
||||
<div className="flex flex-row items-baseline gap-1 text-sm">
|
||||
<span className="text-bunker-400">{t('signup.step2-resend-alert')}</span>
|
||||
<div className="mt-2 text-bunker-400 text-md flex flex-row">
|
||||
<button disabled={isLoading} onClick={handleResendMfaCode} type="button">
|
||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>
|
||||
{isLoadingResend
|
||||
? t('signup.step2-resend-progress')
|
||||
: t('signup.step2-resend-submit')}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-bunker-400 pb-2">{t('signup.step2-spam-alert')}</p>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import Error from '@app/components/basic/Error';
|
||||
import attemptLogin from '@app/components/utilities/attemptLogin';
|
||||
|
||||
import SecurityClient from '../utilities/SecurityClient';
|
||||
import { Button, Input } from '../v2';
|
||||
|
||||
export default function PasswordInputStep({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
setPassword,
|
||||
setProviderAuthToken,
|
||||
setStep
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken: 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 {
|
||||
setIsLoading(true);
|
||||
const loginAttempt = await attemptLogin({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
});
|
||||
|
||||
if (loginAttempt && loginAttempt.success) {
|
||||
// case: login was successful
|
||||
|
||||
if (loginAttempt.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 (
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="h-full mx-auto w-full max-w-md px-6 pt-8">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8">
|
||||
What’s your Infisical Password?
|
||||
</p>
|
||||
<div className="relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type="password"
|
||||
placeholder="Enter your password..."
|
||||
isRequired
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isLoading && loginError && <Error text={t('login.error-login') ?? ''} />}
|
||||
<div className='lg:w-1/6 w-1/4 w-full mx-auto flex items-center justify-center min-w-[22rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={async () => handleLogin()}
|
||||
isFullWidth
|
||||
isLoading={isLoading}
|
||||
className="h-14"
|
||||
>
|
||||
{t('login.login')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-bunker-400 text-xs flex flex-col items-center w-max mx-auto mt-4">
|
||||
<span className='duration-200 max-w-sm text-center px-4'>
|
||||
Infisical Master Password serves as a decryption mechanism so that even Google is not able to access your secrets.
|
||||
</span>
|
||||
<Link href="/verify-email">
|
||||
<span className='hover:underline mt-2 hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>{t('login.forgot-password')}</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
SecurityClient.setProviderAuthToken('');
|
||||
setProviderAuthToken('');
|
||||
}}
|
||||
type="button"
|
||||
className="text-bunker-400 text-xs hover:underline mt-2 hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer"
|
||||
>
|
||||
{t('login.other-option')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import sendVerificationEmail from '@app/pages/api/auth/SendVerificationEmail';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import Error from '../basic/Error';
|
||||
import { Button } from '../v2';
|
||||
|
||||
// The style for the verification code input
|
||||
const props = {
|
||||
@@ -51,6 +51,7 @@ interface CodeInputStepProps {
|
||||
incrementStep: () => void;
|
||||
setCode: (value: string) => void;
|
||||
codeError: boolean;
|
||||
isCodeInputCheckLoading: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +67,8 @@ export default function CodeInputStep({
|
||||
email,
|
||||
incrementStep,
|
||||
setCode,
|
||||
codeError
|
||||
codeError,
|
||||
isCodeInputCheckLoading
|
||||
}: CodeInputStepProps): JSX.Element {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false);
|
||||
@@ -83,10 +85,10 @@ export default function CodeInputStep({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-7/12 mx-auto mb-64 w-max rounded-xl bg-bunker px-8 pt-10 pb-4 drop-shadow-xl md:mb-16">
|
||||
<p className="text-l flex justify-center text-bunker-300">{t('signup.step2-message')}</p>
|
||||
<p className="text-l my-2 flex justify-center font-semibold text-bunker-300">{email} </p>
|
||||
<div className="hidden md:block">
|
||||
<div className="mx-auto h-full w-full pb-4 px-8">
|
||||
<p className="text-md flex justify-center text-bunker-200">{t('signup.step2-message')}</p>
|
||||
<p className="text-md flex justify-center font-semibold my-1 text-bunker-200">{email} </p>
|
||||
<div className="hidden md:block w-max min-w-[20rem] mx-auto">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
@@ -97,7 +99,7 @@ export default function CodeInputStep({
|
||||
className="mt-6 mb-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="block md:hidden">
|
||||
<div className="block md:hidden w-max mt-4 mx-auto">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
@@ -105,31 +107,37 @@ export default function CodeInputStep({
|
||||
fields={6}
|
||||
onChange={setCode}
|
||||
{...propsPhone}
|
||||
className="mt-2 mb-6"
|
||||
className="mt-2 mb-2"
|
||||
/>
|
||||
</div>
|
||||
{codeError && <Error text={t('signup.step2-code-error')} />}
|
||||
<div className="min-w-28 mx-auto mt-4 mb-2 flex max-h-24 max-w-max flex-col items-center justify-center px-4 text-lg md:p-2">
|
||||
<Button text={t('signup.verify') ?? ''} onButtonPressed={incrementStep} size="lg" />
|
||||
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<Button
|
||||
onClick={incrementStep}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-14'
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isLoading={isCodeInputCheckLoading}
|
||||
> {String(t('signup.verify'))} </Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto flex max-h-24 w-full max-w-md flex-col items-center justify-center pt-2">
|
||||
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
|
||||
<div className="flex flex-row items-baseline gap-1 text-sm">
|
||||
<span className="text-bunker-400">{t('signup.step2-resend-alert')}</span>
|
||||
<u
|
||||
className={`font-normal ${
|
||||
isResendingVerificationEmail
|
||||
? 'text-bunker-400'
|
||||
: 'text-primary-700 duration-200 hover:text-primary'
|
||||
}`}
|
||||
>
|
||||
<div className="mt-2 text-bunker-400 text-md flex flex-row">
|
||||
<button disabled={isLoading} onClick={resendVerificationEmail} type="button">
|
||||
{isResendingVerificationEmail
|
||||
? t('signup.step2-resend-progress')
|
||||
: t('signup.step2-resend-submit')}
|
||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>
|
||||
{isResendingVerificationEmail
|
||||
? t('signup.step2-resend-progress')
|
||||
: t('signup.step2-resend-submit')}
|
||||
</span>
|
||||
</button>
|
||||
</u>
|
||||
</div>
|
||||
</div>
|
||||
<p className="pb-2 text-sm text-bunker-400">{t('signup.step2-spam-alert')}</p>
|
||||
<p className="text-sm text-bunker-400 pb-2">{t('signup.step2-spam-alert')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { faWarning } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import issueBackupKey from '../utilities/cryptography/issueBackupKey';
|
||||
import { Button } from '../v2';
|
||||
|
||||
interface DownloadBackupPDFStepProps {
|
||||
incrementStep: () => void;
|
||||
@@ -30,33 +30,35 @@ export default function DonwloadBackupPDFStep({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="h-7/12 mx-1 mb-36 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker py-8 px-4 drop-shadow-xl md:mb-16 md:max-w-lg md:px-6">
|
||||
<p className="flex justify-center text-center text-4xl font-semibold text-primary">
|
||||
{t('signup.step4-message')}
|
||||
<div className="flex flex-col items-center w-full h-full md:px-6 mx-auto mb-36 md:mb-16">
|
||||
<p className="text-xl text-center font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 text-2xl text-bunker-200" />{t('signup.step4-message')}
|
||||
</p>
|
||||
<div className="text-md mt-4 flex w-full max-w-md flex-col items-center justify-center rounded-md px-2 text-gray-400 md:mt-8">
|
||||
<div>{t('signup.step4-description1')}</div>
|
||||
<div className="mt-3">{t('signup.step4-description2')}</div>
|
||||
</div>
|
||||
<div className="mx-auto mt-4 flex w-full max-w-xs flex-row items-center rounded-md bg-white/10 p-2 text-gray-400 md:max-w-md">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
|
||||
{t('signup.step4-description3')}
|
||||
</div>
|
||||
<div className="mx-auto mt-4 flex max-h-24 max-w-max flex-col items-center justify-center px-2 py-3 text-lg md:px-4 md:py-5">
|
||||
<Button
|
||||
text="Download PDF"
|
||||
onButtonPressed={async () => {
|
||||
await issueBackupKey({
|
||||
email,
|
||||
password,
|
||||
personalName: name,
|
||||
setBackupKeyError: () => {},
|
||||
setBackupKeyIssued: () => {}
|
||||
});
|
||||
incrementStep();
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
<div className="flex flex-col pb-2 bg-mineshaft-900 border border-mineshaft-700 items-center justify-center text-center lg:w-1/6 w-full md:min-w-[24rem] mt-8 max-w-md text-bunker-300 text-md rounded-md">
|
||||
<div className="w-full mt-4 md:mt-8 flex flex-row text-center items-center m-2 text-bunker-300 rounded-md lg:w-1/6 lg:w-1/6 w-full md:min-w-[23rem] px-3 mx-auto">
|
||||
<span className='mb-2'>{t('signup.step4-description1')} {t('signup.step4-description3')}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center px-3 justify-center mt-0 md:mt-4 mb-2 md:mb-4 lg:w-1/6 w-full md:min-w-[20rem] mt-2 md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await issueBackupKey({
|
||||
email,
|
||||
password,
|
||||
personalName: name,
|
||||
setBackupKeyError: () => { },
|
||||
setBackupKeyIssued: () => { }
|
||||
});
|
||||
incrementStep();
|
||||
}}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-12'
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
> Download PDF </Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,8 +4,7 @@ import Link from 'next/link';
|
||||
|
||||
import sendVerificationEmail from '@app/pages/api/auth/SendVerificationEmail';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import InputField from '../basic/InputField';
|
||||
import { Button, Input } from '../v2';
|
||||
|
||||
interface DownloadBackupPDFStepProps {
|
||||
incrementStep: () => void;
|
||||
@@ -27,7 +26,6 @@ export default function EnterEmailStep({
|
||||
incrementStep
|
||||
}: DownloadBackupPDFStepProps): JSX.Element {
|
||||
const [emailError, setEmailError] = useState(false);
|
||||
const [emailErrorMessage, setEmailErrorMessage] = useState('');
|
||||
const { t } = useTranslation();
|
||||
|
||||
/**
|
||||
@@ -37,11 +35,9 @@ export default function EnterEmailStep({
|
||||
let emailCheckBool = false;
|
||||
if (!email) {
|
||||
setEmailError(true);
|
||||
setEmailErrorMessage('Please enter your email.');
|
||||
emailCheckBool = true;
|
||||
} else if (!email.includes('@') || !email.includes('.') || !/[a-z]/.test(email)) {
|
||||
setEmailError(true);
|
||||
setEmailErrorMessage('Please enter a valid email.');
|
||||
emailCheckBool = true;
|
||||
} else {
|
||||
setEmailError(false);
|
||||
@@ -56,41 +52,40 @@ export default function EnterEmailStep({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="h-7/12 mx-1 w-full max-w-md rounded-xl bg-bunker py-8 drop-shadow-xl md:px-6">
|
||||
<p className="flex justify-center text-4xl font-semibold text-primary">
|
||||
<div className="w-full md:px-6 mx-auto">
|
||||
<p className="text-xl font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
{t('signup.step1-start')}
|
||||
</p>
|
||||
<div className="m-auto mt-4 flex max-h-24 w-5/6 items-center justify-center rounded-lg md:w-full md:p-2">
|
||||
<InputField
|
||||
label={t('common.email') ?? ''}
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] m-auto rounded-lg mt-8">
|
||||
<Input
|
||||
placeholder="Enter your email address..."
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
error={emailError}
|
||||
errorText={emailErrorMessage}
|
||||
autoComplete="username"
|
||||
className="h-12"
|
||||
/>
|
||||
{emailError && <p className="text-red-600 text-xs text-left w-full ml-1.5 mt-1.5">Please enter a valid email.</p>}
|
||||
</div>
|
||||
<div className="mx-auto mt-4 flex max-h-28 w-5/6 max-w-xs flex-col items-center justify-center text-center text-sm md:mt-4 md:w-full md:max-w-md md:p-2 md:text-left">
|
||||
<p className="mt-2 text-gray-400 md:mx-0.5">{t('signup.step1-privacy')}</p>
|
||||
<div className="text-l m-2 mt-6 px-8 py-1 text-lg md:m-8">
|
||||
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<Button
|
||||
text={t('signup.step1-submit') ?? ''}
|
||||
type="submit"
|
||||
onButtonPressed={emailCheck}
|
||||
size="lg"
|
||||
/>
|
||||
onClick={emailCheck}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-14'
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
> {String(t('signup.step1-submit'))} </Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto mb-48 mt-2 flex w-full max-w-md flex-col items-center justify-center pt-2 md:mb-16 md:pb-2">
|
||||
<Link href="/login">
|
||||
<button type="button" className="w-max pb-3 duration-200 hover:opacity-90">
|
||||
<u className="text-sm font-normal text-primary-500">
|
||||
<span className="text-sm text-mineshaft-400 hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer">
|
||||
{t('signup.already-have-account')}
|
||||
</u>
|
||||
</span>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
// import { faGoogle } from '@fortawesome/free-brands-svg-icons';
|
||||
// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Button } from '../v2';
|
||||
|
||||
export default function InitialSignupStep({
|
||||
setIsSignupWithEmail,
|
||||
}: {
|
||||
setIsSignupWithEmail: (value: boolean) => void
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
|
||||
return <div className='flex flex-col mx-auto w-full justify-center items-center'>
|
||||
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >{t('signup.initial-title')}</h1>
|
||||
{/* <div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
onClick={() => {
|
||||
window.open('/api/v1/auth/redirect/google')
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-1" />}
|
||||
className="h-14 w-full mx-0"
|
||||
>
|
||||
{t('signup.continue-with-google')}
|
||||
</Button>
|
||||
</div> */}
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
onClick={() => {
|
||||
setIsSignupWithEmail(true);
|
||||
}}
|
||||
isFullWidth
|
||||
className="h-14 w-full mx-0"
|
||||
>
|
||||
{t('signup.continue-with-email')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={() => router.push("/saml-sso")}
|
||||
isFullWidth
|
||||
className="h-14 w-full mx-0"
|
||||
>
|
||||
Continue with SAML SSO
|
||||
</Button>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] px-8 text-center mt-6 text-xs text-bunker-400'>
|
||||
{t('signup.create-policy')}
|
||||
</div>
|
||||
<div className="mt-2 text-bunker-400 text-xs flex flex-row">
|
||||
<Link href="/login">
|
||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>{t('signup.already-have-account')}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -7,8 +7,7 @@ import { usePopUp } from '@app/hooks/usePopUp';
|
||||
import addUserToOrg from '@app/pages/api/organization/addUserToOrg';
|
||||
import getWorkspaces from '@app/pages/api/workspace/getWorkspaces';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import { EmailServiceSetupModal } from '../v2';
|
||||
import { Button, EmailServiceSetupModal } from '../v2';
|
||||
|
||||
/**
|
||||
* This is the last step of the signup flow. People can optionally invite their teammates here.
|
||||
@@ -37,50 +36,56 @@ export default function TeamInviteStep(): JSX.Element {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-7/12 mx-auto mb-64 w-max rounded-xl bg-bunker px-8 pt-6 pb-4 drop-shadow-xl md:mb-32">
|
||||
<p className="flex justify-center text-4xl font-semibold text-primary">
|
||||
<div className="w-max mx-auto min-w-lg h-full pb-4 px-8 mb-64 md:mb-32">
|
||||
<p className="text-2xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
{t('signup.step5-invite-team')}
|
||||
</p>
|
||||
<p className="mb-6 mt-4 flex max-w-xs justify-center text-center text-bunker-300 md:mx-8 md:max-w-sm">
|
||||
<p className="text-center flex justify-center text-bunker-400 md:mx-8 mb-6 mt-4">
|
||||
{t('signup.step5-subtitle')}
|
||||
</p>
|
||||
<div>
|
||||
<div className="overflow-auto bg-bunker-800">
|
||||
<div className="whitespace-pre-wrap break-words bg-transparent" />
|
||||
<div className="bg-mineshaft-800 border border-mineshaft-500 w-max mx-auto pt-6 pb-4 px-8 rounded-xl drop-shadow-xl mb-6">
|
||||
<div>
|
||||
<div className="text-bunker-300 font-medium pl-1 pb-1 text-sm">
|
||||
<span>Emails</span>
|
||||
</div>
|
||||
<textarea
|
||||
className="bg-mineshaft-900/70 min-w-[30rem] h-20 w-full placeholder:text-bunker-400 py-1 px-2 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
|
||||
value={emails}
|
||||
onChange={(e) => setEmails(e.target.value)}
|
||||
placeholder="[email protected], [email protected]..."
|
||||
/>
|
||||
</div>
|
||||
<textarea
|
||||
className="h-20 w-full rounded-md border border-mineshaft-500 bg-bunker-800 py-1 px-2 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
|
||||
value={emails}
|
||||
onChange={(e) => setEmails(e.target.value)}
|
||||
placeholder="[email protected], [email protected]..."
|
||||
<div className="flex flex-row items-end justify-end mt-0 md:mt-4 md:mb-2 w-full md:min-w-[30rem] mt-2 md:max-w-md mx-auto text-sm">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (serverDetails?.emailConfigured) {
|
||||
inviteUsers({ emails })
|
||||
} else {
|
||||
handlePopUpOpen('setUpEmail');
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
// isFullWidth
|
||||
className='h-10'
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
> {t('signup.step5-send-invites') ?? ''} </Button>
|
||||
</div>
|
||||
<EmailServiceSetupModal
|
||||
isOpen={popUp.setUpEmail?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-28 mx-auto mt-4 mb-2 flex max-h-24 max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="text-md mx-3 cursor-pointer rounded-md bg-mineshaft-700 py-3 px-5 text-bunker-300 duration-200 hover:bg-mineshaft-500 md:py-3.5 md:text-sm"
|
||||
onClick={redirectToHome}
|
||||
>
|
||||
{t('signup.step5-skip')}
|
||||
</div>
|
||||
<div className="flex flex-row max-w-max min-w-28 items-center justify-center md:p-2 min-w-[20rem] max-h-24 mx-auto text-lg px-4 mt-4 mb-2">
|
||||
<Button
|
||||
text={t('signup.step5-send-invites') ?? ''}
|
||||
onButtonPressed={() => {
|
||||
if (serverDetails?.emailConfigured) {
|
||||
inviteUsers({ emails });
|
||||
} else {
|
||||
handlePopUpOpen('setUpEmail');
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
onClick={redirectToHome}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-12'
|
||||
colorSchema="secondary"
|
||||
variant="outline"
|
||||
> {t('signup.step5-skip') ?? 'Skip'} </Button>
|
||||
</div>
|
||||
<EmailServiceSetupModal
|
||||
isOpen={popUp.setUpEmail?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCheck, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import jsrp from 'jsrp';
|
||||
import nacl from 'tweetnacl';
|
||||
@@ -12,13 +12,13 @@ import completeAccountInformationSignup from '@app/pages/api/auth/CompleteAccoun
|
||||
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||
import ProjectService from '@app/services/ProjectService';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import InputField from '../basic/InputField';
|
||||
import passwordCheck from '../utilities/checks/PasswordCheck';
|
||||
import Aes256Gcm from '../utilities/cryptography/aes-256-gcm';
|
||||
import { deriveArgonKey } from '../utilities/cryptography/crypto';
|
||||
import { saveTokenToLocalStorage } from '../utilities/saveTokenToLocalStorage';
|
||||
import SecurityClient from '../utilities/SecurityClient';
|
||||
import { Button, Input } from '../v2';
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
@@ -28,10 +28,13 @@ interface UserInfoStepProps {
|
||||
email: string;
|
||||
password: string;
|
||||
setPassword: (value: string) => void;
|
||||
firstName: string;
|
||||
setFirstName: (value: string) => void;
|
||||
lastName: string;
|
||||
setLastName: (value: string) => void;
|
||||
name: string;
|
||||
setName: (value: string) => void;
|
||||
organizationName: string;
|
||||
setOrganizationName: (value: string) => void;
|
||||
attributionSource: string;
|
||||
setAttributionSource: (value: string) => void;
|
||||
providerAuthToken?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,13 +55,16 @@ export default function UserInfoStep({
|
||||
email,
|
||||
password,
|
||||
setPassword,
|
||||
firstName,
|
||||
setFirstName,
|
||||
lastName,
|
||||
setLastName
|
||||
name,
|
||||
setName,
|
||||
organizationName,
|
||||
setOrganizationName,
|
||||
attributionSource,
|
||||
setAttributionSource,
|
||||
providerAuthToken,
|
||||
}: UserInfoStepProps): JSX.Element {
|
||||
const [firstNameError, setFirstNameError] = useState(false);
|
||||
const [lastNameError, setLastNameError] = useState(false);
|
||||
const [nameError, setNameError] = useState(false);
|
||||
const [organizationNameError, setOrganizationNameError] = useState(false);
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
@@ -71,17 +77,17 @@ export default function UserInfoStep({
|
||||
const signupErrorCheck = async () => {
|
||||
setIsLoading(true);
|
||||
let errorCheck = false;
|
||||
if (!firstName) {
|
||||
setFirstNameError(true);
|
||||
if (!name) {
|
||||
setNameError(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setFirstNameError(false);
|
||||
setNameError(false);
|
||||
}
|
||||
if (!lastName) {
|
||||
setLastNameError(true);
|
||||
if (!organizationName) {
|
||||
setOrganizationNameError(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setLastNameError(false);
|
||||
setOrganizationNameError(false);
|
||||
}
|
||||
errorCheck = passwordCheck({
|
||||
password,
|
||||
@@ -146,8 +152,8 @@ export default function UserInfoStep({
|
||||
|
||||
const response = await completeAccountInformationSignup({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
firstName: name.split(" ")[0],
|
||||
lastName: name.split(" ").slice(1).join(' '),
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
@@ -155,14 +161,17 @@ export default function UserInfoStep({
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
providerAuthToken,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
organizationName: `${firstName}'s organization`
|
||||
organizationName,
|
||||
attributionSource,
|
||||
});
|
||||
|
||||
// unset signup JWT token and set JWT token
|
||||
SecurityClient.setSignupToken('');
|
||||
SecurityClient.setToken(response.token);
|
||||
SecurityClient.setProviderAuthToken('');
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
@@ -196,109 +205,117 @@ export default function UserInfoStep({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-7/12 mx-auto mb-36 w-max rounded-xl bg-bunker py-10 px-8 drop-shadow-xl md:mb-16">
|
||||
<p className="mx-8 mb-6 flex justify-center text-4xl font-bold text-primary md:mx-16">
|
||||
<div className="h-full mx-auto mb-36 w-max rounded-xl px-8 md:mb-16">
|
||||
<p className="mx-8 mb-6 flex justify-center text-xl font-bold text-medium md:mx-16 text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
{t('signup.step3-message')}
|
||||
</p>
|
||||
<div className="relative z-0 flex max-h-24 w-full items-center justify-end rounded-lg md:p-2">
|
||||
<InputField
|
||||
label={t('common.first-name')}
|
||||
onChangeHandler={setFirstName}
|
||||
type="name"
|
||||
value={firstName}
|
||||
isRequired
|
||||
errorText={
|
||||
t('common.validate-required', {
|
||||
name: t('common.first-name')
|
||||
}) as string
|
||||
}
|
||||
error={firstNameError}
|
||||
autoComplete="given-name"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex max-h-24 w-full items-center justify-center rounded-lg md:p-2">
|
||||
<InputField
|
||||
label={t('common.last-name')}
|
||||
onChangeHandler={setLastName}
|
||||
type="name"
|
||||
value={lastName}
|
||||
isRequired
|
||||
errorText={
|
||||
t('common.validate-required', {
|
||||
name: t('common.last-name')
|
||||
}) as string
|
||||
}
|
||||
error={lastNameError}
|
||||
autoComplete="family-name"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex max-h-60 w-full flex-col items-center justify-center rounded-lg md:p-2">
|
||||
<InputField
|
||||
label={t('section.password.password')}
|
||||
onChangeHandler={(pass: string) => {
|
||||
setPassword(pass);
|
||||
passwordCheck({
|
||||
password: pass,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck: false
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
value={password}
|
||||
isRequired
|
||||
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
/>
|
||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
||||
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||
<div className="mb-1 text-sm text-gray-400">{t('section.password.validate-base')}</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorLength ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
||||
{t('section.password.validate-length')}
|
||||
<div className="h-full mx-auto mb-36 w-max rounded-xl py-6 px-8 md:mb-16 border border-mineshaft-600 bg-mineshaft-800">
|
||||
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg">
|
||||
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Your Name</p>
|
||||
<Input
|
||||
placeholder="Jane Doe"
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
value={name}
|
||||
isRequired
|
||||
autoComplete="given-name"
|
||||
className="h-12"
|
||||
/>
|
||||
{nameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your name</p>}
|
||||
</div>
|
||||
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg">
|
||||
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Organization Name</p>
|
||||
<Input
|
||||
placeholder="Infisical"
|
||||
onChange={(e) => setOrganizationName(e.target.value)}
|
||||
value={organizationName}
|
||||
isRequired
|
||||
className="h-12"
|
||||
/>
|
||||
{organizationNameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your organization name</p>}
|
||||
</div>
|
||||
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg">
|
||||
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Where did you hear about us? <span className="font-light">(optional)</span></p>
|
||||
<Input
|
||||
placeholder=""
|
||||
onChange={(e) => setAttributionSource(e.target.value)}
|
||||
value={attributionSource}
|
||||
isRequired
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex lg:w-1/6 w-1/4 min-w-[20rem] max-h-60 w-full flex-col items-center justify-center rounded-lg py-2">
|
||||
<InputField
|
||||
label={t('section.password.password')}
|
||||
onChangeHandler={(pass: string) => {
|
||||
setPassword(pass);
|
||||
passwordCheck({
|
||||
password: pass,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck: false
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
value={password}
|
||||
isRequired
|
||||
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
/>
|
||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
||||
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||
<div className="mb-1 text-sm text-gray-400">{t('section.password.validate-base')}</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorLength ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
||||
{t('section.password.validate-length')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'} text-sm`}
|
||||
>
|
||||
{t('section.password.validate-case')}
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'} text-sm`}
|
||||
>
|
||||
{t('section.password.validate-case')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
||||
{t('section.password.validate-number')}
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
||||
{t('section.password.validate-number')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<Button
|
||||
onClick={signupErrorCheck}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-14'
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isLoading={isLoading}
|
||||
> {String(t('signup.signup'))} </Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2" />
|
||||
)}
|
||||
</div>
|
||||
<div className="mx-auto flex max-h-48 max-w-max flex-col items-center justify-center px-2 py-3 text-lg md:p-2">
|
||||
<Button
|
||||
text={t('signup.signup') ?? ''}
|
||||
loading={isLoading}
|
||||
onButtonPressed={signupErrorCheck}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,8 +4,20 @@ import {
|
||||
setMfaTempToken,
|
||||
setSignupTempToken} from '@app/reactQuery';
|
||||
|
||||
|
||||
export const PROVIDER_AUTH_TOKEN_KEY = 'infisical__provider-auth-token';
|
||||
|
||||
// depreciated: go for apiRequest module in config/api
|
||||
export default class SecurityClient {
|
||||
|
||||
static setProviderAuthToken(tokenStr: string) {
|
||||
localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || '')
|
||||
}
|
||||
|
||||
static getProviderAuthToken() {
|
||||
return localStorage.getItem(PROVIDER_AUTH_TOKEN_KEY);
|
||||
}
|
||||
|
||||
static setSignupToken(tokenStr: string) {
|
||||
setSignupTempToken(tokenStr);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,17 @@ interface IsLoginSuccessful {
|
||||
* @param {string} password - password of user to log in
|
||||
*/
|
||||
const attemptLogin = async (
|
||||
email: string,
|
||||
password: string
|
||||
{
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string;
|
||||
}
|
||||
): Promise<IsLoginSuccessful> => {
|
||||
|
||||
const telemetry = new Telemetry().getInstance();
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init(
|
||||
@@ -39,7 +47,11 @@ const attemptLogin = async (
|
||||
async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { serverPublicKey, salt } = await login1(email, clientPublicKey);
|
||||
const { serverPublicKey, salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken,
|
||||
});
|
||||
|
||||
client.setSalt(salt);
|
||||
client.setServerPublicKey(serverPublicKey);
|
||||
@@ -57,8 +69,11 @@ const attemptLogin = async (
|
||||
iv,
|
||||
tag
|
||||
} = await login2(
|
||||
email,
|
||||
clientProof
|
||||
{
|
||||
email,
|
||||
clientProof,
|
||||
providerAuthToken,
|
||||
}
|
||||
);
|
||||
|
||||
if (mfaEnabled) {
|
||||
@@ -80,7 +95,9 @@ const attemptLogin = async (
|
||||
token
|
||||
) {
|
||||
// case: MFA is not enabled
|
||||
|
||||
|
||||
// unset provider auth token in case it was used
|
||||
SecurityClient.setProviderAuthToken('');
|
||||
// set JWT token
|
||||
SecurityClient.setToken(token);
|
||||
|
||||
@@ -137,4 +154,4 @@ const attemptLogin = async (
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptLogin;
|
||||
export default attemptLogin;
|
||||
|
||||
@@ -23,10 +23,12 @@ const client = new jsrp.client();
|
||||
const attemptLoginMfa = async ({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string,
|
||||
mfaToken: string;
|
||||
}): Promise<Boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -36,7 +38,11 @@ const attemptLoginMfa = async ({
|
||||
}, async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1(email, clientPublicKey);
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken,
|
||||
});
|
||||
|
||||
const {
|
||||
encryptionVersion,
|
||||
@@ -56,6 +62,7 @@ const attemptLoginMfa = async ({
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken('');
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken('');
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
|
||||
@@ -63,6 +63,11 @@ const buttonVariants = cva(
|
||||
variant: 'star',
|
||||
className: 'bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary hover:text-black hover:border-primary-400 duration-100'
|
||||
},
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'solid',
|
||||
className: 'bg-primary-500 bg-opacity-90 hover:bg-primary-500 hover:text-black'
|
||||
},
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'selected',
|
||||
@@ -71,7 +76,7 @@ const buttonVariants = cva(
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'outline_bg',
|
||||
className: 'bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60 text-bunker-200 duration-100'
|
||||
className: 'bg-mineshaft-600 border border-mineshaft-500 hover:bg-primary/[0.1] hover:border-primary/40 text-bunker-200'
|
||||
},
|
||||
{
|
||||
colorSchema: 'secondary',
|
||||
@@ -91,7 +96,7 @@ const buttonVariants = cva(
|
||||
{
|
||||
colorSchema: 'secondary',
|
||||
variant: 'outline',
|
||||
className: 'hover:bg-mineshaft'
|
||||
className: 'border-mineshaft-700 hover:border-mineshaft-500'
|
||||
},
|
||||
{
|
||||
colorSchema: 'danger',
|
||||
@@ -172,10 +177,10 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
>
|
||||
{isLoading && (
|
||||
<img
|
||||
src="/images/loading/loadingblack.gif"
|
||||
src="/images/loading/loading.gif"
|
||||
width={36}
|
||||
alt="loading animation"
|
||||
className="absolute rounded-xl"
|
||||
className="absolute rounded-xl opacity-80"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
|
||||
@@ -13,7 +13,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const inputVariants = cva(
|
||||
'input w-full py-[0.375rem] text-gray-400 placeholder:text-sm placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2',
|
||||
'input w-full py-[0.375rem] text-gray-400 placeholder:text-sm placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2 hover:ring-[0.05rem] hover:ring-bunker-400/60 duration-100',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
@@ -27,13 +27,13 @@ const inputVariants = cva(
|
||||
false: ''
|
||||
},
|
||||
variant: {
|
||||
filled: ['bg-bunker-800', 'text-gray-400'],
|
||||
filled: ['bg-mineshaft-900', 'text-gray-400'],
|
||||
outline: ['bg-transparent'],
|
||||
plain: 'bg-transparent outline-none'
|
||||
},
|
||||
isError: {
|
||||
true: 'focus:ring-red/50 placeholder-red-300',
|
||||
false: 'focus:ring-mineshaft-400/80 duration-200 focus:ring-1'
|
||||
false: 'focus:ring-primary-400/50 focus:ring-1'
|
||||
}
|
||||
},
|
||||
compoundVariants: []
|
||||
@@ -48,7 +48,7 @@ const inputParentContainerVariants = cva('inline-flex font-inter items-center bo
|
||||
},
|
||||
isError: {
|
||||
true: 'border-red',
|
||||
false: 'border-mineshaft-600'
|
||||
false: 'border-mineshaft-500'
|
||||
},
|
||||
isFullWidth: {
|
||||
true: 'w-full',
|
||||
|
||||
Reference in New Issue
Block a user