1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-29 22:02:57 +00:00

Refactoring functions into services, helper functions, hooks, patch bugs

This commit is contained in:
Tuan Dang
2023-02-14 17:38:58 +07:00
parent 13b1805d04
commit e1ad8fbee8
30 changed files with 383 additions and 360 deletions

@ -269,19 +269,36 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
secure: NODE_ENV === '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 = {
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;
}
// case: user does not have MFA enabled
// return (access) token in response
return res.status(200).send({
encryptionVersion: user.encryptionVersion,
protectedKey: user.protectedKey ?? null,
protectedKeyIV: user.protectedKeyIV ?? null,
protectedKeyTag: user.protectedKeyTag ?? null,
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
});
return res.status(200).send(resObj);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);

@ -7,6 +7,7 @@ import {
} from '../../helpers/signup';
import { issueAuthTokens } from '../../helpers/auth';
import { INVITED, ACCEPTED } from '../../variables';
import { NODE_ENV } from '../../config';
import axios from 'axios';
/**
@ -105,7 +106,6 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
});
token = tokens.token;
refreshToken = tokens.refreshToken;
// sending a welcome email to new users
if (process.env.LOOPS_API_KEY) {
@ -121,6 +121,14 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
},
});
}
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@ -132,8 +140,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
return res.status(200).send({
message: 'Successfully set up account',
user,
token,
refreshToken
token
});
};
@ -219,7 +226,14 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
});
token = tokens.token;
refreshToken = tokens.refreshToken;
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@ -227,11 +241,10 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
message: 'Failed to complete account setup'
});
}
return res.status(200).send({
message: 'Successfully set up account',
user,
token,
refreshToken
token
});
};

@ -12,7 +12,7 @@ import {
import {
SALT_ROUNDS
} from '../config';
import { UnauthorizedRequestError } from '../utils/errors';
import { ForbiddenRequestError } from '../utils/errors';
/**
* Create and store a token in the database for purpose [type]
@ -65,29 +65,45 @@ const createTokenHelper = async ({
break;
}
interface Query {
interface TokenDataQuery {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
interface TokenDataUpdate {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
tokenHash: string;
expiresAt: Date;
}
const query: Query = { type };
const query: TokenDataQuery = { type };
const update: TokenDataUpdate = {
type,
tokenHash: await bcrypt.hash(token, SALT_ROUNDS),
expiresAt
}
if (email) { query.email = email; }
if (phoneNumber) { query.phoneNumber = phoneNumber; }
if (organizationId) { query.organization = organizationId }
if (email) {
query.email = email;
update.email = email;
}
if (phoneNumber) {
query.phoneNumber = phoneNumber;
update.phoneNumber = phoneNumber;
}
if (organizationId) {
query.organization = organizationId
update.organization = organizationId
}
await TokenData.findOneAndUpdate(
query,
{
type,
email,
phoneNumber,
organization: organizationId,
tokenHash: await bcrypt.hash(token, SALT_ROUNDS),
expiresAt
},
update,
{
new: true,
upsert: true
@ -123,37 +139,38 @@ const validateTokenHelper = async ({
organizationId?: Types.ObjectId;
token: string;
}) => {
try {
interface Query {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
interface Query {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
const query: Query = { type };
const query: Query = { type };
if (email) { query.email = email; }
if (phoneNumber) { query.phoneNumber = phoneNumber; }
if (organizationId) { query.organization = organizationId; }
if (email) { query.email = email; }
if (phoneNumber) { query.phoneNumber = phoneNumber; }
if (organizationId) { query.organization = organizationId; }
const tokenData = await TokenData.findOneAndDelete(query);
if (!tokenData) throw new Error('Failed to find token to validate');
if (tokenData.expiresAt < new Date()) throw new Error('Token has expired');
const isValid = await bcrypt.compare(token, tokenData.tokenHash);
if (!isValid) throw UnauthorizedRequestError({
const tokenData = await TokenData.findOne(query).select('+tokenHash');
if (!tokenData) throw new Error('Failed to find token to validate');
if (tokenData.expiresAt < new Date()) {
await TokenData.findByIdAndDelete(tokenData._id);
throw ForbiddenRequestError({
message: 'Failed token data validation due to token is no longer valid'
});
}
const isValid = await bcrypt.compare(token, tokenData.tokenHash);
if (!isValid) {
throw ForbiddenRequestError({
message: 'Failed token data validation due to incorrect token'
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(
"Failed to validate token data"
);
}
await TokenData.findByIdAndDelete(tokenData._id);
}
export {

@ -1,11 +1,12 @@
import { ErrorRequestHandler } from "express";
import * as Sentry from '@sentry/node';
import { InternalServerError } from "../utils/errors";
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
import { getLogger } from "../utils/logger";
import RequestError, { LogLevel } from "../utils/requestError";
import { NODE_ENV } from "../config";
import { TokenExpiredError } from 'jsonwebtoken';
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => {
if (res.headersSent) return next();
@ -16,7 +17,9 @@ export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | E
}
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
if (!(error instanceof RequestError)) {
if (error instanceof TokenExpiredError) {
error = UnauthorizedRequestError({ stack: error.stack, message: 'Token expired' });
} else if (!(error instanceof RequestError)) {
error = InternalServerError({ context: { exception: error.message }, stack: error.stack })
getLogger('backend-main').log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message)
}

@ -1,5 +1,37 @@
---
title: "Circle CI"
description: "How to automatically sync secrets from Infisical into your CircleCI project."
---
Coming soon.
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for CircleCI
Obtain a Fly.io access token in Access Tokens
![integrations fly dashboard](../../images/integrations-flyio-dashboard.png)
![integrations fly token](../../images/integrations-flyio-token.png)
Press on the Fly.io tile and input your Fly.io access token to grant Infisical access to your Fly.io account.
![integrations fly authorization](../../images/integrations-flyio-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which Fly.io app and press create integration to start syncing secrets to Fly.io.
![integrations fly](../../images/integrations-flyio-create.png)
![integrations fly](../../images/integrations-flyio.png)

@ -21,6 +21,8 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
| [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available |
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Available |
| [React](/integrations/frameworks/react) | Framework | Available |
| [Vue](/integrations/frameworks/vue) | Framework | Available |
| [Express](/integrations/frameworks/express) | Framework | Available |
@ -39,8 +41,6 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| GCP | Cloud | Coming soon |
| Azure | Cloud | Coming soon |
| DigitalOcean | Cloud | Coming soon |
| [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available |
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Coming soon |
| TravisCI | CI/CD | Coming soon |
| GitHub Actions | CI/CD | Coming soon |
| Jenkins | CI/CD | Coming soon |

@ -9,9 +9,9 @@
"step1-start": "Let's get started",
"step1-privacy": "By creating an account, you agree to our Terms and have read and acknowledged the Privacy Policy.",
"step1-submit": "Get Started",
"step2-message": "We've sent a verification email to",
"step2-message": "We've sent a verification code to",
"step2-code-error": "Oops. Your code is wrong. Please try again.",
"step2-resend-alert": "Don't see the email?",
"step2-resend-alert": "Don't see the code?",
"step2-resend-submit": "Resend",
"step2-resend-progress": "Resending...",
"step2-spam-alert": "Make sure to check your spam inbox.",

@ -11,7 +11,7 @@
"step1-submit": "C'est parti",
"step2-message": "Nous avons envoyé un email de vérification à",
"step2-code-error": "Oops. Votre code est faux. Veuillez réessayer.",
"step2-resend-alert": "Vous ne voyez pas l'email?",
"step2-resend-alert": "Vous ne voyez pas le code?",
"step2-resend-submit": "Renvoyer",
"step2-resend-progress": "Envoie en cours...",
"step2-spam-alert": "Assurez-vous de vérifier vos spams.",

@ -11,7 +11,7 @@
"step1-submit": "Başla",
"step2-message": "Şu adrese bir doğrulama maili yolladık",
"step2-code-error": "Tüh. Kodun hatalı. Lütfen tekrar dene.",
"step2-resend-alert": "Mail ulaşamadı mı?",
"step2-resend-alert": "Kodu ulaşamadı mı?",
"step2-resend-submit": "Tekrar Yolla",
"step2-resend-progress": "Tekrar yollanıyor...",
"step2-spam-alert": "Spam kutunuzu kontrol ettiğinizden emin olun.",

@ -5,7 +5,7 @@ import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import attemptLoginMfa from '@app/components/utilities/attemptLoginMfa';
import resendMfaToken from '@app/pages/api/auth/resendMfaToken';
import { useSendMfaToken } from '@app/hooks/api/auth';
import Button from '../basic/buttons/Button';
import Error from '../basic/Error';
@ -50,6 +50,8 @@ export default function MFAStep({
const [mfaCode, setMfaCode] = useState('');
const [codeError, setCodeError] = useState(false);
const sendMfaToken = useSendMfaToken();
const { t } = useTranslation();
const handleLoginMfa = async () => {
@ -72,15 +74,14 @@ export default function MFAStep({
} catch (err) {
console.error(err);
setIsLoading(false);
setCodeError(true);
}
}
const handleResendMfaCode = async () => {
try {
await resendMfaToken({
email
});
await sendMfaToken.mutateAsync({ email });
} catch (err) {
console.error(err);
}

@ -87,7 +87,6 @@ export default function Navbar() {
}, []);
const closeApp = async () => {
console.log('Logging out...');
await logout();
router.push('/login');
};

@ -161,6 +161,8 @@ export default function UserInfoStep({
organizationName: `${firstName}'s organization`
});
// unset signup JWT token and set JWT token
SecurityClient.setSignupToken('');
SecurityClient.setToken(response.token);
saveTokenToLocalStorage({
@ -174,14 +176,18 @@ export default function UserInfoStep({
privateKey
});
incrementStep();
const userOrgs = await getOrganizations();
await ProjectService.initProject({
organizationId: userOrgs[0]?._id,
const orgId = userOrgs[0]?._id;
const project = await ProjectService.initProject({
organizationId: orgId,
projectName: 'Example Project'
});
localStorage.setItem('orgData.id', orgId);
localStorage.setItem('projectData.id', project._id);
incrementStep();
} catch (error) {
setIsLoading(false);
console.error(error);

@ -1,7 +1,15 @@
import { getAuthToken, setAuthToken , setMfaTempToken } from '@app/reactQuery';
import {
getAuthToken,
setAuthToken,
setMfaTempToken,
setSignupTempToken} from '@app/reactQuery';
// depreciated: go for apiRequest module in config/api
export default class SecurityClient {
static setSignupToken(tokenStr: string) {
setSignupTempToken(tokenStr);
}
static setMfaToken(tokenStr: string) {
setMfaTempToken(tokenStr);
}

@ -44,7 +44,7 @@ const attemptLogin = async (
client.setSalt(salt);
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
const {
mfaEnabled,
encryptionVersion,
@ -65,7 +65,7 @@ const attemptLogin = async (
// case: MFA is enabled
// set temporary (MFA) JWT token
SecurityClient.setToken(token);
SecurityClient.setMfaToken(token);
resolve({
mfaEnabled,
@ -113,40 +113,14 @@ const attemptLogin = async (
const userOrgs = await getOrganizations();
const orgId = userOrgs[0]._id;
localStorage.setItem('orgData.id', orgId);
const orgUserProjects = await getOrganizationUserProjects({
orgId
});
localStorage.setItem('projectData.id', orgUserProjects[0]._id);
// // TODO: this part definitely needs to be refactored
// const userOrgs = await getOrganizations();
// const userOrgsData = userOrgs.map((org: { _id: string }) => org._id);
// let orgToLogin;
// if (userOrgsData.includes(localStorage.getItem('orgData.id'))) {
// orgToLogin = localStorage.getItem('orgData.id');
// } else {
// orgToLogin = userOrgsData[0];
// localStorage.setItem('orgData.id', orgToLogin);
// }
// let orgUserProjects = await getOrganizationUserProjects({
// orgId: orgToLogin
// });
// orgUserProjects = orgUserProjects?.map((project: { _id: string }) => project._id);
// let projectToLogin;
// if (orgUserProjects.includes(localStorage.getItem('projectData.id'))) {
// projectToLogin = localStorage.getItem('projectData.id');
// } else {
// try {
// projectToLogin = orgUserProjects[0];
// localStorage.setItem('projectData.id', projectToLogin);
// } catch (error) {
// console.log('ERROR: User likely has no projects. ', error);
// }
// }
if (orgUserProjects.length > 0) {
localStorage.setItem('projectData.id', orgUserProjects[0]._id);
}
if (email) {
telemetry.identify(email);
@ -166,100 +140,4 @@ const attemptLogin = async (
});
};
export default attemptLogin;
// should be function: init first project
// if (isSignUp) {
// const randomBytes = crypto.randomBytes(16).toString('hex');
// const PRIVATE_KEY = String(localStorage.getItem('PRIVATE_KEY'));
// const myUser = await getUser();
// const { ciphertext, nonce } = encryptAssymmetric({
// plaintext: randomBytes,
// publicKey: myUser.publicKey,
// privateKey: PRIVATE_KEY
// }) as { ciphertext: string; nonce: string };
// await uploadKeys(projectToLogin, myUser._id, ciphertext, nonce);
// const secretsToBeAdded: SecretDataProps[] = [
// {
// pos: 0,
// key: 'DATABASE_URL',
// // eslint-disable-next-line no-template-curly-in-string
// value: 'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
// valueOverride: undefined,
// comment: 'Secret referencing example',
// id: '',
// tags: []
// },
// {
// pos: 1,
// key: 'DB_USERNAME',
// value: 'OVERRIDE_THIS',
// valueOverride: undefined,
// comment:
// 'Override secrets with personal value',
// id: '',
// tags: []
// },
// {
// pos: 2,
// key: 'DB_PASSWORD',
// value: 'OVERRIDE_THIS',
// valueOverride: undefined,
// comment:
// 'Another secret override',
// id: '',
// tags: []
// },
// {
// pos: 3,
// key: 'DB_USERNAME',
// value: 'user1234',
// valueOverride: 'user1234',
// comment: '',
// id: '',
// tags: []
// },
// {
// pos: 4,
// key: 'DB_PASSWORD',
// value: 'example_password',
// valueOverride: 'example_password',
// comment: '',
// id: '',
// tags: []
// },
// {
// pos: 5,
// key: 'TWILIO_AUTH_TOKEN',
// value: 'example_twillio_token',
// valueOverride: undefined,
// comment: '',
// id: '',
// tags: []
// },
// {
// pos: 6,
// key: 'WEBSITE_URL',
// value: 'http://localhost:3000',
// valueOverride: undefined,
// comment: '',
// id: '',
// tags: []
// }
// ];
// const secrets = await encryptSecrets({
// secretsToEncrypt: secretsToBeAdded,
// workspaceId: String(localStorage.getItem('projectData.id')),
// env: 'dev'
// });
// await addSecrets({
// secrets: secrets ?? [],
// env: 'dev',
// workspaceId: String(localStorage.getItem('projectData.id'))
// });
// }
export default attemptLogin;

@ -3,6 +3,8 @@ import jsrp from 'jsrp';
import login1 from '@app/pages/api/auth/Login1';
import verifyMfaToken from '@app/pages/api/auth/verifyMfaToken';
import getOrganizations from '@app/pages/api/organization/getOrgs';
import getOrganizationUserProjects from '@app/pages/api/organization/GetOrgUserProjects';
import KeyService from '@app/services/KeyService';
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
@ -51,7 +53,8 @@ const attemptLoginMfa = async ({
mfaToken
});
// set JWT token
// unset temporary (MFA) JWT token and set JWT token
SecurityClient.setMfaToken('');
SecurityClient.setToken(token);
const privateKey = await KeyService.decryptPrivateKey({
@ -77,6 +80,18 @@ const attemptLoginMfa = async ({
privateKey
});
// TODO: in the future - move this logic elsewhere
// because this function is about logging the user in
// and not initializing the login details
const userOrgs = await getOrganizations();
const orgId = userOrgs[0]._id;
localStorage.setItem('orgData.id', orgId);
const orgUserProjects = await getOrganizationUserProjects({
orgId
});
localStorage.setItem('projectData.id', orgUserProjects[0]._id);
resolve(true);
} catch (err) {
reject(err);

@ -1,6 +1,9 @@
import axios from 'axios';
import { getAuthToken } from '@app/reactQuery';
import {
getAuthToken,
getMfaTempToken,
getSignupTempToken} from '@app/reactQuery';
export const apiRequest = axios.create({
baseURL: '/',
@ -10,11 +13,17 @@ export const apiRequest = axios.create({
});
apiRequest.interceptors.request.use((config) => {
const signupTempToken = getSignupTempToken();
const mfaTempToken = getMfaTempToken();
const token = getAuthToken();
console.log('interceptors');
console.log('token', token);
console.log('config.headers', config.headers);
if (token && config.headers) {
if (signupTempToken && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${signupTempToken}`;
} else if (mfaTempToken && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${mfaTempToken}`;
} else if (token && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${token}`;
}

@ -1 +1,4 @@
export { useGetAuthToken } from './queries';
export {
useGetAuthToken,
useSendMfaToken,
useVerifyMfaToken} from './queries'

@ -1,14 +1,39 @@
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { apiRequest } from '@app/config/request';
import { setAuthToken } from '@app/reactQuery';
import { GetAuthTokenAPI } from './types';
import {
GetAuthTokenAPI,
SendMfaTokenDTO,
VerifyMfaTokenDTO,
VerifyMfaTokenRes} from './types';
const authKeys = {
getAuthToken: ['token'] as const
};
export const useSendMfaToken = () => {
return useMutation<{}, {}, SendMfaTokenDTO>({
mutationFn: async ({ email }) => {
const { data } = await apiRequest.post('/api/v2/auth/mfa/send', { email });
return data;
}
});
}
export const useVerifyMfaToken = () => {
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
mutationFn: async ({ email, mfaCode }) => {
const { data } = await apiRequest.post('/api/v2/auth/mfa/verify', {
email,
mfaToken: mfaCode
});
return data;
}
});
}
// Refresh token is set as cookie when logged in
// Using that we fetch the auth bearer token needed for auth calls
const fetchAuthToken = async () => {

@ -1,3 +1,24 @@
export type GetAuthTokenAPI = {
token: string;
};
export type SendMfaTokenDTO = {
email: string;
}
export type VerifyMfaTokenDTO = {
email: string;
mfaCode: string;
}
export type VerifyMfaTokenRes = {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}

@ -93,6 +93,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
// Placing the localstorage as much as possible
// Wait till tony integrates the azure and its launched
useEffect(() => {
// Put a user in a workspace if they're not in one yet
const putUserInWorkSpace = async () => {
if (tempLocalStorage('orgData.id') === '') {

@ -75,8 +75,18 @@ export const Navbar = () => {
const closeApp = async () => {
try {
console.log('Logging out...');
console.log('Logging out...')
await logout.mutateAsync();
localStorage.removeItem('protectedKey');
localStorage.removeItem('protectedKeyIV');
localStorage.removeItem('protectedKeyTag');
localStorage.removeItem('publicKey');
localStorage.removeItem('encryptedPrivateKey');
localStorage.removeItem('iv');
localStorage.removeItem('tag');
localStorage.removeItem('PRIVATE_KEY');
localStorage.removeItem('orgData.id');
localStorage.removeItem('projectData.id');
router.push('/login');
} catch (error) {
console.error(error);

@ -1,4 +1,5 @@
import SecurityClient from '@app/components/utilities/SecurityClient';
import { apiRequest } from "@app/config/request";
interface Props {
email: string;
@ -35,7 +36,7 @@ interface Props {
* @param {string} obj.verifier
* @returns
*/
const completeAccountInformationSignup = ({
const completeAccountInformationSignup = async ({
email,
firstName,
lastName,
@ -49,32 +50,24 @@ const completeAccountInformationSignup = ({
salt,
verifier,
organizationName
}: Props) => SecurityClient.fetchCall('/api/v2/signup/complete-account/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName
})
}).then(async (res) => {
if (res && res?.status === 200) {
return res.json();
}
console.log('Failed to verify MFA code');
throw new Error('Something went wrong during MFA code verification');
}: Props) => {
const { data } = await apiRequest.post('/api/v2/signup/complete-account/signup', {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName
});
return data;
}
export default completeAccountInformationSignup;

@ -1,3 +1,5 @@
import { apiRequest } from "@app/config/request";
interface Props {
email: string;
firstName: string;
@ -11,9 +13,12 @@ interface Props {
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
token: string;
}
// missing token?
// TODO: add to SecurityClient
/**
* This function is called in the end of the signup process.
* It sends all the necessary nformation to the server.
@ -30,7 +35,7 @@ interface Props {
* @param {string} obj.token - token that confirms a user's identity
* @returns
*/
const completeAccountInformationSignupInvite = ({
const completeAccountInformationSignupInvite = async ({
email,
firstName,
lastName,
@ -42,28 +47,24 @@ const completeAccountInformationSignupInvite = ({
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
token
}: Props) => fetch('/api/v2/signup/complete-account/invite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
})
verifier
}: Props) => {
const { data } = await apiRequest.post('/api/v2/signup/complete-account/invite', {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
});
return data;
}
export default completeAccountInformationSignupInvite;

@ -4,14 +4,16 @@ import SecurityClient from '@app/components/utilities/SecurityClient';
* This route logs the user out. Note: the user should authorized to do this.
* We first try to log out - if the authorization fails (response.status = 401), we refetch the new token, and then retry
*/
const logout = async () =>
SecurityClient.fetchCall('/api/v1/auth/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
}).then((res) => {
const logout = async () => {
try {
const res = await SecurityClient.fetchCall('/api/v1/auth/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
});
if (res?.status === 200) {
SecurityClient.setToken('');
// Delete the cookie by not setting a value; Alternatively clear the local storage
@ -23,12 +25,17 @@ const logout = async () =>
localStorage.removeItem('iv');
localStorage.removeItem('tag');
localStorage.removeItem('PRIVATE_KEY');
localStorage.removeItem('orgData.id');
localStorage.removeItem('projectData.id');
console.log('User logged out', res);
return res;
}
console.log('Failed to log out');
return undefined;
});
} catch (error) {
console.log('Error logging out', error);
}
return undefined;
};
export default logout;

@ -1,29 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* Send new MFA token to user with email [email]
* @param {object} obj
* @param {string} obj.email - email of user
* @returns
*/
const resendMfaToken = async ({
email,
}: {
email: string;
}) => SecurityClient.fetchCall('/api/v2/auth/mfa/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email
})
}).then(async (res) => {
if (res && res?.status === 200) {
return res.json();
}
console.log('Failed to send new MFA code');
throw new Error('Something went wrong while sending new MFA code');
});
export default resendMfaToken;

