merge backend

This commit is contained in:
Maidul Islam
2023-03-16 11:26:15 -07:00
18 changed files with 310 additions and 154 deletions
+4 -3
View File
@@ -34,12 +34,12 @@ export const getPostHogProjectApiKey = () => infisical.get('POSTHOG_PROJECT_API_
export const getSentryDSN = () => infisical.get('SENTRY_DSN')!; export const getSentryDSN = () => infisical.get('SENTRY_DSN')!;
export const getSiteURL = () => infisical.get('SITE_URL')!; export const getSiteURL = () => infisical.get('SITE_URL')!;
export const getSmtpHost = () => infisical.get('SMTP_HOST')!; export const getSmtpHost = () => infisical.get('SMTP_HOST')!;
export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false; export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false;
export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587; export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587;
export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!; export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!;
export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!; export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!;
export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!; export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!;
export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical'; export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical';
export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!; export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!;
export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!; export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!;
export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!; export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!;
@@ -47,4 +47,5 @@ export const getStripePublishableKey = () => infisical.get('STRIPE_PUBLISHABLE_K
export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!; export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!;
export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!; export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!;
export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true;
export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!; export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!;
export const getSmtpConfigured = () => infisical.get('SMTP_HOST') == '' || infisical.get('SMTP_HOST') == undefined ? false : true
@@ -7,7 +7,7 @@ import {
} from '../../helpers/signup'; } from '../../helpers/signup';
import { createToken } from '../../helpers/auth'; import { createToken } from '../../helpers/auth';
import { BadRequestError } from '../../utils/errors'; import { BadRequestError } from '../../utils/errors';
import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret } from '../../config'; import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
/** /**
* Signup step 1: Initialize account for user under email [email] and send a verification code * Signup step 1: Initialize account for user under email [email] and send a verification code
@@ -21,7 +21,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
try { try {
email = req.body.email; email = req.body.email;
if (getInviteOnlySignup() || false) { if (getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account // Only one user can create an account without being invited. The rest need to be invited in order to make an account
const userCount = await User.countDocuments({}) const userCount = await User.countDocuments({})
if (userCount != 0) { if (userCount != 0) {
@@ -75,10 +75,12 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
} }
// verify email // verify email
await checkEmailVerification({ if (getSmtpConfigured()) {
email, await checkEmailVerification({
code email,
}); code
});
}
if (!user) { if (!user) {
user = await new User({ user = await new User({
+87 -75
View File
@@ -87,7 +87,7 @@ export const login1 = async (req: Request, res: Response) => {
*/ */
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
try { try {
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' }); if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' });
const { email, clientProof } = req.body; const { email, clientProof } = req.body;
@@ -127,12 +127,12 @@ export const login2 = async (req: Request, res: Response) => {
expiresIn: getJwtMfaLifetime(), expiresIn: getJwtMfaLifetime(),
secret: getJwtMfaSecret() secret: getJwtMfaSecret()
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email email
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: 'emailMfa.handlebars', template: 'emailMfa.handlebars',
@@ -142,13 +142,13 @@ export const login2 = async (req: Request, res: Response) => {
code code
} }
}); });
return res.status(200).send({ return res.status(200).send({
mfaEnabled: true, mfaEnabled: true,
token token
}); });
} }
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.ip, ip: req.ip,
@@ -181,7 +181,7 @@ export const login2 = async (req: Request, res: Response) => {
iv?: string; iv?: string;
tag?: string; tag?: string;
} }
const response: ResponseData = { const response: ResponseData = {
mfaEnabled: false, mfaEnabled: false,
encryptionVersion: user.encryptionVersion, encryptionVersion: user.encryptionVersion,
@@ -191,7 +191,7 @@ export const login2 = async (req: Request, res: Response) => {
iv: user.iv, iv: user.iv,
tag: user.tag tag: user.tag
} }
if ( if (
user?.protectedKey && user?.protectedKey &&
user?.protectedKeyIV && user?.protectedKeyIV &&
@@ -206,14 +206,14 @@ export const login2 = async (req: Request, res: Response) => {
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id userId: user._id
}); });
loginAction && await EELogService.createLog({ loginAction && await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip ipAddress: req.ip
}); });
return res.status(200).send(response); return res.status(200).send(response);
} }
@@ -244,7 +244,7 @@ export const sendMfaToken = async (req: Request, res: Response) => {
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email email
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: 'emailMfa.handlebars', template: 'emailMfa.handlebars',
@@ -259,9 +259,9 @@ export const sendMfaToken = async (req: Request, res: Response) => {
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
message: 'Failed to send MFA code' message: 'Failed to send MFA code'
}); });
} }
return res.status(200).send({ return res.status(200).send({
message: 'Successfully sent new MFA code' message: 'Successfully sent new MFA code'
}); });
@@ -274,75 +274,87 @@ export const sendMfaToken = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const verifyMfaToken = async (req: Request, res: Response) => { export const verifyMfaToken = async (req: Request, res: Response) => {
const { email, mfaToken } = req.body; const { email, mfaToken } = req.body;
await TokenService.validateToken({ await TokenService.validateToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email, email,
token: mfaToken token: mfaToken
}); });
const user = await User.findOne({ const user = await User.findOne({
email email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag'); }).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error('Failed to find user');
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.ip, ip: req.ip,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ userId: user._id.toString() }); const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie('jid', tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: getNodeEnv() === 'production' ? true : false secure: getNodeEnv() === 'production' ? true : false
}); });
interface VerifyMfaTokenRes {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}
const resObj: VerifyMfaTokenRes = { interface VerifyMfaTokenRes {
encryptionVersion: user.encryptionVersion, encryptionVersion: number;
token: tokens.token, protectedKey?: string;
publicKey: user.publicKey as string, protectedKeyIV?: string;
encryptedPrivateKey: user.encryptedPrivateKey as string, protectedKeyTag?: string;
iv: user.iv as string, token: string;
tag: user.tag as string publicKey: string;
} encryptedPrivateKey: string;
iv: string;
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) { tag: string;
resObj.protectedKey = user.protectedKey; }
resObj.protectedKeyIV = user.protectedKeyIV;
resObj.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({ interface VerifyMfaTokenRes {
name: ACTION_LOGIN, encryptionVersion: number;
userId: user._id protectedKey?: string;
}); protectedKeyIV?: string;
protectedKeyTag?: string;
loginAction && await EELogService.createLog({ token: string;
userId: user._id, publicKey: string;
actions: [loginAction], encryptedPrivateKey: string;
channel: getChannelFromUserAgent(req.headers['user-agent']), iv: string;
ipAddress: req.ip tag: string;
}); }
return res.status(200).send(resObj); const resObj: VerifyMfaTokenRes = {
} encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string,
tag: user.tag as string
}
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
resObj.protectedKey = user.protectedKey;
resObj.protectedKeyIV = user.protectedKeyIV;
resObj.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
});
return res.status(200).send(resObj);
}
+19 -17
View File
@@ -3,7 +3,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import handlebars from 'handlebars'; import handlebars from 'handlebars';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { getSmtpFromName, getSmtpFromAddress } from '../config'; import { getSmtpFromName, getSmtpFromAddress, getSmtpConfigured } from '../config';
let smtpTransporter: nodemailer.Transporter; let smtpTransporter: nodemailer.Transporter;
@@ -25,23 +25,25 @@ const sendMail = async ({
recipients: string[]; recipients: string[];
substitutions: any; substitutions: any;
}) => { }) => {
try { if (getSmtpConfigured()) {
const html = fs.readFileSync( try {
path.resolve(__dirname, '../templates/' + template), const html = fs.readFileSync(
'utf8' path.resolve(__dirname, '../templates/' + template),
); 'utf8'
const temp = handlebars.compile(html); );
const htmlToSend = temp(substitutions); const temp = handlebars.compile(html);
const htmlToSend = temp(substitutions);
await smtpTransporter.sendMail({ await smtpTransporter.sendMail({
from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`, from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`,
to: recipients.join(', '), to: recipients.join(', '),
subject: subjectLine, subject: subjectLine,
html: htmlToSend html: htmlToSend
}); });
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
}
} }
}; };
+41 -39
View File
@@ -23,49 +23,49 @@ const swaggerFile = require('../spec.json');
const requestIp = require('request-ip'); const requestIp = require('request-ip');
import { apiLimiter } from './helpers/rateLimiter'; import { apiLimiter } from './helpers/rateLimiter';
import { import {
workspace as eeWorkspaceRouter, workspace as eeWorkspaceRouter,
secret as eeSecretRouter, secret as eeSecretRouter,
secretSnapshot as eeSecretSnapshotRouter, secretSnapshot as eeSecretSnapshotRouter,
action as eeActionRouter action as eeActionRouter
} from './ee/routes/v1'; } from './ee/routes/v1';
import { import {
signup as v1SignupRouter, signup as v1SignupRouter,
auth as v1AuthRouter, auth as v1AuthRouter,
bot as v1BotRouter, bot as v1BotRouter,
organization as v1OrganizationRouter, organization as v1OrganizationRouter,
workspace as v1WorkspaceRouter, workspace as v1WorkspaceRouter,
membershipOrg as v1MembershipOrgRouter, membershipOrg as v1MembershipOrgRouter,
membership as v1MembershipRouter, membership as v1MembershipRouter,
key as v1KeyRouter, key as v1KeyRouter,
inviteOrg as v1InviteOrgRouter, inviteOrg as v1InviteOrgRouter,
user as v1UserRouter, user as v1UserRouter,
userAction as v1UserActionRouter, userAction as v1UserActionRouter,
secret as v1SecretRouter, secret as v1SecretRouter,
serviceToken as v1ServiceTokenRouter, serviceToken as v1ServiceTokenRouter,
password as v1PasswordRouter, password as v1PasswordRouter,
stripe as v1StripeRouter, stripe as v1StripeRouter,
integration as v1IntegrationRouter, integration as v1IntegrationRouter,
integrationAuth as v1IntegrationAuthRouter integrationAuth as v1IntegrationAuthRouter
} from './routes/v1'; } from './routes/v1';
import { import {
signup as v2SignupRouter, signup as v2SignupRouter,
auth as v2AuthRouter, auth as v2AuthRouter,
users as v2UsersRouter, users as v2UsersRouter,
organizations as v2OrganizationsRouter, organizations as v2OrganizationsRouter,
workspace as v2WorkspaceRouter, workspace as v2WorkspaceRouter,
secret as v2SecretRouter, // begin to phase out secret as v2SecretRouter, // begin to phase out
secrets as v2SecretsRouter, secrets as v2SecretsRouter,
serviceTokenData as v2ServiceTokenDataRouter, serviceTokenData as v2ServiceTokenDataRouter,
apiKeyData as v2APIKeyDataRouter, apiKeyData as v2APIKeyDataRouter,
environment as v2EnvironmentRouter, environment as v2EnvironmentRouter,
tags as v2TagsRouter, tags as v2TagsRouter,
} from './routes/v2'; } from './routes/v2';
import { healthCheck } from './routes/status'; import { healthCheck } from './routes/status';
import { getLogger } from './utils/logger'; import { getLogger } from './utils/logger';
import { RouteNotFoundError } from './utils/errors'; import { RouteNotFoundError } from './utils/errors';
import { requestErrorHandler } from './middleware/requestErrorHandler'; import { requestErrorHandler } from './middleware/requestErrorHandler';
import { import {
getMongoURL, getMongoURL,
getNodeEnv, getNodeEnv,
getPort, getPort,
getSentryDSN, getSentryDSN,
@@ -73,10 +73,12 @@ import {
} from './config'; } from './config';
const main = async () => { const main = async () => {
await infisical.connect({ if (process.env.INFISICAL_TOKEN != "" || process.env.INFISICAL_TOKEN != undefined) {
token: process.env.INFISICAL_TOKEN! await infisical.connect({
}); token: process.env.INFISICAL_TOKEN!
});
}
logTelemetryMessage(); logTelemetryMessage();
setTransporter(initSmtp()); setTransporter(initSmtp());
@@ -158,7 +160,7 @@ const main = async () => {
//* Handle unrouted requests and respond with proper error message as well as status code //* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next) => { app.use((req, res, next) => {
if (res.headersSent) return next(); if (res.headersSent) return next();
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` })) next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` }))
}) })
@@ -170,7 +172,7 @@ const main = async () => {
createTestUserForDevelopment(); createTestUserForDevelopment();
setUpHealthEndpoint(server); setUpHealthEndpoint(server);
server.on('close', async () => { server.on('close', async () => {
await DatabaseService.closeDatabase(); await DatabaseService.closeDatabase();
}) })
+2
View File
@@ -1,4 +1,5 @@
import express, { Request, Response } from 'express'; import express, { Request, Response } from 'express';
import { getSmtpConfigured } from '../../config';
const router = express.Router(); const router = express.Router();
@@ -8,6 +9,7 @@ router.get(
res.status(200).json({ res.status(200).json({
date: new Date(), date: new Date(),
message: 'Ok', message: 'Ok',
emailConfigured: getSmtpConfigured()
}) })
} }
); );
@@ -2,10 +2,13 @@ import React, { useState } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { usePopUp } from '@app/hooks/usePopUp';
import addUserToOrg from '@app/pages/api/organization/addUserToOrg'; import addUserToOrg from '@app/pages/api/organization/addUserToOrg';
import getWorkspaces from '@app/pages/api/workspace/getWorkspaces'; import getWorkspaces from '@app/pages/api/workspace/getWorkspaces';
import Button from '../basic/buttons/Button'; import Button from '../basic/buttons/Button';
import { EmailServiceSetupModal } from '../v2';
/** /**
* This is the last step of the signup flow. People can optionally invite their teammates here. * This is the last step of the signup flow. People can optionally invite their teammates here.
@@ -14,6 +17,10 @@ export default function TeamInviteStep(): JSX.Element {
const [emails, setEmails] = useState(''); const [emails, setEmails] = useState('');
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const {data: serverDetails } = useFetchServerStatus()
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
'setUpEmail'
] as const);
// Redirect user to the getting started page // Redirect user to the getting started page
const redirectToHome = async () => { const redirectToHome = async () => {
@@ -62,10 +69,20 @@ export default function TeamInviteStep(): JSX.Element {
</div> </div>
<Button <Button
text={t('signup:step5-send-invites') ?? ''} text={t('signup:step5-send-invites') ?? ''}
onButtonPressed={() => inviteUsers({ emails })} onButtonPressed={() => {
if(serverDetails?.emailConfigured){
inviteUsers({ emails })
}else{
handlePopUpOpen('setUpEmail');
}
}}
size="lg" size="lg"
/> />
</div> </div>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div> </div>
); );
} }
@@ -0,0 +1,21 @@
import { Button } from '../Button';
import { Modal, ModalContent } from '../Modal';
type Props = {
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
};
export const EmailServiceSetupModal = ({ isOpen, onOpenChange }: Props): JSX.Element => (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent title="Email service not configured">
<p className="mb-4 text-bunker-300">
The administrators of this Infisical instance have not yet set up an email service provider required to perform this action
</p>
<a href="https://infisical.com/docs/self-hosting/configuration/email">
<Button className="mr-4">Learn more</Button>
</a>
</ModalContent>
</Modal>
);
@@ -0,0 +1 @@
export { EmailServiceSetupModal } from './EmailServiceSetupModal';
+1
View File
@@ -3,6 +3,7 @@ export * from './Card';
export * from './Checkbox'; export * from './Checkbox';
export * from './DeleteActionModal'; export * from './DeleteActionModal';
export * from './Dropdown'; export * from './Dropdown';
export * from './EmailServiceSetupModal'
export * from './EmptyState'; export * from './EmptyState';
export * from './FormControl'; export * from './FormControl';
export * from './IconButton'; export * from './IconButton';
@@ -0,0 +1 @@
export { useFetchServerStatus } from './queries'
@@ -0,0 +1,19 @@
import {useQuery } from '@tanstack/react-query';
import { apiRequest } from '@app/config/request';
import { ServerStatus } from './types';
// cache key
const serverStatusKeys = {
serverStatus: ['serverStatus'] as const
};
const fetchServerStatus = async () => {
const {data} = await apiRequest.get<ServerStatus>('/api/status');
return data;
};
export const useFetchServerStatus= () => {
return useQuery({ queryKey: serverStatusKeys.serverStatus, queryFn: fetchServerStatus });
}
@@ -0,0 +1,5 @@
export type ServerStatus = {
date: string;
message: string;
emailConfigured: boolean;
};
+11 -1
View File
@@ -13,6 +13,7 @@ import TeamInviteStep from '@app/components/signup/TeamInviteStep';
import UserInfoStep from '@app/components/signup/UserInfoStep'; import UserInfoStep from '@app/components/signup/UserInfoStep';
import SecurityClient from '@app/components/utilities/SecurityClient'; import SecurityClient from '@app/components/utilities/SecurityClient';
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode'; import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode';
import getWorkspaces from './api/workspace/getWorkspaces'; import getWorkspaces from './api/workspace/getWorkspaces';
@@ -25,10 +26,12 @@ export default function SignUp() {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [firstName, setFirstName] = useState(''); const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState(''); const [lastName, setLastName] = useState('');
const [code, setCode] = useState(''); const [code, setCode] = useState('123456');
const [codeError, setCodeError] = useState(false); const [codeError, setCodeError] = useState(false);
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const router = useRouter(); const router = useRouter();
const {data: serverDetails } = useFetchServerStatus()
const { t } = useTranslation(); const { t } = useTranslation();
@@ -68,6 +71,13 @@ export default function SignUp() {
} }
}; };
// when email service is not configured, skip step 2
useEffect(() => {
if (!serverDetails?.emailConfigured && step === 2){
incrementStep()
}
}, [step]);
return ( return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center"> <div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
<Head> <Head>
+19 -1
View File
@@ -6,12 +6,19 @@ import Link from 'next/link';
import Button from '@app/components/basic/buttons/Button'; import Button from '@app/components/basic/buttons/Button';
import InputField from '@app/components/basic/InputField'; import InputField from '@app/components/basic/InputField';
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
import { EmailServiceSetupModal } from '@app/components/v2';
import { usePopUp } from '@app/hooks';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import SendEmailOnPasswordReset from './api/auth/SendEmailOnPasswordReset'; import SendEmailOnPasswordReset from './api/auth/SendEmailOnPasswordReset';
export default function VerifyEmail() { export default function VerifyEmail() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const {data: serverDetails } = useFetchServerStatus()
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
'setUpEmail'
] as const);
/** /**
* This function sends the verification email and forwards a user to the next step. * This function sends the verification email and forwards a user to the next step.
@@ -63,7 +70,13 @@ export default function VerifyEmail() {
</div> </div>
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm"> <div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg"> <div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button text="Continue" onButtonPressed={sendVerificationEmail} size="lg" /> <Button text="Continue" onButtonPressed={()=>{
if (serverDetails?.emailConfigured){
sendVerificationEmail()
} else {
handlePopUpOpen('setUpEmail');
}
}} size="lg" />
</div> </div>
</div> </div>
</div> </div>
@@ -80,6 +93,11 @@ export default function VerifyEmail() {
</div> </div>
</div> </div>
)} )}
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div> </div>
); );
} }
@@ -13,6 +13,7 @@ import * as yup from 'yup';
import { import {
Button, Button,
DeleteActionModal, DeleteActionModal,
EmailServiceSetupModal,
EmptyState, EmptyState,
FormControl, FormControl,
IconButton, IconButton,
@@ -28,6 +29,7 @@ import {
THead, THead,
Tr} from '@app/components/v2'; Tr} from '@app/components/v2';
import { usePopUp } from '@app/hooks'; import { usePopUp } from '@app/hooks';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { IncidentContact } from '@app/hooks/api/types'; import { IncidentContact } from '@app/hooks/api/types';
type Props = { type Props = {
@@ -50,9 +52,11 @@ export const OrgIncidentContactsTable = ({
isLoading isLoading
}: Props) => { }: Props) => {
const [searchContact, setSearchContact] = useState(''); const [searchContact, setSearchContact] = useState('');
const {data: serverDetails } = useFetchServerStatus()
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
'addContact', 'addContact',
'removeContact' 'removeContact',
'setUpEmail'
] as const); ] as const);
const { const {
@@ -92,7 +96,13 @@ export const OrgIncidentContactsTable = ({
<div> <div>
<Button <Button
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen('addContact')} onClick={() => {
if (serverDetails?.emailConfigured){
handlePopUpOpen('addContact');
} else {
handlePopUpOpen('setUpEmail');
}
}}
> >
Add Contact Add Contact
</Button> </Button>
@@ -180,6 +190,10 @@ export const OrgIncidentContactsTable = ({
onChange={(isOpen) => handlePopUpToggle('removeContact', isOpen)} onChange={(isOpen) => handlePopUpToggle('removeContact', isOpen)}
onDeleteApproved={onRemoveIncidentContact} onDeleteApproved={onRemoveIncidentContact}
/> />
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div> </div>
); );
}; };
@@ -9,7 +9,7 @@ import * as yup from 'yup';
import { import {
Button, Button,
DeleteActionModal, DeleteActionModal,
EmptyState, EmailServiceSetupModal, EmptyState,
FormControl, FormControl,
IconButton, IconButton,
Input, Input,
@@ -28,6 +28,7 @@ import {
Tr, Tr,
UpgradePlanModal} from '@app/components/v2'; UpgradePlanModal} from '@app/components/v2';
import { usePopUp } from '@app/hooks'; import { usePopUp } from '@app/hooks';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { OrgUser, Workspace } from '@app/hooks/api/types'; import { OrgUser, Workspace } from '@app/hooks/api/types';
type Props = { type Props = {
@@ -64,10 +65,12 @@ export const OrgMembersTable = ({
}: Props) => { }: Props) => {
const router = useRouter(); const router = useRouter();
const [searchMemberFilter, setSearchMemberFilter] = useState(''); const [searchMemberFilter, setSearchMemberFilter] = useState('');
const {data: serverDetails } = useFetchServerStatus()
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
'addMember', 'addMember',
'removeMember', 'removeMember',
'upgradePlan' 'upgradePlan',
'setUpEmail'
] as const); ] as const);
const { const {
@@ -79,8 +82,8 @@ export const OrgMembersTable = ({
const onAddMember = async ({ email }: TAddMemberForm) => { const onAddMember = async ({ email }: TAddMemberForm) => {
await onInviteMember(email); await onInviteMember(email);
handlePopUpClose('addMember'); handlePopUpClose('addMember');
reset(); reset();
}; };
const onRemoveOrgMemberApproved = async () => { const onRemoveOrgMemberApproved = async () => {
@@ -121,10 +124,14 @@ export const OrgMembersTable = ({
<Button <Button
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => { onClick={() => {
if (isMoreUserNotAllowed) { if (serverDetails?.emailConfigured){
handlePopUpOpen('upgradePlan'); if (isMoreUserNotAllowed) {
handlePopUpOpen('upgradePlan');
} else {
handlePopUpOpen('addMember');
}
} else { } else {
handlePopUpOpen('addMember'); handlePopUpOpen('setUpEmail');
} }
}} }}
> >
@@ -291,6 +298,10 @@ export const OrgMembersTable = ({
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)} onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
text="You can add custom environments if you switch to Infisical's Team plan." text="You can add custom environments if you switch to Infisical's Team plan."
/> />
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div> </div>
); );
}; };
@@ -1,7 +1,9 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider'; import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
import { Checkbox } from '@app/components/v2'; import { Checkbox, EmailServiceSetupModal } from '@app/components/v2';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { usePopUp } from '@app/hooks/usePopUp';
import { useGetUser } from '../../../../hooks/api'; import { useGetUser } from '../../../../hooks/api';
import { User } from '../../../../hooks/api/types'; import { User } from '../../../../hooks/api/types';
@@ -11,6 +13,11 @@ export const SecuritySection = () => {
const [isMfaEnabled, setIsMfaEnabled] = useState(false); const [isMfaEnabled, setIsMfaEnabled] = useState(false);
const { data: user } = useGetUser(); const { data: user } = useGetUser();
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
'setUpEmail'
] as const);
const {data: serverDetails } = useFetchServerStatus()
useEffect(() => { useEffect(() => {
if (user && typeof user.isMfaEnabled !== 'undefined') { if (user && typeof user.isMfaEnabled !== 'undefined') {
@@ -42,6 +49,7 @@ export const SecuritySection = () => {
} }
return ( return (
<>
<form> <form>
<div className="mb-6 mt-2 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2"> <div className="mb-6 mt-2 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
<p className="mb-4 mt-2 text-xl font-semibold"> <p className="mb-4 mt-2 text-xl font-semibold">
@@ -52,12 +60,21 @@ export const SecuritySection = () => {
id="isTwoFAEnabled" id="isTwoFAEnabled"
isChecked={isMfaEnabled} isChecked={isMfaEnabled}
onCheckedChange={(state) => { onCheckedChange={(state) => {
toggleMfa(state as boolean); if (serverDetails?.emailConfigured){
toggleMfa(state as boolean);
} else {
handlePopUpOpen('setUpEmail');
}
}} }}
> >
Enable 2-factor authentication via your personal email. Enable 2-factor authentication via your personal email.
</Checkbox> </Checkbox>
</div> </div>
</form> </form>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</>
); );
}; };