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
+1
View File
@@ -48,3 +48,4 @@ 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
if (getSmtpConfigured()) {
await checkEmailVerification({ await checkEmailVerification({
email, email,
code code
}); });
}
if (!user) { if (!user) {
user = await new User({ user = await new User({
@@ -317,6 +317,18 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
tag: string; tag: string;
} }
interface VerifyMfaTokenRes {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}
const resObj: VerifyMfaTokenRes = { const resObj: VerifyMfaTokenRes = {
encryptionVersion: user.encryptionVersion, encryptionVersion: user.encryptionVersion,
token: tokens.token, token: tokens.token,
+3 -1
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,6 +25,7 @@ const sendMail = async ({
recipients: string[]; recipients: string[];
substitutions: any; substitutions: any;
}) => { }) => {
if (getSmtpConfigured()) {
try { try {
const html = fs.readFileSync( const html = fs.readFileSync(
path.resolve(__dirname, '../templates/' + template), path.resolve(__dirname, '../templates/' + template),
@@ -43,6 +44,7 @@ const sendMail = async ({
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
} }
}
}; };
const setTransporter = (transporter: nodemailer.Transporter) => { const setTransporter = (transporter: nodemailer.Transporter) => {
+2
View File
@@ -73,9 +73,11 @@ import {
} from './config'; } from './config';
const main = async () => { const main = async () => {
if (process.env.INFISICAL_TOKEN != "" || process.env.INFISICAL_TOKEN != undefined) {
await infisical.connect({ await infisical.connect({
token: process.env.INFISICAL_TOKEN! token: process.env.INFISICAL_TOKEN!
}); });
}
logTelemetryMessage(); logTelemetryMessage();
setTransporter(initSmtp()); setTransporter(initSmtp());
+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 {
@@ -121,11 +124,15 @@ export const OrgMembersTable = ({
<Button <Button
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => { onClick={() => {
if (serverDetails?.emailConfigured){
if (isMoreUserNotAllowed) { if (isMoreUserNotAllowed) {
handlePopUpOpen('upgradePlan'); handlePopUpOpen('upgradePlan');
} else { } else {
handlePopUpOpen('addMember'); handlePopUpOpen('addMember');
} }
} else {
handlePopUpOpen('setUpEmail');
}
}} }}
> >
Add Member Add Member
@@ -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) => {
if (serverDetails?.emailConfigured){
toggleMfa(state as boolean); 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)}
/>
</>
); );
}; };