Merge pull request #535 from sheensantoscapadngan/feature/google-signin-signup-integration

Feature/google signin signup integration
This commit is contained in:
vmatsiiako
2023-06-02 23:44:45 -07:00
committed by GitHub
67 changed files with 7542 additions and 7797 deletions
+1 -17
View File
@@ -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&apos;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>
}
+47 -55
View File
@@ -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>
);
}
+44 -23
View File
@@ -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>
);
}
+134 -117
View File
@@ -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,
+9 -4
View File
@@ -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
+4 -4
View File
@@ -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',
+5
View File
@@ -1,5 +1,6 @@
import axios from 'axios';
import SecurityClient from '@app/components/utilities/SecurityClient';
import {
getAuthToken,
getMfaTempToken,
@@ -16,6 +17,7 @@ apiRequest.interceptors.request.use((config) => {
const signupTempToken = getSignupTempToken();
const mfaTempToken = getMfaTempToken();
const token = getAuthToken();
const providerAuthToken = SecurityClient.getProviderAuthToken();
if (signupTempToken && config.headers) {
// eslint-disable-next-line no-param-reassign
@@ -26,6 +28,9 @@ apiRequest.interceptors.request.use((config) => {
} else if (token && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${token}`;
} else if(providerAuthToken && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${providerAuthToken}`;
}
return config;
});
+4 -1
View File
@@ -16,7 +16,10 @@ export const publicPaths = [
`/terms`,
`/subprocessors`,
`/verify-email`,
`/password-reset`
`/password-reset`,
`/saml-sso`,
`/login/provider/success`,
`/login/provider/error`
];
export const languageMap = {
+99
View File
@@ -0,0 +1,99 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import jwt_decode from 'jwt-decode';
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
import SecurityClient, { PROVIDER_AUTH_TOKEN_KEY } from '@app/components/utilities/SecurityClient';
export const useProviderAuth = () => {
const router = useRouter();
const { providerAuthToken: redirectedProviderAuthToken } = router.query;
const [email, setEmail] = useState<string>('');
const [userId, setUserId] = useState<string>('');
const [providerAuthToken, setProviderAuthToken] = useState<string>(
redirectedProviderAuthToken as string || ''
);
const [isProviderUserCompleted, setIsProviderUserCompleted] = useState<boolean>();
const { createNotification } = useNotificationContext();
const AUTH_ERROR_KEY = 'PROVIDER_AUTH_ERROR'
const handleRedirectWithToken = () => {
if (providerAuthToken) {
const {
userId: resultUserId,
email: resultEmail,
isUserCompleted: resultIsUserCompleted,
} = jwt_decode(providerAuthToken) as any;
setEmail(resultEmail);
setUserId(resultUserId);
setIsProviderUserCompleted(resultIsUserCompleted);
}
}
useEffect(() => {
handleRedirectWithToken();
// reset when there is no redirect auth token
if (!providerAuthToken) {
SecurityClient.setProviderAuthToken('');
}
window.localStorage.removeItem(AUTH_ERROR_KEY);
const handleStorageChange = (event: StorageEvent) => {
if (event.storageArea !== localStorage) {
return;
}
if (event.key === PROVIDER_AUTH_TOKEN_KEY) {
if (event.newValue) {
const token = event.newValue;
const {
userId: resultUserId,
email: resultEmail,
isUserCompleted: resultIsUserCompleted,
} = jwt_decode(token) as any;
setIsProviderUserCompleted(resultIsUserCompleted);
setProviderAuthToken(token);
setEmail(resultEmail);
setUserId(resultUserId);
} else {
setProviderAuthToken('');
setEmail('');
setUserId('');
setIsProviderUserCompleted(false);
}
setProviderAuthToken(event.newValue || '');
}
if (event.key === AUTH_ERROR_KEY) {
if (event.newValue) {
createNotification({
text: 'An error has occured during login.',
type: 'error',
timeoutMs: 6000,
})
window.localStorage.removeItem(AUTH_ERROR_KEY);
}
}
};
window.addEventListener('storage', handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, []);
return {
email,
isProviderUserCompleted,
providerAuthToken,
userId,
setEmail,
setProviderAuthToken,
setUserId,
};
};
+5 -3
View File
@@ -57,9 +57,11 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element =>
) {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
<NotificationProvider>
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
</NotificationProvider>
</QueryClientProvider>
);
}
@@ -8,6 +8,7 @@ interface Props {
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
providerAuthToken?: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
@@ -15,6 +16,7 @@ interface Props {
organizationName: string;
salt: string;
verifier: string;
attributionSource?: string;
}
/**
@@ -49,9 +51,11 @@ const completeAccountInformationSignup = async ({
encryptedPrivateKeyTag,
salt,
verifier,
organizationName
organizationName,
providerAuthToken,
attributionSource
}: Props) => {
const { data } = await apiRequest.post('/api/v2/signup/complete-account/signup', {
const { data } = await apiRequest.post('/api/v3/signup/complete-account/signup', {
email,
firstName,
lastName,
@@ -64,7 +68,9 @@ const completeAccountInformationSignup = async ({
encryptedPrivateKeyTag,
salt,
verifier,
organizationName
organizationName,
providerAuthToken,
attributionSource,
});
return data;
+7 -6
View File
@@ -9,16 +9,17 @@ interface Login1 {
* @param {*} clientPublicKey
* @returns
*/
const login1 = async (email: string, clientPublicKey: string) => {
const response = await fetch("/api/v2/auth/login1", {
const login1 = async (loginDetails: {
email: string;
clientPublicKey: string;
providerAuthToken?: string;
}) => {
const response = await fetch("/api/v3/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) {
+7 -6
View File
@@ -17,16 +17,17 @@ interface Login2Response {
* @param {*} clientPublicKey
* @returns
*/
const login2 = async (email: string, clientProof: string) => {
const response = await fetch('/api/v2/auth/login2', {
const login2 = async (loginDetails: {
email: string;
clientProof: string;
providerAuthToken?: string;
}) => {
const response = await fetch('/api/v3/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
+66 -30
View File
@@ -5,9 +5,12 @@ import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import ListBox from '@app/components/basic/Listbox';
// import ListBox from '@app/components/basic/Listbox';
import InitialLoginStep from '@app/components/login/InitialLoginStep';
import LoginStep from '@app/components/login/LoginStep';
import MFAStep from '@app/components/login/MFAStep';
import PasswordInputStep from '@app/components/login/PasswordInputStep';
import { useProviderAuth } from '@app/hooks/useProviderAuth';
import { isLoggedIn } from '@app/reactQuery';
import getWorkspaces from './api/workspace/getWorkspaces';
@@ -18,12 +21,23 @@ export default function Login() {
const [password, setPassword] = useState('');
const [step, setStep] = useState(1);
const { t } = useTranslation();
const lang = router.locale ?? 'en';
// const lang = router.locale ?? 'en';
const [isLoginWithEmail, setIsLoginWithEmail] = useState(false);
const {
providerAuthToken,
email: providerEmail,
setProviderAuthToken,
isProviderUserCompleted
} = useProviderAuth();
const setLanguage = async (to: string) => {
router.push('/login', '/login', { locale: to });
localStorage.setItem('lang', to);
};
if (providerAuthToken && isProviderUserCompleted === false) {
router.push(`/signup?providerAuthToken=${encodeURIComponent(providerAuthToken)}`);
}
// const setLanguage = async (to: string) => {
// router.push('/login', '/login', { locale: to });
// localStorage.setItem('lang', to);
// };
useEffect(() => {
// TODO(akhilmhdh): workspace will be controlled by a workspace context
@@ -42,29 +56,51 @@ export default function Login() {
}
}, []);
const renderStep = (loginStep: number) => {
// TODO: add MFA step
switch (loginStep) {
case 1:
return (
<LoginStep
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
setStep={setStep}
/>
);
case 2:
// TODO: add MFA step
return <MFAStep email={email} password={password} />;
default:
return <div />;
const renderView = (loginStep: number) => {
if (providerAuthToken && step === 1) {
return (
<PasswordInputStep
email={providerEmail}
password={password}
providerAuthToken={providerAuthToken}
setPassword={setPassword}
setProviderAuthToken={setProviderAuthToken}
setStep={setStep}
/>
);
}
if (isLoginWithEmail && loginStep === 1) {
return (
<LoginStep
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
setStep={setStep}
/>
);
}
if (!isLoginWithEmail && loginStep === 1) {
return <InitialLoginStep setIsLoginWithEmail={setIsLoginWithEmail} />;
}
if (step === 2) {
return (
<MFAStep
email={email || providerEmail}
password={password}
providerAuthToken={providerAuthToken}
/>
);
}
return <div />;
};
return (
<div className="flex h-screen flex-col justify-start bg-bunker-800 px-6">
<div className="flex h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
<Head>
<title>{t('common.head-title', { title: t('login.title') })}</title>
<link rel="icon" href="/infisical.ico" />
@@ -73,12 +109,12 @@ export default function Login() {
<meta name="og:description" content={t('login.og-description') ?? ''} />
</Head>
<Link href="/">
<div className="mb-8 mt-20 flex cursor-pointer justify-center">
<Image src="/images/biglogo.png" height={90} width={120} alt="long logo" />
<div className="mb-4 mt-20 flex justify-center">
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
</div>
</Link>
{renderStep(step)}
<div className="absolute right-4 top-0 mt-4 flex items-center justify-center">
{renderView(step)}
{/* <div className="absolute right-4 top-0 mt-4 flex items-center justify-center">
<div className="mx-auto w-48">
<ListBox
isSelected={lang}
@@ -88,7 +124,7 @@ export default function Login() {
text={`${t('common.language')}: `}
/>
</div>
</div>
</div> */}
</div>
);
}
@@ -0,0 +1,10 @@
import { useEffect } from "react";
export default function LoginProviderError() {
useEffect(() => {
window.localStorage.setItem('PROVIDER_AUTH_ERROR', 'err');
window.close();
}, [])
return <div />
}
@@ -0,0 +1,16 @@
import { useEffect } from "react";
import { useRouter } from "next/router"
import SecurityClient from '@app/components/utilities/SecurityClient';
export default function LoginProviderSuccess() {
const router = useRouter();
useEffect(() => {
const { token } = router.query;
SecurityClient.setProviderAuthToken(token as string);
window.close();
}, [])
return <div />
}
+90
View File
@@ -0,0 +1,90 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { Button, Input } from '@app/components/v2';
import { isLoggedIn } from '@app/reactQuery';
import getWorkspaces from './api/workspace/getWorkspaces';
export default function Login() {
const router = useRouter();
const [password, setPassword] = useState('');
const { t } = useTranslation();
useEffect(() => {
// TODO(akhilmhdh): workspace will be controlled by a workspace context
const redirectToDashboard = async () => {
let userWorkspace;
try {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
router.push(`/dashboard/${userWorkspace}`);
} catch (error) {
console.log('Error - Not logged in yet');
}
};
if (isLoggedIn()) {
redirectToDashboard();
}
}, []);
return (
<div className="flex h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
<Head>
<title>{t('common.head-title', { title: t('login.title') })}</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content={t('login.og-title') ?? ''} />
<meta name="og:description" content={t('login.og-description') ?? ''} />
</Head>
<Link href="/">
<div className="mb-4 mt-20 flex justify-center">
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
</div>
</Link>
<div className="mx-auto w-full max-w-md px-6">
<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 email?
</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 email..."
isRequired
autoComplete="email"
id="email"
className="h-12"
/>
</div>
</div>
<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={() => {}}
isFullWidth
className="h-14"
>
{t('login.login')}
</Button>
</div>
<div className="flex flex-row items-center justify-center mt-4">
<button
onClick={() => {router.push('/login')}}
type="button"
className="text-bunker-300 text-sm 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>
</div>
);
}
+79 -45
View File
@@ -3,16 +3,17 @@ import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import CodeInputStep from '@app/components/signup/CodeInputStep';
import DownloadBackupPDF from '@app/components/signup/DonwloadBackupPDFStep';
import EnterEmailStep from '@app/components/signup/EnterEmailStep';
import InitialSignupStep from '@app/components/signup/InitialSignupStep';
import TeamInviteStep from '@app/components/signup/TeamInviteStep';
import UserInfoStep from '@app/components/signup/UserInfoStep';
import SecurityClient from '@app/components/utilities/SecurityClient';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { useProviderAuth } from '@app/hooks/useProviderAuth';
import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode';
import getWorkspaces from './api/workspace/getWorkspaces';
@@ -23,15 +24,26 @@ import getWorkspaces from './api/workspace/getWorkspaces';
export default function SignUp() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [name, setName] = useState('');
const [organizationName, setOrganizationName] = useState('');
const [attributionSource, setAttributionSource] = useState('');
const [code, setCode] = useState('123456');
const [codeError, setCodeError] = useState(false);
const [step, setStep] = useState(1);
const router = useRouter();
const { data: serverDetails } = useFetchServerStatus();
const [isSignupWithEmail, setIsSignupWithEmail] = useState(false);
const [isCodeInputCheckLoading, setIsCodeInputCheckLoading] = useState(false);
const { t } = useTranslation();
const { email: providerEmail, providerAuthToken, isProviderUserCompleted } = useProviderAuth();
if (providerAuthToken && isProviderUserCompleted) {
router.push(`/login?providerAuthToken=${encodeURIComponent(providerAuthToken)}`);
}
if (providerAuthToken && step < 3) {
setStep(3);
}
useEffect(() => {
const tryAuth = async () => {
@@ -57,6 +69,7 @@ export default function SignUp() {
if (step === 1 || step === 3 || step === 4) {
setStep(step + 1);
} else if (step === 2) {
setIsCodeInputCheckLoading(true);
// Checking if the code matches the email.
const response = await checkEmailVerificationCode({ email, code });
if (response.status === 200) {
@@ -66,6 +79,7 @@ export default function SignUp() {
} else {
setCodeError(true);
}
setIsCodeInputCheckLoading(false);
}
};
@@ -82,8 +96,65 @@ export default function SignUp() {
}
}, [step]);
const renderView = (registerStep: number) => {
if (isSignupWithEmail && registerStep === 1) {
return <EnterEmailStep email={email} setEmail={setEmail} incrementStep={incrementStep} />;
}
if (!isSignupWithEmail && registerStep === 1) {
return <InitialSignupStep setIsSignupWithEmail={setIsSignupWithEmail} />;
}
if (registerStep === 2) {
return (
<CodeInputStep
email={email}
incrementStep={incrementStep}
setCode={setCode}
codeError={codeError}
isCodeInputCheckLoading={isCodeInputCheckLoading}
/>
);
}
if (registerStep === 3) {
return (
<UserInfoStep
incrementStep={incrementStep}
email={email || providerEmail}
password={password}
setPassword={setPassword}
name={name}
setName={setName}
organizationName={organizationName}
setOrganizationName={setOrganizationName}
attributionSource={attributionSource}
setAttributionSource={setAttributionSource}
providerAuthToken={providerAuthToken}
/>
);
}
if (registerStep === 4) {
return (
<DownloadBackupPDF
incrementStep={incrementStep}
email={email || providerEmail}
password={password}
name={name}
/>
);
}
if (serverDetails?.emailConfigured) {
return <TeamInviteStep />;
}
return '';
};
return (
<div className="flex h-screen flex-col items-center justify-center bg-bunker-800">
<div className="flex min-h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
<Head>
<title>{t('common.head-title', { title: t('signup.title') })}</title>
<link rel="icon" href="/infisical.ico" />
@@ -91,47 +162,10 @@ export default function SignUp() {
<meta property="og:title" content={t('signup.og-title') as string} />
<meta name="og:description" content={t('signup.og-description') as string} />
</Head>
<div className="flex flex-col items-center justify-center">
<Link href="/">
<div className="mb-2 flex cursor-pointer justify-center md:mb-8">
<Image src="/images/biglogo.png" height={90} width={120} alt="Infisical Wide Logo" />
</div>
</Link>
<form onSubmit={(e) => e.preventDefault()}>
{step === 1 ? (
<EnterEmailStep email={email} setEmail={setEmail} incrementStep={incrementStep} />
) : step === 2 ? (
<CodeInputStep
email={email}
incrementStep={incrementStep}
setCode={setCode}
codeError={codeError}
/>
) : step === 3 ? (
<UserInfoStep
incrementStep={incrementStep}
email={email}
password={password}
setPassword={setPassword}
firstName={firstName}
setFirstName={setFirstName}
lastName={lastName}
setLastName={setLastName}
/>
) : step === 4 ? (
<DownloadBackupPDF
incrementStep={incrementStep}
email={email}
password={password}
name={`${firstName} ${lastName}`}
/>
) : serverDetails?.emailConfigured ? (
<TeamInviteStep />
) : (
''
)}
</form>
<div className="mb-4 mt-20 flex justify-center">
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
</div>
<form onSubmit={(e) => e.preventDefault()}>{renderView(step)}</form>
</div>
);
}
+9 -9
View File
@@ -180,7 +180,7 @@ export default function SignupInvite() {
// Step 4 of the sign up process (download the emergency kit pdf)
const stepConfirmEmail = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<div className="border border-mineshaft-600 bg-mineshaft-800 flex flex-col items-center w-full max-w-xs md:max-w-lg h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold mb-6 flex justify-center text-primary-100">
Confirm your email
</p>
@@ -219,8 +219,8 @@ export default function SignupInvite() {
// Because this is the invite signup - we directly go to the last step of signup (email is already verified)
const main = (
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16">
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
<div className="border border-mineshaft-600 bg-mineshaft-800 w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16">
<p className="text-4xl font-bold flex justify-center mb-6 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-tr from-mineshaft-300 to-white">
Almost there!
</p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
@@ -322,8 +322,8 @@ export default function SignupInvite() {
// Step 4 of the sign up process (download the emergency kit pdf)
const step4 = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
<div className="border border-mineshaft-600 bg-mineshaft-800 flex flex-col items-center w-full max-w-xs md:max-w-lg h-7/12 pt-8 pb-6 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-white to-mineshaft-300">
Save your Emergency Kit
</p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
@@ -336,7 +336,7 @@ export default function SignupInvite() {
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
It contains your Secret Key which we cannot access or recover for you if you lose it.
</div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-4 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
<Button
text="Download PDF"
onButtonPressed={async () => {
@@ -368,14 +368,14 @@ export default function SignupInvite() {
);
return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
<div className="bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 h-screen flex flex-col items-center justify-center">
<Head>
<title>Sign Up</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<Link href="/">
<div className="flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer">
<Image src="/images/biglogo.png" height={90} width={120} alt="Infisical Wide Logo" />
<div className="mb-4 mt-20 flex justify-center">
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
</div>
</Link>
{step === 1 ? stepConfirmEmail : step === 2 ? main : step4}
@@ -571,6 +571,8 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
leftIcon={<FontAwesomeIcon icon={isRollbackMode ? faClockRotateLeft : faCheck} />}
onClick={handleSubmit(onSaveSecret)}
className="h-10"
color="primary"
variant="star"
>
{isRollbackMode ? 'Rollback' : 'Save Changes'}
</Button>
@@ -54,7 +54,8 @@ export const OrgNameChangeSection = ({ onOrgNameChange, orgName }: Props): JSX.E
</div>
<Button
isLoading={isSubmitting}
color="mineshaft"
color="primary"
variant="outline_bg"
size="sm"
type="submit"
isDisabled={!isDirty || isSubmitting}
@@ -57,7 +57,8 @@ export const ProjectNameChangeSection = ({
</div>
<Button
isLoading={isSubmitting}
color="mineshaft"
color="primary"
variant="outline_bg"
size="sm"
type="submit"
isDisabled={!isDirty || isSubmitting}