@ -1,4 +1,4 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
import { apiRequest } from "@app/config/request";
/**
* Verify MFA token [mfaToken] for user with email [email]
@ -13,23 +13,13 @@ const verifyMfaToken = async ({
}: {
email: string;
mfaToken: string;
}) => SecurityClient.fetchCall('/api/v2/auth/mfa/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
mfaToken
})
}).then(async (res) => {
if (res && res?.status === 200) {
return res.json();
}
console.log('Failed to verify MFA code');
throw new Error('Something went wrong during MFA code verification');
});
}) => {
const { data } = await apiRequest.post('/api/v2/auth/mfa/verify', {
email,
mfaToken
});
return data;
}
export default verifyMfaToken;

@ -17,7 +17,6 @@ export default function Login() {
const router = useRouter();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isAlreadyLoggedIn, setIsAlreadyLoggedIn] = useState(false);
const [step, setStep] = useState(1);
const { t } = useTranslation();
const lang = router.locale ?? 'en';
@ -41,7 +40,6 @@ export default function Login() {
}
};
if (isLoggedIn()) {
setIsAlreadyLoggedIn(true);
redirectToDashboard();
}
}, []);
@ -72,10 +70,6 @@ export default function Login() {
}
}
if (isAlreadyLoggedIn) {
return null
}
return (
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
<Head>
@ -106,4 +100,4 @@ export default function Login() {
);
}
export const getStaticProps = getTranslatedStaticProps(['auth', 'login']);
export const getStaticProps = getTranslatedStaticProps(['auth', 'login', 'signup']);

@ -59,7 +59,8 @@ export default function SignUp() {
// Checking if the code matches the email.
const response = await checkEmailVerificationCode({ email, code });
if (response.status === 200) {
SecurityClient.setToken((await response.json()).token);
const {token} = await response.json();
SecurityClient.setSignupToken(token);
setStep(3);
} else {
setCodeError(true);

@ -21,6 +21,10 @@ import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
import { deriveArgonKey } from '@app/components/utilities/cryptography/crypto';
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
import { saveTokenToLocalStorage } from '@app/components/utilities/saveTokenToLocalStorage';
import SecurityClient from '@app/components/utilities/SecurityClient';
import getOrganizations from '@app/pages/api/organization/getOrgs';
import getOrganizationUserProjects from '@app/pages/api/organization/GetOrgUserProjects';
import completeAccountInformationSignupInvite from './api/auth/CompleteAccountInformationSignupInvite';
import verifySignupInvite from './api/auth/VerifySignupInvite';
@ -29,7 +33,6 @@ import verifySignupInvite from './api/auth/VerifySignupInvite';
const client = new jsrp.client();
export default function SignupInvite() {
console.log('SignupInvite');
const [password, setPassword] = useState('');
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
@ -82,13 +85,6 @@ export default function SignupInvite() {
const privateKey = encodeBase64(secretKeyUint8Array);
const publicKey = encodeBase64(publicKeyUint8Array);
// const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
// text: PRIVATE_KEY,
// secret: password
// .slice(0, 32)
// .padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), '0')
// });
localStorage.setItem('PRIVATE_KEY', privateKey);
client.init(
@ -134,8 +130,9 @@ export default function SignupInvite() {
secret: Buffer.from(derivedKey.hash)
});
console.log('SignupInvite A');
let response = await completeAccountInformationSignupInvite({
const {
token: jwtToken
} = await completeAccountInformationSignupInvite({
email,
firstName,
lastName,
@ -147,24 +144,30 @@ export default function SignupInvite() {
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken
verifier: result.verifier
});
console.log('SignupInvite B');
// unset temporary signup JWT token and set JWT token
SecurityClient.setSignupToken('');
SecurityClient.setToken(jwtToken);
// if everything works, go the main dashboard page.
if (!errorCheck && response.status === 200) {
response = await response.json();
saveTokenToLocalStorage({
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
privateKey
});
console.log('SignupInvite C');
localStorage.setItem('publicKey', publicKey);
localStorage.setItem('encryptedPrivateKey', encryptedPrivateKey);
localStorage.setItem('iv', encryptedPrivateKeyIV);
localStorage.setItem('tag', encryptedPrivateKeyTag);
console.log('SignupInvite D');
const userOrgs = await getOrganizations();
setStep(3);
}
const orgId = userOrgs[0]._id;
localStorage.setItem('orgData.id', orgId);
setStep(3);
} catch (error) {
setIsLoading(false);
console.error(error);
@ -197,7 +200,7 @@ export default function SignupInvite() {
// user will have temp token if doesn't have an account
// then continue with account setup workflow
if (res?.token) {
setVerificationToken(res.token);
SecurityClient.setSignupToken(res.token);
setStep(2);
} else {
// user will be redirected to dashboard

@ -1,6 +1,7 @@
import { QueryClient } from '@tanstack/react-query';
// this is saved in react-query cache
export const SIGNUP_TEMP_TOKEN_CACHE_KEY = ['infisical__signup-temp-token'];
export const MFA_TEMP_TOKEN_CACHE_KEY = ['infisical__mfa-temp-token'];
export const AUTH_TOKEN_CACHE_KEY = ['infisical__auth-token'];
@ -14,12 +15,16 @@ export const queryClient = new QueryClient({
});
// set token in memory cache
export const setSignupTempToken = (token: string) =>
queryClient.setQueryData(SIGNUP_TEMP_TOKEN_CACHE_KEY, token);
export const setMfaTempToken = (token: string) =>
queryClient.setQueryData(MFA_TEMP_TOKEN_CACHE_KEY, token);
export const setAuthToken = (token: string) =>
queryClient.setQueryData(AUTH_TOKEN_CACHE_KEY, token);
export const getSignupTempToken = () => queryClient.getQueryData(SIGNUP_TEMP_TOKEN_CACHE_KEY) as string;
export const getMfaTempToken = () => queryClient.getQueryData(MFA_TEMP_TOKEN_CACHE_KEY) as string;
export const getAuthToken = () => queryClient.getQueryData(AUTH_TOKEN_CACHE_KEY) as string;