fix: resolved backup key generation in signup, removed owner check in custom role and login race condition

This commit is contained in:
Akhil Mohan
2023-11-16 13:28:30 +05:30
parent 94f893017b
commit 6734ce50a5
3 changed files with 144 additions and 148 deletions

View File

@@ -1,24 +1,16 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import {
Membership,
MembershipOrg,
Workspace
} from "../../models";
import { Membership, MembershipOrg, Workspace } from "../../models";
import { Role } from "../../ee/models";
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
import {
import {
createOrganization as create,
deleteOrganization,
updateSubscriptionOrgQuantity
} from "../../helpers/organization";
import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import {
ACCEPTED,
ADMIN,
CUSTOM
} from "../../variables";
import { ACCEPTED, ADMIN, CUSTOM } from "../../variables";
import * as reqValidator from "../../validation/organization";
import { validateRequest } from "../../helpers/validation";
import {
@@ -155,7 +147,7 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
OrgPermissionSubjects.Member
);
const isCustomRole = !["admin", "member", "owner"].includes(role);
const isCustomRole = !["admin", "member"].includes(role);
if (isCustomRole) {
const orgRole = await Role.findOne({ slug: role, isOrgRole: true });
if (!orgRole) throw BadRequestError({ message: "Role not found" });
@@ -336,7 +328,7 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
* @param res
* @returns
*/
export const createOrganization = async (req: Request, res: Response) => {
export const createOrganization = async (req: Request, res: Response) => {
const {
body: { name }
} = await validateRequest(reqValidator.CreateOrgv2, req);
@@ -361,27 +353,27 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
/**
* Delete organization with id [organizationId]
* @param req
* @param res
* @param req
* @param res
*/
export const deleteOrganizationById = async (req: Request, res: Response) => {
const {
params: { organizationId }
} = await validateRequest(reqValidator.DeleteOrgv2, req);
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: new Types.ObjectId(organizationId),
role: ADMIN
});
if (!membershipOrg) throw UnauthorizedRequestError();
const organization = await deleteOrganization({
organizationId: new Types.ObjectId(organizationId)
});
return res.status(200).send({
organization
});
}
};

View File

@@ -2,7 +2,11 @@ import { useTranslation } from "react-i18next";
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import issueBackupKey from "../utilities/cryptography/issueBackupKey";
import { useToggle } from "@app/hooks";
import { generateUserBackupKey } from "@app/lib/crypto";
import { useNotificationContext } from "../context/Notifications/NotificationProvider";
import { generateBackupPDFAsync } from "../utilities/generateBackupPDF";
import { Button } from "../v2";
interface DownloadBackupPDFStepProps {
@@ -28,35 +32,56 @@ export default function DonwloadBackupPDFStep({
name
}: DownloadBackupPDFStepProps): JSX.Element {
const { t } = useTranslation();
const { createNotification } = useNotificationContext();
const [isLoading, setIsLoading] = useToggle();
const handleBackupKeyGenerate = async () => {
try {
setIsLoading.on();
const generatedKey = await generateUserBackupKey(email, password);
await generateBackupPDFAsync({
generatedKey,
personalEmail: email,
personalName: name
});
incrementStep();
} catch (err) {
console.log(err);
createNotification({
type: "error",
text: "Faield to generate backup key"
});
} finally {
setIsLoading.off();
}
};
return (
<div className="flex flex-col items-center w-full h-full md:px-6 mx-auto mb-36 md:mb-16">
<p className="text-xl text-center font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 text-2xl text-bunker-200" />{t("signup.step4-message")}
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 text-2xl text-bunker-200" />
{t("signup.step4-message")}
</p>
<div className="flex flex-col pb-2 bg-mineshaft-800 border border-mineshaft-600 items-center justify-center text-center lg:w-1/6 w-full md:min-w-[24rem] mt-8 max-w-md text-bunker-300 text-md rounded-md">
<div className="w-full mt-4 md:mt-8 flex flex-row text-center items-center m-2 text-bunker-300 rounded-md lg:w-1/6 lg:w-1/6 w-full md:min-w-[23rem] px-3 mx-auto">
<span className='mb-2'>{t("signup.step4-description1")} {t("signup.step4-description3")}</span>
<span className="mb-2">
{t("signup.step4-description1")} {t("signup.step4-description3")}
</span>
</div>
<div className="flex flex-col items-center px-3 justify-center mt-0 md:mt-4 mb-2 md:mb-4 lg:w-1/6 w-full md:min-w-[20rem] mt-2 md:max-w-md mx-auto text-sm text-center md:text-left">
<div className="text-l py-1 text-lg w-full">
<Button
onClick={async () => {
await issueBackupKey({
email,
password,
personalName: name,
setBackupKeyError: () => { },
setBackupKeyIssued: () => { }
});
incrementStep();
}}
onClick={handleBackupKeyGenerate}
size="sm"
isFullWidth
className='h-12'
isLoading={isLoading}
isDisabled={isLoading}
className="h-12"
colorSchema="primary"
variant="outline_bg"
> Download PDF </Button>
>
Download PDF
</Button>
</div>
</div>
</div>

View File

@@ -8,9 +8,6 @@ import Telemetry from "./telemetry/Telemetry";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
import SecurityClient from "./SecurityClient";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
interface IsLoginSuccessful {
mfaEnabled: boolean;
success: boolean;
@@ -22,118 +19,100 @@ interface IsLoginSuccessful {
* @param {string} email - email of user to log in
* @param {string} password - password of user to log in
*/
const attemptLogin = async (
{
email,
password,
providerAuthToken,
}: {
email: string;
password: string;
providerAuthToken?: string;
}
): Promise<IsLoginSuccessful> => {
const attemptLogin = async ({
email,
password,
providerAuthToken
}: {
email: string;
password: string;
providerAuthToken?: string;
}): Promise<IsLoginSuccessful> => {
const telemetry = new Telemetry().getInstance();
return new Promise((resolve, reject) => {
client.init(
{
username: email,
password
},
async () => {
try {
const clientPublicKey = client.getPublicKey();
const { serverPublicKey, salt } = await login1({
email,
clientPublicKey,
providerAuthToken,
});
client.setSalt(salt);
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
const {
mfaEnabled,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await login2(
{
email,
clientProof,
providerAuthToken,
}
);
if (mfaEnabled) {
// case: MFA is enabled
// set temporary (MFA) JWT token
SecurityClient.setMfaToken(token);
resolve({
mfaEnabled,
success: true
});
} else if (
!mfaEnabled &&
encryptionVersion &&
encryptedPrivateKey &&
iv &&
tag &&
token
) {
// case: MFA is not enabled
// unset provider auth token in case it was used
SecurityClient.setProviderAuthToken("");
// set JWT token
SecurityClient.setToken(token);
const privateKey = await KeyService.decryptPrivateKey({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
if (email) {
telemetry.identify(email, email);
telemetry.capture("User Logged In");
}
resolve({
mfaEnabled: false,
success: true
});
}
} catch (err) {
reject(err);
}
}
);
// eslint-disable-next-line new-cap
const client = new jsrp.client();
await new Promise((resolve) => {
client.init({ username: email, password }, () => resolve(null));
});
const clientPublicKey = client.getPublicKey();
const { serverPublicKey, salt } = await login1({
email,
clientPublicKey,
providerAuthToken
});
client.setSalt(salt);
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
const {
mfaEnabled,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await login2({
email,
clientProof,
providerAuthToken
});
if (mfaEnabled) {
// case: MFA is enabled
// set temporary (MFA) JWT token
SecurityClient.setMfaToken(token);
return {
mfaEnabled,
success: true
};
}
if (!mfaEnabled && encryptionVersion && encryptedPrivateKey && iv && tag && token) {
// case: MFA is not enabled
// unset provider auth token in case it was used
SecurityClient.setProviderAuthToken("");
// set JWT token
SecurityClient.setToken(token);
const privateKey = await KeyService.decryptPrivateKey({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
if (email) {
telemetry.identify(email, email);
telemetry.capture("User Logged In");
}
return {
mfaEnabled: false,
success: true
};
}
return { success: false, mfaEnabled: false };
};
export default attemptLogin;