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
+5 -4
View File
@@ -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);
+14 -5
View File
@@ -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}`;
}
+4 -1
View File
@@ -1 +1,4 @@
export { useGetAuthToken } from './queries';
export {
useGetAuthToken,
useSendMfaToken,
useVerifyMfaToken} from './queries'
+27 -2
View File
@@ -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 () => {
+21
View File
@@ -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;
+19 -12
View File
@@ -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;
+9 -19
View File
@@ -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;
+1 -7
View File
@@ -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']);
+2 -1
View File
@@ -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);
+28 -25
View File
@@ -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
+5
View File
@@ -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;