Merge pull request #838 from Infisical/deprecation

Cleaning & deprecating parts of code
This commit is contained in:
BlackMagiq
2023-08-11 12:10:19 +07:00
committed by GitHub
234 changed files with 1511 additions and 6337 deletions

View File

@@ -260,22 +260,6 @@ export const createOrganizationPortalSession = async (
}
};
/**
* Return organization subscriptions
* @param req
* @param res
* @returns
*/
export const getOrganizationSubscriptions = async (
req: Request,
res: Response
) => {
return res.status(200).send({
subscriptions: []
});
};
/**
* Given a org id, return the projects each member of the org belongs to
* @param req

View File

@@ -1,7 +1,7 @@
import { Types } from "mongoose";
import { Request, Response } from "express";
import { ISecret, Secret, ServiceTokenData } from "../../models";
import { IAction, SecretVersion, EventType, AuditLog } from "../../ee/models";
import { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
@@ -14,7 +14,7 @@ import {
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EventService } from "../../services";
import { eventPushSecrets } from "../../events";
import { EELogService, EESecretService, EEAuditLogService } from "../../ee/services";
import { EEAuditLogService, EELogService, EESecretService } from "../../ee/services";
import { SecretService, TelemetryService } from "../../services";
import { getUserAgentType } from "../../utils/posthog";
import { PERMISSION_WRITE_SECRETS } from "../../variables";

View File

@@ -16,7 +16,7 @@ type ValidEventScope =
| Required<EventScope>
export default class EEAuditLogService {
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope, shouldSave: boolean = true) {
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope, shouldSave = true) {
const MS_IN_DAY = 24 * 60 * 60 * 1000;

View File

@@ -8,7 +8,7 @@ import { AuthMode } from "../../variables";
router.post("/token", validateRequest, authController.getNewToken);
router.post( // deprecated (moved to api/v2/auth/login1)
router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1)
"/login1",
authLimiter,
body("email").exists().trim().notEmpty(),
@@ -17,7 +17,7 @@ router.post( // deprecated (moved to api/v2/auth/login1)
authController.login1
);
router.post( // deprecated (moved to api/v2/auth/login2)
router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login2)
"/login2",
authLimiter,
body("email").exists().trim().notEmpty(),
@@ -49,7 +49,7 @@ router.get(
authController.getCommonPasswords
);
router.delete(
router.delete( // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
"/sessions",
authLimiter,
requireAuth({

View File

@@ -5,6 +5,8 @@ import { requireAuth, validateRequest } from "../../middleware";
import { membershipOrgController } from "../../controllers/v1";
import { AuthMode } from "../../variables";
// TODO endpoint: consider moving these endpoints to be under /organization to be more RESTful
router.post(
"/signup",
requireAuth({

View File

@@ -9,6 +9,8 @@ import { body, param } from "express-validator";
import { ADMIN, AuthMode, MEMBER } from "../../variables";
import { keyController } from "../../controllers/v1";
// TODO endpoint: consider moving these endpoints to be under /workspaces to be more RESTful
router.post(
"/:workspaceId",
requireAuth({
@@ -24,7 +26,7 @@ router.post(
keyController.uploadKey
);
router.get(
router.get( // TODO endpoint: deprecate (note: move frontend to v2/workspace/key or something)
"/:workspaceId/latest",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],

View File

@@ -7,8 +7,9 @@ import { membershipController as EEMembershipControllers } from "../../ee/contro
import { AuthMode } from "../../variables";
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
// TODO endpoint: consider moving these endpoints to be under /workspace to be more RESTful
router.get( // used for old CLI (deprecate)
router.get( // TODO endpoint: deprecate - used for old CLI (deprecate)
"/:workspaceId/connect",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -18,7 +19,7 @@ router.get( // used for old CLI (deprecate)
membershipController.validateMembership
);
router.delete(
router.delete( // TODO endpoint: check dashboard
"/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -28,7 +29,7 @@ router.delete(
membershipController.deleteMembership
);
router.post(
router.post( // TODO endpoint: check dashboard
"/:membershipId/change-role",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -38,7 +39,7 @@ router.post(
membershipController.changeMembershipRole
);
router.post(
router.post( // TODO endpoint: check dashboard
"/:membershipId/deny-permissions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],

View File

@@ -5,8 +5,7 @@ import { requireAuth, validateRequest } from "../../middleware";
import { membershipOrgController } from "../../controllers/v1";
import { AuthMode } from "../../variables";
router.post(
// TODO
router.post( // TODO endpoint: check dashboard
"/membershipOrg/:membershipOrgId/change-role",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -17,7 +16,7 @@ router.post(
);
router.delete(
"/:membershipOrgId",
"/:membershipOrgId", // TODO endpoint: check dashboard
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),

View File

@@ -15,7 +15,7 @@ import {
} from "../../variables";
import { organizationController } from "../../controllers/v1";
router.get( // deprecated (moved to api/v2/users/me/organizations)
router.get( // TODO endpoint: deprecate (moved to api/v2/users/me/organizations)
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -47,7 +47,7 @@ router.get(
organizationController.getOrganization
);
router.get( // deprecated (moved to api/v2/organizations/:organizationId/memberships)
router.get( // TODO endpoint: deprecate (moved to api/v2/organizations/:organizationId/memberships)
"/:organizationId/users",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -61,7 +61,7 @@ router.get( // deprecated (moved to api/v2/organizations/:organizationId/members
organizationController.getOrganizationMembers
);
router.get(
router.get( // TODO endpoint: move to /v2/users/me/organizations/:organizationId/workspaces
"/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -135,7 +135,7 @@ router.delete(
);
router.post(
"/:organizationId/customer-portal-session",
"/:organizationId/customer-portal-session", // TODO endpoint: move to EE
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
@@ -148,20 +148,6 @@ router.post(
organizationController.createOrganizationPortalSession
);
router.get(
"/:organizationId/subscriptions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationSubscriptions
);
router.get(
"/:organizationId/workspace-memberships",
requireAuth({

View File

@@ -14,9 +14,9 @@ import {
MEMBER
} from "../../variables";
// note to devs: these endpoints will be deprecated in favor of v2
// note: endpoints deprecated in favor of v3/secrets
router.post(
router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -34,7 +34,7 @@ router.post(
secretController.pushSecrets
);
router.get(
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -50,7 +50,7 @@ router.get(
secretController.pullSecrets
);
router.get(
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
"/:workspaceId/service-token",
requireServiceTokenAuth,
query("environment").exists().trim(),

View File

@@ -16,13 +16,13 @@ import { serviceTokenController } from "../../controllers/v1";
// note: deprecate service-token routes in favor of service-token data routes/structure
router.get(
router.get( // TODO endpoint: deprecate
"/",
requireServiceTokenAuth,
serviceTokenController.getServiceToken
);
router.post(
router.post( // TODO endpoint: deprecate
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],

View File

@@ -5,7 +5,9 @@ import { validateRequest } from "../../middleware";
import { signupController } from "../../controllers/v1";
import { authLimiter } from "../../helpers/rateLimiter";
router.post(
// TODO: consider moving to users/v3/signup
router.post( // TODO endpoint: consider moving to v3/users/signup/mail
"/email/signup",
authLimiter,
body("email").exists().trim().notEmpty().isEmail(),
@@ -14,7 +16,7 @@ router.post(
);
router.post(
"/email/verify",
"/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify
authLimiter,
body("email").exists().trim().notEmpty().isEmail(),
body("code").exists().trim().notEmpty(),
@@ -22,4 +24,4 @@ router.post(
signupController.verifyEmailSignup
);
export default router;
export default router;

View File

@@ -4,7 +4,7 @@ import { requireAuth } from "../../middleware";
import { userController } from "../../controllers/v1";
import { AuthMode } from "../../variables";
router.get(
router.get( // TODO endpoint: deprecate (moved to v2/users/me)
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],

View File

@@ -6,7 +6,7 @@ import { userActionController } from "../../controllers/v1";
import { AuthMode } from "../../variables";
// note: [userAction] will be deprecated in /v2 in favor of [action]
router.post(
router.post( // TODO endpoint: move this into /users/me
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],

View File

@@ -147,7 +147,7 @@ router.get(
);
router.get(
"/:workspaceId/service-tokens", // deprecate
"/:workspaceId/service-tokens", // TODO endpoint: deprecate
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),

View File

@@ -5,7 +5,7 @@ import { requireMfaAuth, validateRequest } from "../../middleware";
import { authController } from "../../controllers/v2";
import { authLimiter } from "../../helpers/rateLimiter";
router.post(
router.post( // TODO: deprecate (moved to api/v3/auth/login1)
"/login1",
authLimiter,
body("email").isString().trim().notEmpty(),
@@ -14,7 +14,7 @@ router.post(
authController.login1
);
router.post(
router.post( // TODO: deprecate (moved to api/v3/auth/login1)
"/login2",
authLimiter,
body("email").isString().trim().notEmpty(),

View File

@@ -85,7 +85,7 @@ router.get(
organizationsController.getOrganizationWorkspaces
);
router.get(
router.get( // TODO endpoint: deprecate service accounts
"/:organizationId/service-accounts",
param("organizationId").exists().trim(),
validateRequest,

View File

@@ -1,4 +1,5 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireSecretAuth,
@@ -16,11 +17,9 @@ import {
import { CreateSecretRequestBody, ModifySecretRequestBody } from "../../types/secret";
import { secretController } from "../../controllers/v2";
// note to devs: stop supporting these routes [deprecated]
// note: endpoints deprecated in favor of v3/secrets
const router = express.Router();
router.post(
router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
"/batch-create/workspace/:workspaceId/environment/:environment",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -38,7 +37,7 @@ router.post(
);
router.post(
"/workspace/:workspaceId/environment/:environment",
"/workspace/:workspaceId/environment/:environment", // TODO endpoint: deprecate (moved to POST api/v3/secrets)
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
@@ -54,7 +53,7 @@ router.post(
secretController.createSecret
);
router.get(
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
"/workspace/:workspaceId",
param("workspaceId").exists().trim(),
query("environment").exists(),
@@ -70,7 +69,7 @@ router.get(
secretController.getSecrets
);
router.get(
router.get( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
"/:secretId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN],
@@ -83,7 +82,7 @@ router.get(
secretController.getSecret
);
router.delete(
router.delete( // TODO endpoint: deprecate (moved to DELETE api/v3/secrets)
"/batch/workspace/:workspaceId/environment/:environmentName",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -99,7 +98,7 @@ router.delete(
secretController.deleteSecrets
);
router.delete(
router.delete( // TODO endpoint: deprecate (moved to DELETE api/v3/secrets)
"/:secretId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -113,7 +112,7 @@ router.delete(
secretController.deleteSecret
);
router.patch(
router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
"/batch-modify/workspace/:workspaceId/environment/:environmentName",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -129,7 +128,7 @@ router.patch(
secretController.updateSecrets
);
router.patch(
router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
"/workspace/:workspaceId/environment/:environmentName",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],

View File

@@ -21,7 +21,7 @@ import {
} from "../../variables";
import { BatchSecretRequest } from "../../types/secret";
router.post(
router.post( // TODO endpoint: strongly consider deprecation in favor of a single operation experience on dashboard
"/batch",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
@@ -56,7 +56,7 @@ router.post(
secretsController.batchSecrets
);
router.post(
router.post( // TODO endpoint: deprecate (moved to POST api/v3/secrets)
"/",
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
@@ -117,7 +117,7 @@ router.post(
secretsController.createSecrets
);
router.get(
router.get( // TODO endpoint: deprecate (moved to GET api/v3/secrets)
"/",
query("workspaceId").exists().trim(),
query("environment").exists().trim(),
@@ -138,7 +138,7 @@ router.get(
secretsController.getSecrets
);
router.patch(
router.patch( // TODO endpoint: deprecate (moved to PATCH api/v3/secrets)
"/",
body("secrets")
.exists()
@@ -173,7 +173,7 @@ router.patch(
secretsController.updateSecrets
);
router.delete(
router.delete( // TODO endpoint: deprecate (moved to DELETE api/v3/secrets)
"/",
body("secretIds")
.exists()

View File

@@ -1,5 +1,8 @@
import express from "express";
const router = express.Router();
// TODO endpoint: deprecate all
// import {
// requireAuth,
// requireOrganizationAuth,

View File

@@ -6,7 +6,7 @@ import { signupController } from "../../controllers/v2";
import { authLimiter } from "../../helpers/rateLimiter";
router.post(
"/complete-account/signup",
"/complete-account/signup", // TODO endpoint: deprecate (moved to v3/signup/complete/account-signup)
authLimiter,
requireSignupAuth,
body("email").exists().isString().trim().notEmpty().isEmail(),
@@ -27,7 +27,7 @@ router.post(
);
router.post(
"/complete-account/invite",
"/complete-account/invite", // TODO: consider moving to v3/users/new/complete-account/invite
authLimiter,
requireSignupAuth,
body("email").exists().isString().trim().notEmpty().isEmail(),

View File

@@ -14,7 +14,7 @@ import {
} from "../../variables";
import { workspaceController } from "../../controllers/v2";
router.post(
router.post( // TODO endpoint: deprecate (moved to POST v3/secrets)
"/:workspaceId/secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
@@ -32,7 +32,7 @@ router.post(
workspaceController.pushWorkspaceSecrets
);
router.get(
router.get( // TODO endpoint: deprecate (moved to GET v3/secrets)
"/:workspaceId/secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN],
@@ -48,7 +48,7 @@ router.get(
workspaceController.pullSecrets
);
router.get(
router.get( // TODO endpoint: consider moving to v3/users/me/workspaces/:workspaceId/key
"/:workspaceId/encrypted-key",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],

View File

@@ -6,7 +6,7 @@ import { authLimiter } from "../../helpers/rateLimiter";
import { validateRequest } from "../../middleware";
router.post(
"/complete-account/signup",
"/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup
authLimiter,
body("email").exists().isString().trim().notEmpty().isEmail(),
body("firstName").exists().isString().trim().notEmpty(),

View File

@@ -1,99 +0,0 @@
import { Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import { Dialog, Transition } from "@headlessui/react";
import addIncidentContact from "@app/pages/api/organization/addIncidentContact";
import Button from "../buttons/Button";
import InputField from "../InputField";
type Props = {
isOpen: boolean;
closeModal: () => void;
incidentContacts: string[];
setIncidentContacts: (arg: string[]) => void;
};
const AddIncidentContactDialog = ({
isOpen,
closeModal,
incidentContacts,
setIncidentContacts
}: Props) => {
const [incidentContactEmail, setIncidentContactEmail] = useState("");
const { t } = useTranslation();
const submit = () => {
setIncidentContacts(
incidentContacts?.length > 0
? incidentContacts.concat([incidentContactEmail])
: [incidentContactEmail]
);
addIncidentContact(localStorage.getItem("orgData.id") as string, incidentContactEmail);
closeModal();
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
{t("section.incident.add-dialog.title")}
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
{t("section.incident.add-dialog.description")}
</p>
</div>
<div className="max-h-28">
<InputField
label={t("common.email")}
onChangeHandler={setIncidentContactEmail}
type="varName"
value={incidentContactEmail}
placeholder=""
isRequired
/>
</div>
<div className="mt-6 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text={t("section.incident.add-dialog.add-incident") as string}
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddIncidentContactDialog;

View File

@@ -1,271 +0,0 @@
import crypto from "crypto";
import { Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Dialog, Transition } from "@headlessui/react";
import addServiceToken from "@app/pages/api/serviceToken/addServiceToken";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
import { decryptAssymmetric, encryptSymmetric } from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
import InputField from "../InputField";
import ListBox from "../Listbox";
const expiryMapping = {
"1 day": 86400,
"7 days": 604800,
"1 month": 2592000,
"6 months": 15552000,
"12 months": 31104000
};
type Props = {
isOpen: boolean;
closeModal: () => void;
workspaceId: string;
workspaceName: string;
serviceTokens: any[];
environments: Array<{ name: string; slug: string }>;
setServiceTokens: (arg: any[]) => void;
};
const AddServiceTokenDialog = ({
isOpen,
closeModal,
workspaceId,
workspaceName,
serviceTokens,
environments,
setServiceTokens
}: Props) => {
const [serviceToken, setServiceToken] = useState("");
const [serviceTokenName, setServiceTokenName] = useState("");
const [selectedServiceTokenEnv, setSelectedServiceTokenEnv] = useState(environments?.[0]);
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const { t } = useTranslation();
const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey({ workspaceId });
const key = decryptAssymmetric({
ciphertext: latestFileKey.latestKey.encryptedKey,
nonce: latestFileKey.latestKey.nonce,
publicKey: latestFileKey.latestKey.sender.publicKey,
privateKey: localStorage.getItem("PRIVATE_KEY") as string
});
const randomBytes = crypto.randomBytes(16).toString("hex");
const { ciphertext, iv, tag } = encryptSymmetric({
plaintext: key,
key: randomBytes
});
console.log(
1234,
selectedServiceTokenEnv,
environments,
selectedServiceTokenEnv?.slug ? selectedServiceTokenEnv.slug : environments[0]?.slug
);
const newServiceToken = await addServiceToken({
name: serviceTokenName,
workspaceId,
environment: selectedServiceTokenEnv?.slug
? selectedServiceTokenEnv.slug
: environments[0]?.slug,
expiresIn: expiryMapping[serviceTokenExpiresIn as keyof typeof expiryMapping],
encryptedKey: ciphertext,
iv,
tag
});
setServiceTokens(serviceTokens.concat([newServiceToken.serviceTokenData]));
setServiceToken(`${newServiceToken.serviceToken}.${randomBytes}`);
};
function copyToClipboard() {
// Get the text field
const copyText = document.getElementById("serviceToken") as HTMLInputElement;
// Select the text field
copyText.select();
copyText.setSelectionRange(0, 99999); // For mobile devices
// Copy the text inside the text field
navigator.clipboard.writeText(copyText.value);
setServiceTokenCopied(true);
setTimeout(() => setServiceTokenCopied(false), 2000);
// Alert the copied text
// alert("Copied the text: " + copyText.value);
}
const closeAddServiceTokenModal = () => {
closeModal();
setServiceTokenName("");
setServiceToken("");
};
return (
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
{serviceToken === "" ? (
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="z-50 text-lg font-medium leading-6 text-gray-400"
>
{t("section.token.add-dialog.title", {
target: workspaceName
})}
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
{t("section.token.add-dialog.description")}
</p>
</div>
</div>
<div className="mb-2 max-h-28">
<InputField
label={t("section.token.add-dialog.name")}
onChangeHandler={setServiceTokenName}
type="varName"
value={serviceTokenName}
placeholder=""
isRequired
/>
</div>
<div className="mb-2 max-h-28">
<ListBox
isSelected={
selectedServiceTokenEnv?.name
? selectedServiceTokenEnv?.name
: environments[0]?.name
}
data={environments.map(({ name }) => name)}
onChange={(envName) =>
setSelectedServiceTokenEnv(
environments.find(({ name }) => envName === name) || {
name: "unknown",
slug: "unknown"
}
)
}
isFull
text={`${t("common.environment")}: `}
/>
</div>
<div className="max-h-28">
<ListBox
isSelected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn}
data={["1 day", "7 days", "1 month", "6 months", "12 months"]}
isFull
text={`${t("common.expired-in")}: `}
/>
</div>
<div className="max-w-max">
<div className="mt-6 flex w-max flex-col justify-start">
<Button
onButtonPressed={() => generateServiceToken()}
color="mineshaft"
text={t("section.token.add-dialog.add") as string}
textDisabled={t("section.token.add-dialog.add") as string}
size="md"
active={serviceTokenName !== ""}
/>
</div>
</div>
</Dialog.Panel>
) : (
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="z-50 text-lg font-medium leading-6 text-gray-400"
>
{t("section.token.add-dialog.copy-service-token")}
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
{t("section.token.add-dialog.copy-service-token-description")}
</p>
</div>
</div>
<div className="w-full">
<div className="mt-2 mr-2 flex h-20 w-full items-center justify-end rounded-md bg-white/[0.07] text-base text-gray-400">
<input
type="text"
value={serviceToken}
id="serviceToken"
className="invisible w-full min-w-full bg-white/0 py-2 px-2 text-gray-400 outline-none"
/>
<div className="w-full max-w-md break-words bg-white/0 py-2 pl-14 pr-2 text-sm text-gray-400 outline-none">
{serviceToken}
</div>
<div className="group relative inline-block h-full font-normal text-gray-400 underline duration-200 hover:text-primary">
<button
onClick={copyToClipboard}
type="button"
className="h-full border-l border-white/20 py-2 pl-3.5 pr-4 duration-200 hover:bg-white/[0.12]"
>
{serviceTokenCopied ? (
<FontAwesomeIcon icon={faCheck} className="pr-0.5" />
) : (
<FontAwesomeIcon icon={faCopy} />
)}
</button>
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-chicago-900 px-3 py-2 text-center text-sm text-gray-400 duration-300 group-hover:flex group-hover:animate-popup">
{t("common.click-to-copy")}
</span>
</div>
</div>
</div>
<div className="mt-6 flex w-max flex-col justify-start">
<Button
onButtonPressed={() => closeAddServiceTokenModal()}
color="mineshaft"
text="Close"
size="md"
/>
</div>
</Dialog.Panel>
)}
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddServiceTokenDialog;

View File

@@ -1,184 +0,0 @@
import { useEffect, useState } from "react";
import { faPencil, faPlus, faX } from "@fortawesome/free-solid-svg-icons";
import { plans } from "public/data/frequentConstants";
import { usePopUp } from "../../../hooks/usePopUp";
import getOrganizationSubscriptions from "../../../pages/api/organization/GetOrgSubscription";
import Button from "../buttons/Button";
import { AddUpdateEnvironmentDialog } from "../dialog/AddUpdateEnvironmentDialog";
import DeleteActionModal from "../dialog/DeleteActionModal";
import UpgradePlanModal from "../dialog/UpgradePlan";
type Env = { name: string; slug: string };
type Props = {
data: Env[];
onCreateEnv: (arg0: Env) => Promise<void>;
onUpdateEnv: (oldSlug: string, arg0: Env) => Promise<void>;
onDeleteEnv: (slug: string) => Promise<void>;
};
const EnvironmentTable = ({ data = [], onCreateEnv, onDeleteEnv, onUpdateEnv }: Props) => {
const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"createUpdateEnv",
"deleteEnv",
"upgradePlan"
] as const);
const [plan, setPlan] = useState("");
const host = window.location.origin;
useEffect(() => {
// on initial load - run auth check
(async () => {
const orgId = localStorage.getItem("orgData.id") as string;
const subscriptions = await getOrganizationSubscriptions({
orgId
});
if (subscriptions) {
setPlan(subscriptions.data[0].plan.product)
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onEnvCreateCB = async (env: Env) => {
try {
await onCreateEnv(env);
handlePopUpClose("createUpdateEnv");
} catch (error) {
console.error(error);
}
};
const onEnvUpdateCB = async (env: Env) => {
try {
await onUpdateEnv((popUp.createUpdateEnv?.data as Pick<Env, "slug">)?.slug, env);
handlePopUpClose("createUpdateEnv");
} catch (error) {
console.error(error);
}
};
const onEnvDeleteCB = async () => {
try {
await onDeleteEnv((popUp.deleteEnv?.data as Pick<Env, "slug">)?.slug);
handlePopUpClose("deleteEnv");
} catch (error) {
console.error(error);
}
};
return (
<>
<div className="flex w-full flex-row justify-between">
<div className="flex w-full flex-col">
<p className="mb-3 text-xl font-semibold">Project Environments</p>
<p className="mb-4 text-base text-gray-400">
Choose which environments will show up in your dashboard like development, staging,
production
</p>
<p className="mr-1 self-start text-sm text-gray-500">
Note: the text in slugs shows how these environmant should be accessed in CLI.
</p>
</div>
<div className="w-48">
<Button
text="Add New Environment"
onButtonPressed={() => {
if (plan !== plans.starter || host !== "https://app.infisical.com" || localStorage.getItem("projectData.id") === "63ea8121b6e2b0543ba79616") {
handlePopUpOpen("createUpdateEnv");
} else {
handlePopUpOpen("upgradePlan");
}
}}
color="mineshaft"
icon={faPlus}
size="md"
/>
</div>
</div>
<div className="table-container relative mb-6 mt-1 w-full rounded-md border border-mineshaft-700 bg-bunker">
<div className="absolute h-12 w-full rounded-t-md bg-white/5" />
<table className="my-1 w-full">
<thead className="text-bunker-300">
<tr>
<th className="pl-6 pt-2.5 pb-2 text-left">Name</th>
<th className="pl-6 pt-2.5 pb-2 text-left">Slug</th>
<th aria-label="buttons" />
</tr>
</thead>
<tbody>
{data?.length > 0 ? (
data.map(({ name, slug }) => (
<tr key={name} className="bg-bunker-800 duration-100 hover:bg-bunker-800/5">
<td className="border-t border-mineshaft-700 py-2 pl-6 text-gray-300">{name}</td>
<td className="border-t border-mineshaft-700 py-2 pl-6 text-gray-300">{slug}</td>
<td className="flex justify-end border-t border-mineshaft-700 py-2">
<div className="mr-2 flex items-center duration-200 hover:opacity-100">
<Button
onButtonPressed={() => {
if (plan !== plans.starter || host !== "https://app.infisical.com") {
handlePopUpOpen("createUpdateEnv", { name, slug });
} else {
handlePopUpOpen("upgradePlan");
}
}}
color="mineshaft"
size="icon-sm"
icon={faPencil}
/>
</div>
<div className="mr-6 flex items-center opacity-50 duration-200 hover:opacity-100">
<Button
onButtonPressed={() => {
if (plan !== plans.starter || host !== "https://app.infisical.com") {
handlePopUpOpen("deleteEnv", { name, slug });
} else {
handlePopUpOpen("upgradePlan");
}
}}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
</td>
</tr>
))
) : (
<tr>
<td colSpan={4} className="pt-7 pb-4 text-center text-bunker-400">
No environments found
</td>
</tr>
)}
</tbody>
</table>
<DeleteActionModal
isOpen={popUp.deleteEnv.isOpen}
title={`Are you sure want to delete ${
(popUp?.deleteEnv?.data as { name: string })?.name || " "
}?`}
deleteKey={(popUp?.deleteEnv?.data as { slug: string })?.slug || ""}
onClose={() => handlePopUpClose("deleteEnv")}
onSubmit={onEnvDeleteCB}
/>
<AddUpdateEnvironmentDialog
isOpen={popUp.createUpdateEnv.isOpen}
isEditMode={Boolean(popUp.createUpdateEnv?.data)}
initialValues={popUp?.createUpdateEnv?.data as any}
onClose={() => handlePopUpClose("createUpdateEnv")}
onCreateSubmit={onEnvCreateCB}
onEditSubmit={onEnvUpdateCB}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onClose={() => handlePopUpClose("upgradePlan")}
text="You can add custom environments if you switch to Infisical's Team plan."
/>
</div>
</>
);
};
export default EnvironmentTable;

View File

@@ -4,13 +4,13 @@ import { faEye, faEyeSlash, faPenToSquare, faPlus, faX } from "@fortawesome/free
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { Select, SelectItem } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { useSubscription, useWorkspace } from "@app/context";
import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission";
import changeUserRoleInWorkspace from "@app/pages/api/workspace/changeUserRoleInWorkspace";
import deleteUserFromWorkspace from "@app/pages/api/workspace/deleteUserFromWorkspace";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
import getProjectInfo from "@app/pages/api/workspace/getProjectInfo";
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
import {
useDeleteUserFromWorkspace,
useGetUserWsKey,
useUpdateUserWorkspaceRole,
useUploadWsKey} from "@app/hooks/api";
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
import guidGenerator from "../../utilities/randomId";
@@ -39,10 +39,16 @@ type EnvironmentProps = {
* @returns
*/
const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => {
const { currentWorkspace } = useWorkspace();
const { subscription } = useSubscription();
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
const { mutateAsync: deleteUserFromWorkspaceMutateAsync } = useDeleteUserFromWorkspace();
const { mutateAsync: uploadWsKeyMutateAsync } = useUploadWsKey();
const { mutateAsync: updateUserWorkspaceRoleMutateAsync } = useUpdateUserWorkspaceRole();
// const [roleSelected, setRoleSelected] = useState(
// Array(userData?.length).fill(userData.map((user) => user.role))
// );
const router = useRouter();
const [myRole, setMyRole] = useState("member");
const [workspaceEnvs, setWorkspaceEnvs] = useState<EnvironmentProps[]>([]);
@@ -52,38 +58,15 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
const workspaceId = router.query.id as string;
// Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
const handleDelete = (membershipId: string, index: number) => {
// setUserIdToBeDeleted(userId);
// onClick();
deleteUserFromWorkspace(membershipId);
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length)
]);
const handleDelete = async (membershipId: string) => {
await deleteUserFromWorkspaceMutateAsync(membershipId);
};
// Update the rold of a certain user
const handleRoleUpdate = (index: number, e: string) => {
changeUserRoleInWorkspace(userData[index].membershipId, e.toLowerCase());
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: e.toLocaleLowerCase(),
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
deniedPermissions: userData[index].deniedPermissions
}
],
...userData.slice(index + 1, userData?.length)
]);
const handleRoleUpdate = async (index: number, e: string) => {
await updateUserWorkspaceRoleMutateAsync({
membershipId: userData[index].membershipId,
role: e.toLowerCase()
});
createNotification({
text: "Successfully changed user role.",
type: "success"
@@ -163,32 +146,38 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
useEffect(() => {
setMyRole(userData.filter((user) => user.email === myUser)[0]?.role);
(async () => {
const result = await getProjectInfo({ projectId: workspaceId });
setWorkspaceEnvs(result.environments);
if (currentWorkspace) {
setWorkspaceEnvs(currentWorkspace.environments);
}
})();
}, [userData, myUser]);
}, [userData, myUser, currentWorkspace]);
const grantAccess = async (id: string, publicKey: string) => {
const result = await getLatestFileKey({ workspaceId });
if (wsKey) {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: wsKey.encryptedKey,
nonce: wsKey.nonce,
publicKey: wsKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey: PRIVATE_KEY
});
uploadKeys(workspaceId, id, ciphertext, nonce);
router.reload();
await uploadWsKeyMutateAsync({
workspaceId,
userId: id,
encryptedKey: ciphertext,
nonce
});
router.reload();
}
};
const closeUpgradeModal = () => {
@@ -372,7 +361,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
myRole !== "member" ? (
<div className="mt-0.5 flex items-center opacity-50 hover:opacity-100">
<Button
onButtonPressed={() => handleDelete(row.membershipId, index)}
onButtonPressed={() => handleDelete(row.membershipId)}
color="red"
size="icon-sm"
icon={faX}

View File

@@ -1,98 +0,0 @@
import { faX } from "@fortawesome/free-solid-svg-icons";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken";
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
interface TokenProps {
_id: string;
name: string;
environment: string;
expiresAt: string;
}
interface ServiceTokensProps {
data: TokenProps[];
workspaceName: string;
setServiceTokens: (value: TokenProps[]) => void;
}
/**
* This is the component that we utilize for the service token table
* #TODO: add the possibility of choosing and doing operations on multiple users.
* @param {object} obj
* @param {any[]} obj.data - current state of the service token table
* @param {string} obj.workspaceName - name of the current project
* @param {function} obj.setServiceTokens - updating the state of the service token table
* @returns
*/
const ServiceTokenTable = ({ data, workspaceName, setServiceTokens }: ServiceTokensProps) => {
const { createNotification } = useNotificationContext();
return (
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="absolute rounded-t-md w-full h-12 bg-white/5" />
<table className="w-full my-1">
<thead className="text-bunker-300 text-sm font-light">
<tr>
<th className="text-left pl-6 pt-2.5 pb-2">TOKEN NAME</th>
<th className="text-left pl-6 pt-2.5 pb-2">PROJECT</th>
<th className="text-left pl-6 pt-2.5 pb-2">ENVIRONMENT</th>
<th className="text-left pl-6 pt-2.5 pb-2">VAILD UNTIL</th>
<th aria-label="button" />
</tr>
</thead>
<tbody>
{data?.length > 0 ? (
data?.map((row) => (
<tr
key={guidGenerator()}
className="bg-bunker-800 hover:bg-bunker-800/5 duration-100"
>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.name}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{workspaceName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.environment}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{new Date(row.expiresAt).toUTCString()}
</td>
<td className="py-2 border-mineshaft-700 border-t">
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
<Button
onButtonPressed={() => {
deleteServiceToken({ serviceTokenId: row._id });
setServiceTokens(data.filter((token) => token._id !== row._id));
createNotification({
text: `'${row.name}' token has been revoked.`,
type: "error"
});
}}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
</td>
</tr>
))
) : (
<tr>
<td colSpan={4} className="text-center pt-7 pb-5 text-bunker-300 text-sm">
No service tokens yet
</td>
</tr>
)}
</tbody>
</table>
</div>
);
};
export default ServiceTokenTable;

View File

@@ -1,234 +0,0 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faX } from "@fortawesome/free-solid-svg-icons";
import changeUserRoleInOrganization from "@app/pages/api/organization/changeUserRoleInOrganization";
import deleteUserFromOrganization from "@app/pages/api/organization/deleteUserFromOrganization";
import getOrganizationProjectMemberships from "@app/pages/api/organization/GetOrgProjectMemberships";
import deleteUserFromWorkspace from "@app/pages/api/workspace/deleteUserFromWorkspace";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
import guidGenerator from "../../utilities/randomId";
import Button from "../buttons/Button";
import Listbox from "../Listbox";
// const roles = ['admin', 'user'];
// TODO: Set type for this
type Props = {
userData: any[];
changeData: (users: any[]) => void;
myUser: string;
filter: string;
resendInvite: (email: string) => void;
isOrg: boolean;
};
/**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
* #TODO: add the possibility of choosing and doing operations on multiple users.
* @param {*} props
* @returns
*/
const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }: Props) => {
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const router = useRouter();
const [myRole, setMyRole] = useState("member");
const [userProjectMemberships, setUserProjectMemberships] = useState<any[]>([]);
const workspaceId = router.query.id as string;
// Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
const handleDelete = (membershipId: string, index: number) => {
// setUserIdToBeDeleted(userId);
// onClick();
if (isOrg) {
deleteUserFromOrganization(membershipId);
} else {
deleteUserFromWorkspace(membershipId);
}
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length)
]);
};
// Update the role of a certain user
const handleRoleUpdate = (index: number, e: string) => {
changeUserRoleInOrganization(String(localStorage.getItem("orgData.id")), userData[index].membershipId, e);
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: e,
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey
}
],
...userData.slice(index + 1, userData?.length)
]);
};
useEffect(() => {
setMyRole(userData.filter((user) => user.email === myUser)[0]?.role);
(async () => {
const result = await getOrganizationProjectMemberships({ orgId: String(localStorage.getItem("orgData.id"))})
setUserProjectMemberships(result);
})();
}, [userData, myUser]);
const grantAccess = async (id: string, publicKey: string) => {
const result = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey: PRIVATE_KEY
});
uploadKeys(workspaceId, id, ciphertext, nonce);
router.reload();
};
const deleteMembershipAndResendInvite = (email: string) => {
// deleteUserFromWorkspace(membershipId);
resendInvite(email);
};
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1 min-w-max w-full">
<div className="absolute rounded-t-md w-full h-[3.25rem] bg-white/5" />
<table className="w-full my-0.5">
<thead className="text-gray-400 text-sm font-light">
<tr>
<th className="text-left pl-4 py-3.5">NAME</th>
<th className="text-left pl-4 py-3.5">EMAIL</th>
<th className="text-left pl-6 pr-10 py-3.5">ROLE</th>
<th className="text-left pl-6 pr-10 py-3.5">PROJECTS</th>
<th aria-label="buttons" />
</tr>
</thead>
<tbody>
{userData?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
).length > 0 &&
userData
?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
)
.map((row, index) => (
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-700">
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName} {row.lastName}
</td>
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.email}
</td>
<td className="pl-6 pr-10 py-2 border-mineshaft-700 border-t text-gray-300">
<div className="justify-start h-full flex flex-row items-center">
{row.status === "accepted" &&
((myRole === "admin" && row.role !== "owner") || myRole === "owner") &&
(myUser !== row.email) ? (
<Listbox
isSelected={row.role}
onChange={(e) => handleRoleUpdate(index, e)}
data={
myRole === "owner" ? ["owner", "admin", "member"] : ["admin", "member"]
}
/>
) : (
row.status !== "invited" &&
row.status !== "verified" && (
<Listbox
isSelected={row.role}
onChange={() => {
throw new Error("Function not implemented.");
}}
data={null}
/>
)
)}
{(row.status === "invited" || row.status === "verified") && (
<div className="w-full pr-20">
<Button
onButtonPressed={() => deleteMembershipAndResendInvite(row.email)}
color="mineshaft"
text="Resend Invite"
size="md"
/>
</div>
)}
{row.status === "completed" && myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<Button
onButtonPressed={() => grantAccess(row.userId, row.publicKey)}
color="mineshaft"
text="Grant Access"
size="md"
/>
</div>
)}
</div>
</td>
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
<div className="flex items-center max-h-16 overflow-x-auto w-full max-w-xl break-all">
{userProjectMemberships[row.userId]
? userProjectMemberships[row.userId]?.map((project: any) => (
<div key={project._id} className='mx-1 min-w-max px-1.5 bg-mineshaft-500 rounded-sm text-sm text-bunker-200 flex items-center'>
<span className='mb-0.5 cursor-default'>{project.name}</span>
</div>
))
: <span className='ml-1 text-bunker-100 rounded-sm px-1 py-0.5 text-sm bg-red/80'>This user isn&apos;t part of any projects yet.</span>}
</div>
</td>
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
{myUser !== row.email &&
// row.role !== "admin" &&
myRole !== "member" ? (
<div className="opacity-50 hover:opacity-100 flex items-center mt-0.5">
<Button
onButtonPressed={() => handleDelete(row.membershipId, index)}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
) : (
<div className="w-9 h-9" />
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default UserTable;

View File

@@ -1,88 +0,0 @@
import { SetStateAction, useEffect, useState } from "react";
import Image from "next/image";
import { WorkspaceEnv } from "@app/hooks/api/types";
import getSecretsForProject from "../utilities/secrets/getSecretsForProject";
import { Modal, ModalContent } from "../v2";
interface Secrets {
label: string;
secret: string;
}
interface CompareSecretsModalProps {
compareModal: boolean;
setCompareModal: React.Dispatch<SetStateAction<boolean>>;
selectedEnv: WorkspaceEnv;
workspaceEnvs: WorkspaceEnv[];
workspaceId: string;
currentSecret: {
key: string;
value: string;
};
}
const CompareSecretsModal = ({
compareModal,
setCompareModal,
selectedEnv,
workspaceEnvs,
workspaceId,
currentSecret
}: CompareSecretsModalProps) => {
const [secrets, setSecrets] = useState<Secrets[]>([]);
const getEnvSecrets = async () => {
const workspaceEnvironments = workspaceEnvs?.filter((env) => env !== selectedEnv);
const newSecrets = await Promise.all(
workspaceEnvironments.map(async (env) => {
// #TODO: optimize this query somehow...
const allSecrets = await getSecretsForProject({ env: env.slug, workspaceId });
const secret =
allSecrets.find((item) => item.key === currentSecret.key)?.value ?? "Not found";
return { label: env.name, secret };
})
);
setSecrets([{ label: selectedEnv.name, secret: currentSecret.value }, ...newSecrets]);
};
useEffect(() => {
if (compareModal) {
(async () => {
await getEnvSecrets();
})();
}
}, [compareModal]);
return (
<Modal isOpen={compareModal} onOpenChange={setCompareModal}>
<ModalContent title={currentSecret?.key} onOpenAutoFocus={(e) => e.preventDefault()}>
<div className="space-y-4">
{secrets.length === 0 ? (
<div className="flex items-center bg-bunker-900 justify-center h-full py-4 rounded-md">
<Image
src="/images/loading/loading.gif"
height={60}
width={100}
alt="infisical loading indicator"
/>
</div>
) : (
secrets.map((item) => (
<div key={`${currentSecret.key}${item.label}`} className="space-y-0.5">
<p className="text-sm text-bunker-300">{item.label}</p>
<input
defaultValue={item.secret}
className="h-no-capture border border-mineshaft-500 text-md min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full rounded-md bg-bunker-800 px-2 py-1.5 font-mono text-gray-400 caret-white outline-none duration-200 no-scrollbar focus:ring-2 focus:ring-primary/50 "
readOnly
/>
</div>
))
)}
</div>
</ModalContent>
</Modal>
);
};
export default CompareSecretsModal;

View File

@@ -1,222 +0,0 @@
/* eslint-disable react/no-unused-prop-types */
import { useState } from "react";
import { useTranslation } from "react-i18next";
import Image from "next/image";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import SecretVersionList from "@app/ee/components/SecretVersionList";
import { WorkspaceEnv } from "@app/hooks/api/types";
import Button from "../basic/buttons/Button";
import Toggle from "../basic/Toggle";
import CommentField from "./CommentField";
import CompareSecretsModal from "./CompareSecretsModal";
import DashboardInputField from "./DashboardInputField";
import { DeleteActionButton } from "./DeleteActionButton";
import GenerateSecretMenu from "./GenerateSecretMenu";
interface SecretProps {
key: string;
value: string | undefined;
valueOverride: string | undefined;
pos: number;
id: string;
comment: string;
}
export interface DeleteRowFunctionProps {
ids: string[];
secretName: string;
}
interface SideBarProps {
toggleSidebar: (value: string) => void;
data: SecretProps[];
modifyKey: (value: string, id: string) => void;
modifyValue: (value: string, id: string) => void;
modifyValueOverride: (value: string | undefined, id: string) => void;
modifyComment: (value: string, id: string) => void;
buttonReady: boolean;
savePush: () => void;
sharedToHide: string[];
setSharedToHide: (values: string[]) => void;
deleteRow: (props: DeleteRowFunctionProps) => void;
workspaceEnvs: WorkspaceEnv[];
selectedEnv: WorkspaceEnv;
workspaceId: string;
}
/**
* @param {object} obj
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
* @param {SecretProps[]} obj.data - data of a certain key valeu pair
* @param {function} obj.modifyKey - function that modifies the secret key
* @param {function} obj.modifyValue - function that modifies the secret value
* @param {function} obj.modifyValueOverride - function that modifies the secret value if it is an override
* @param {boolean} obj.buttonReady - is the button for saving chagnes active
* @param {function} obj.savePush - save changes andp ush secrets
* @param {function} obj.deleteRow - a function to delete a certain keyPair
* @returns the sidebar with 'secret's settings'
*/
const SideBar = ({
toggleSidebar,
data,
modifyKey,
modifyValue,
modifyValueOverride,
modifyComment,
buttonReady,
savePush,
deleteRow,
workspaceEnvs,
selectedEnv,
workspaceId
}: SideBarProps) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isLoading, setIsLoading] = useState(false);
const [overrideEnabled, setOverrideEnabled] = useState(data[0]?.valueOverride !== undefined);
const [compareModal, setCompareModal] = useState(false);
const { t } = useTranslation();
return (
<div className="min-w-sm absolute sticky top-0 right-0 z-[70] flex h-full w-full max-w-sm flex-col justify-between border-l border-mineshaft-500 bg-bunker shadow-xl">
{isLoading ? (
<div className="flex h-full w-full items-center justify-center">
<Image
src="/images/loading/loading.gif"
height={60}
width={100}
alt="infisical loading indicator"
/>
</div>
) : (
<div className="h-min w-full overflow-y-auto">
<div className="flex flex-row items-center justify-between border-b border-mineshaft-500 px-4 py-3">
<p className="text-lg font-semibold text-bunker-200">{t("dashboard.sidebar.secret")}</p>
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
className="p-1"
onClick={() => toggleSidebar("None")}
>
<FontAwesomeIcon icon={faXmark} className="h-4 w-4 cursor-pointer text-bunker-300" />
</div>
</div>
<div className="pointer-events-none mt-4 px-4">
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.key")}</p>
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
<DashboardInputField
onChangeHandler={modifyKey}
type="varName"
id={data[0]?.id}
value={data[0]?.key}
isDuplicate={false}
blurred={false}
/>
</div>
</div>
{data[0]?.value || data[0]?.value === "" ? (
<div
className={`relative mt-2 px-4 ${
overrideEnabled && "pointer-events-none opacity-40"
} duration-200`}
>
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.value")}</p>
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
<DashboardInputField
onChangeHandler={modifyValue}
type="value"
id={data[0].id}
value={data[0]?.value}
isDuplicate={false}
blurred
/>
</div>
<div className="absolute right-[1.07rem] top-[1.6rem] z-50 bg-bunker-800">
<GenerateSecretMenu modifyValue={modifyValue} id={data[0]?.id} />
</div>
</div>
) : (
<div className="px-4 pt-4 text-sm text-bunker-300">
<span className="mr-1 rounded-md bg-primary-200/10 py-0.5 px-1">
{t("common.note")}:
</span>
{t("dashboard.sidebar.personal-explanation")}
</div>
)}
<div className="mt-4 px-4">
{(data[0]?.value || data[0]?.value === "") && (
<div className="my-2 flex flex-row items-center justify-between pl-1 pr-2">
<p className="text-sm text-bunker-300">{t("dashboard.sidebar.override")}</p>
<Toggle
enabled={overrideEnabled}
setEnabled={setOverrideEnabled}
addOverride={modifyValueOverride}
id={data[0]?.id}
/>
</div>
)}
<div
className={`relative ${
!overrideEnabled && "pointer-events-none opacity-40"
} duration-200`}
>
<div className="overflow-hidden rounded-md border border-mineshaft-600 bg-white/5">
<DashboardInputField
onChangeHandler={modifyValueOverride}
type="value"
id={data[0]?.id}
value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value}
isDuplicate={false}
blurred
/>
</div>
<div className="absolute right-[0.57rem] top-[0.3rem] z-50">
<GenerateSecretMenu modifyValue={modifyValueOverride} id={data[0]?.id} />
</div>
</div>
</div>
<SecretVersionList secretId={data[0]?.id} />
<CommentField comment={data[0]?.comment} modifyComment={modifyComment} id={data[0]?.id} />
</div>
)}
<div className="mt-full mt-4 mb-4 flex w-96 max-w-sm flex-col justify-start space-y-2 px-4">
<div>
<Button
text="Compare secret across environments"
color="mineshaft"
size="md"
onButtonPressed={() => setCompareModal(true)}
/>
<CompareSecretsModal
compareModal={compareModal}
setCompareModal={setCompareModal}
currentSecret={{ key: data[0]?.key, value: data[0]?.value ?? "" }}
workspaceEnvs={workspaceEnvs}
selectedEnv={selectedEnv}
workspaceId={workspaceId}
/>
</div>
<div className="flex">
<Button
text={String(t("common.save-changes"))}
onButtonPressed={savePush}
color="primary"
size="md"
active={buttonReady}
textDisabled="Saved"
/>
<DeleteActionButton
onSubmit={() =>
deleteRow({ ids: data.map((secret) => secret.id), secretName: data[0]?.key })
}
/>
</div>
</div>
</div>
);
};
export default SideBar;

View File

@@ -1,122 +0,0 @@
import Image from "next/image";
import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import deleteIntegrationAuth from "../../pages/api/integrations/DeleteIntegrationAuth";
interface IntegrationOption {
clientId: string;
clientSlug?: string; // vercel-integration specific
docsLink: string;
image: string;
isAvailable: boolean;
name: string;
slug: string;
type: string;
}
interface IntegrationAuth {
_id: string;
integration: string;
workspace: string;
createdAt: string;
updatedAt: string;
}
interface Props {
cloudIntegrationOption: IntegrationOption;
setSelectedIntegrationOption: (cloudIntegration: IntegrationOption) => void;
integrationOptionPress: (cloudIntegrationOption: IntegrationOption) => void;
integrationAuths: IntegrationAuth[];
handleDeleteIntegrationAuth: (args: { integrationAuth: IntegrationAuth }) => void;
}
const CloudIntegration = ({
cloudIntegrationOption,
setSelectedIntegrationOption,
integrationOptionPress,
integrationAuths,
handleDeleteIntegrationAuth
}: Props) => {
return integrationAuths ? (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
className={`relative ${
cloudIntegrationOption.isAvailable
? "cursor-pointer duration-200 hover:bg-mineshaft-700"
: "opacity-50"
} flex h-32 flex-row items-center rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4`}
onClick={() => {
if (!cloudIntegrationOption.isAvailable) return;
setSelectedIntegrationOption(cloudIntegrationOption);
integrationOptionPress(cloudIntegrationOption);
}}
key={cloudIntegrationOption.name}
>
<Image
src={`/images/integrations/${cloudIntegrationOption.image}`}
height={70}
width={70}
alt="integration logo"
/>
{cloudIntegrationOption.name.split(" ").length > 2 ? (
<div className="ml-4 max-w-xs text-3xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
<div>{cloudIntegrationOption.name.split(" ")[0]}</div>
<div className="text-base">
{cloudIntegrationOption.name.split(" ")[1]} {cloudIntegrationOption.name.split(" ")[2]}
</div>
</div>
) : (
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
{cloudIntegrationOption.name}
</div>
)}
{cloudIntegrationOption.isAvailable &&
integrationAuths
.map((authorization) => authorization?.integration)
.includes(cloudIntegrationOption.slug) && (
<div className="group absolute top-0 right-0 z-40 flex flex-row">
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={async (event) => {
event.stopPropagation();
const deletedIntegrationAuth = await deleteIntegrationAuth({
integrationAuthId: integrationAuths
.filter(
(authorization) => authorization.integration === cloudIntegrationOption.slug
)
.map((authorization) => authorization._id)[0]
});
handleDeleteIntegrationAuth({
integrationAuth: deletedIntegrationAuth
});
}}
className="flex w-max cursor-pointer flex-row items-center rounded-bl-md bg-red py-0.5 px-2 text-xs opacity-30 duration-200 group-hover:opacity-100"
>
<FontAwesomeIcon icon={faXmark} className="mr-2 text-xs" />
Revoke
</div>
<div className="flex w-max flex-row items-center rounded-tr-md bg-primary py-0.5 px-2 text-xs text-black opacity-70 duration-200 group-hover:opacity-100">
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
Authorized
</div>
</div>
)}
{!cloudIntegrationOption.isAvailable && (
<div className="group absolute top-0 right-0 z-50 flex flex-row">
<div className="flex w-max flex-row items-center rounded-bl-md rounded-tr-md bg-yellow py-0.5 px-2 text-xs text-black opacity-90">
Coming Soon
</div>
</div>
)}
</div>
) : (
<div />
);
};
export default CloudIntegration;

View File

@@ -1,63 +0,0 @@
import { useTranslation } from "react-i18next";
import CloudIntegration from "./CloudIntegration";
interface IntegrationOption {
clientId: string;
clientSlug?: string; // vercel-integration specific
docsLink: string;
image: string;
isAvailable: boolean;
name: string;
slug: string;
type: string;
}
interface IntegrationAuth {
_id: string;
integration: string;
workspace: string;
createdAt: string;
updatedAt: string;
}
interface Props {
cloudIntegrationOptions: IntegrationOption[];
setSelectedIntegrationOption: () => void;
integrationOptionPress: (integrationOption: IntegrationOption) => void;
integrationAuths: IntegrationAuth[];
handleDeleteIntegrationAuth: (args: { integrationAuth: IntegrationAuth }) => void;
}
const CloudIntegrationSection = ({
cloudIntegrationOptions,
setSelectedIntegrationOption,
integrationOptionPress,
integrationAuths,
handleDeleteIntegrationAuth
}: Props) => {
const { t } = useTranslation();
return (
<>
<div className="m-4 mt-7 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
<h1 className="text-3xl font-semibold">{t("integrations.cloud-integrations")}</h1>
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
</div>
<div className="mx-6 grid max-w-5xl grid-cols-4 grid-rows-2 gap-4">
{cloudIntegrationOptions.map((cloudIntegrationOption) => (
<CloudIntegration
cloudIntegrationOption={cloudIntegrationOption}
setSelectedIntegrationOption={setSelectedIntegrationOption}
integrationOptionPress={integrationOptionPress}
integrationAuths={integrationAuths}
handleDeleteIntegrationAuth={handleDeleteIntegrationAuth}
key={`cloud-integration-${cloudIntegrationOption.slug}`}
/>
))}
</div>
</>
);
};
export default CloudIntegrationSection;

View File

@@ -1,36 +0,0 @@
import Image from "next/image";
interface Framework {
name: string;
slug: string;
image: string;
docsLink: string;
}
const FrameworkIntegration = ({ framework }: { framework: Framework }) => (
<a
href={framework.docsLink}
rel="noopener noreferrer"
target="_blank"
className="relative flex flex-row justify-center duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer"
>
<div
className={`hover:bg-mineshaft-700 cursor-pointer font-semibold bg-mineshaft-800 border border-mineshaft-600 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${
framework?.name?.split(" ").length > 1 ? "text-sm px-1" : "text-xl px-2"
} text-center w-full max-w-xs`}
>
{framework?.image && (
<Image
src={`/images/integrations/${framework.image}.png`}
height={framework?.name ? 60 : 90}
width={framework?.name ? 60 : 90}
alt="integration logo"
/>
)}
{framework?.name && framework?.image && <div className="h-2" />}
{framework?.name && framework.name}
</div>
</a>
);
export default FrameworkIntegration;

View File

@@ -1,38 +0,0 @@
import { useTranslation } from "react-i18next";
import FrameworkIntegration from "./FrameworkIntegration";
interface Framework {
name: string;
image: string;
link: string;
slug: string;
docsLink: string;
}
interface Props {
frameworks: [Framework];
}
const FrameworkIntegrationSection = ({ frameworks }: Props) => {
const { t } = useTranslation();
return (
<>
<div className="mx-4 mt-12 mb-4 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
<h1 className="text-3xl font-semibold">{t("integrations.framework-integrations")}</h1>
<p className="text-base text-gray-400">{t("integrations.click-to-setup")}</p>
</div>
<div className="mx-6 mt-4 grid max-w-5xl grid-cols-7 grid-rows-2 gap-4">
{frameworks.map((framework) => (
<FrameworkIntegration
framework={framework}
key={`framework-integration-${framework.slug}`}
/>
))}
</div>
</>
);
};
export default FrameworkIntegrationSection;

View File

@@ -1,313 +0,0 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faArrowRight, faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// TODO: This needs to be moved from public folder
import {
contextNetlifyMapping,
integrationSlugNameMapping,
reverseContextNetlifyMapping
} from "public/data/frequentConstants";
import Button from "@app/components/basic/buttons/Button";
import ListBox from "@app/components/basic/Listbox";
import deleteIntegration from "../../pages/api/integrations/DeleteIntegration";
import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps";
import updateIntegration from "../../pages/api/integrations/updateIntegration";
interface Integration {
_id: string;
isActive: boolean;
app: string | null;
appId: string | null;
path: string | null;
region: string | null;
createdAt: string;
updatedAt: string;
environment: string;
integration: string;
targetEnvironment: string;
workspace: string;
integrationAuth: string;
secretPath: string;
}
interface IntegrationApp {
name: string;
appId?: string;
owner?: string;
}
type Props = {
integration: Integration;
integrations: Integration[];
setIntegrations: any;
bot: any;
setBot: any;
environments: Array<{ name: string; slug: string }>;
handleDeleteIntegration: (args: { integration: Integration }) => void;
};
// TODO: refactor
const IntegrationTile = ({
integration,
integrations,
bot,
setBot,
setIntegrations,
environments = [],
handleDeleteIntegration
}: Props) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState<Props["environments"][0]>(
environments.find(({ slug }) => slug === integration?.environment) || {
name: "",
slug: ""
}
);
const router = useRouter();
const [apps, setApps] = useState<IntegrationApp[]>([]); // integration app objects
const [integrationApp, setIntegrationApp] = useState(""); // integration app name
const [integrationTargetEnvironment, setIntegrationTargetEnvironment] = useState("");
useEffect(() => {
const loadIntegration = async () => {
const tempApps: [IntegrationApp] = await getIntegrationApps({
integrationAuthId: integration?.integrationAuth
});
setApps(tempApps);
if (integration?.app) {
setIntegrationApp(integration.app);
} else if (integration?.path && integration?.region) {
setIntegrationApp(`${integration.path} (${integration.region})`);
} else if (tempApps.length > 0) {
setIntegrationApp(tempApps[0].name);
} else {
setIntegrationApp("");
}
switch (integration.integration) {
case "vercel":
setIntegrationTargetEnvironment(
integration?.targetEnvironment
? integration.targetEnvironment.charAt(0).toUpperCase() +
integration.targetEnvironment.substring(1)
: "Development"
);
break;
case "netlify":
setIntegrationTargetEnvironment(
integration?.targetEnvironment
? contextNetlifyMapping[integration.targetEnvironment]
: "Local development"
);
break;
default:
break;
}
};
loadIntegration();
}, []);
const handleStartIntegration = async () => {
const reformatTargetEnvironment = (targetEnvironment: string) => {
switch (integration.integration) {
case "vercel":
return targetEnvironment.toLowerCase();
case "netlify":
return reverseContextNetlifyMapping[targetEnvironment];
default:
return null;
}
};
try {
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
const appId = siteApp?.appId ?? null;
const owner = siteApp?.owner ?? null;
// return updated integration
const updatedIntegration = await updateIntegration({
integrationId: integration._id,
environment: integrationEnvironment.slug,
isActive: true,
app: integrationApp,
appId,
targetEnvironment: reformatTargetEnvironment(integrationTargetEnvironment),
owner
});
setIntegrations(
integrations.map((i) => (i._id === updatedIntegration._id ? updatedIntegration : i))
);
} catch (err) {
console.error(err);
}
};
// eslint-disable-next-line @typescript-eslint/no-shadow
const renderIntegrationSpecificParams = (integration: Integration) => {
try {
switch (integration.integration) {
case "vercel":
return (
<div>
<div className="mb-2 w-60 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
<ListBox
data={!integration.isActive ? ["Development", "Preview", "Production"] : null}
isSelected={integrationTargetEnvironment}
onChange={setIntegrationTargetEnvironment}
isFull
/>
</div>
);
case "netlify":
return (
<div>
<div className="mb-2 text-xs font-semibold text-gray-400">CONTEXT</div>
<ListBox
data={
!integration.isActive
? ["Production", "Deploy previews", "Branch deploys", "Local development"]
: null
}
isSelected={integrationTargetEnvironment}
onChange={setIntegrationTargetEnvironment}
/>
</div>
);
case "railway":
return (
<div>
<div className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
<ListBox
data={
!integration.isActive
? ["Production", "Deploy previews", "Branch deploys", "Local development"]
: null
}
isSelected={integration.targetEnvironment}
onChange={setIntegrationTargetEnvironment}
/>
</div>
);
case "gitlab":
return (
<div>
<div className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
<ListBox
data={null}
isSelected={integration.targetEnvironment}
onChange={setIntegrationTargetEnvironment}
/>
</div>
);
default:
return <div />;
}
} catch (err) {
console.error(err);
}
return <div />;
};
if (!integrationApp && integration.integration !== "checkly") return <div />;
const isSelected =
integration.integration === "hashicorp-vault"
? `${integration.app} - path: ${integration.path}`
: integrationApp;
return (
<div className="mx-6 mb-8 flex max-w-5xl justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6">
<div className="flex">
<div>
<p className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</p>
<ListBox
data={!integration.isActive ? environments.map(({ name }) => name) : null}
isSelected={integrationEnvironment.name}
onChange={(envName) =>
setIntegrationEnvironment(
environments.find(({ name }) => envName === name) || {
name: "unknown",
slug: "unknown"
}
)
}
isFull
/>
</div>
<div className="ml-2">
<p className="mb-2 text-xs font-semibold text-gray-400">SECRET PATH</p>
<div className="cursor-default rounded-md bg-white/[.07] py-2.5 pl-4 pr-10 text-sm font-semibold text-gray-300">
{/* {integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)} */}
{integration.secretPath}
</div>
</div>
<div className="pt-2">
<FontAwesomeIcon icon={faArrowRight} className="mx-4 mt-8 text-gray-400" />
</div>
<div className="mr-2">
<p className="mb-2 text-xs font-semibold text-gray-400">INTEGRATION</p>
<div className="cursor-default rounded-md bg-white/[.07] py-2.5 pl-4 pr-10 text-sm font-semibold text-gray-300">
{/* {integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)} */}
{integrationSlugNameMapping[integration.integration]}
</div>
</div>
<div className="mr-2">
<div className="mb-2 text-xs font-semibold text-gray-400">APP</div>
{integrationApp ? (
<div title={integrationApp}>
<ListBox
data={!integration.isActive ? apps.map((app) => app.name) : null}
isSelected={isSelected}
onChange={(app) => {
setIntegrationApp(app);
}}
/>
</div>
) : (
<div className="h-10 w-52 animate-pulse rounded-md bg-mineshaft-600 px-4 py-2 font-bold">
-
</div>
)}
</div>
{renderIntegrationSpecificParams(integration)}
</div>
<div className="flex cursor-default items-end">
{integration.isActive ? (
<div className="flex max-w-5xl flex-row items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 p-[0.44rem] px-4">
<FontAwesomeIcon icon={faCheck} className="mr-2.5 text-lg text-primary" />
<div className="font-semibold text-gray-300">In Sync</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={() => handleStartIntegration()}
color="mineshaft"
size="md"
/>
)}
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
<Button
onButtonPressed={() =>
handleDeleteIntegration({
integration
})
}
color="red"
size="icon-md"
icon={faXmark}
/>
</div>
</div>
</div>
);
};
export default IntegrationTile;

View File

@@ -1,63 +0,0 @@
import IntegrationTile from "./Integration";
interface Props {
integrations: any;
setIntegrations: any;
bot: any;
setBot: any;
environments: Array<{ name: string; slug: string }>;
handleDeleteIntegration: (args: { integration: Integration }) => void;
}
interface Integration {
_id: string;
isActive: boolean;
app: string | null;
appId: string | null;
path: string | null;
region: string | null;
createdAt: string;
updatedAt: string;
environment: string;
integration: string;
targetEnvironment: string;
workspace: string;
integrationAuth: string;
secretPath: string;
}
const ProjectIntegrationSection = ({
integrations,
setIntegrations,
bot,
setBot,
environments = [],
handleDeleteIntegration
}: Props) => {
return integrations.length > 0 ? (
<div className="mb-12">
<div className="mx-4 mb-4 mt-6 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
<h1 className="text-3xl font-semibold">Current Integrations</h1>
<p className="text-base text-bunker-300">Manage integrations with third-party services.</p>
</div>
{integrations.map((integration: Integration) => {
return (
<IntegrationTile
key={`integration-${integration?._id.toString()}`}
integration={integration}
integrations={integrations}
bot={bot}
setBot={setBot}
setIntegrations={setIntegrations}
environments={environments}
handleDeleteIntegration={handleDeleteIntegration}
/>
);
})}
</div>
) : (
<div />
);
};
export default ProjectIntegrationSection;

View File

@@ -3,7 +3,9 @@ import React, { useState } from "react";
import ReactCodeInput from "react-code-input";
import { useTranslation } from "react-i18next";
import sendVerificationEmail from "@app/pages/api/auth/SendVerificationEmail";
import {
useSendVerificationEmail
} from "@app/hooks/api";
import Error from "../basic/Error";
import { Button } from "../v2";
@@ -70,6 +72,7 @@ export default function CodeInputStep({
codeError,
isCodeInputCheckLoading
}: CodeInputStepProps): JSX.Element {
const { mutateAsync } = useSendVerificationEmail();
const [isLoading, setIsLoading] = useState(false);
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false);
const { t } = useTranslation();
@@ -77,7 +80,7 @@ export default function CodeInputStep({
const resendVerificationEmail = async () => {
setIsResendingVerificationEmail(true);
setIsLoading(true);
sendVerificationEmail(email);
await mutateAsync({ email });
setTimeout(() => {
setIsLoading(false);
setIsResendingVerificationEmail(false);

View File

@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import Link from "next/link";
import sendVerificationEmail from "@app/pages/api/auth/SendVerificationEmail";
import { useSendVerificationEmail } from "@app/hooks/api";
import { Button, Input } from "../v2";
@@ -25,13 +25,14 @@ export default function EnterEmailStep({
setEmail,
incrementStep
}: DownloadBackupPDFStepProps): JSX.Element {
const { mutateAsync } = useSendVerificationEmail();
const [emailError, setEmailError] = useState(false);
const { t } = useTranslation();
/**
* Verifies if the entered email "looks" correct
*/
const emailCheck = () => {
const emailCheck = async () => {
let emailCheckBool = false;
if (!email) {
setEmailError(true);
@@ -45,7 +46,7 @@ export default function EnterEmailStep({
// If everything is correct, go to the next step
if (!emailCheckBool) {
sendVerificationEmail(email);
await mutateAsync({ email });
incrementStep();
}
};

View File

@@ -2,9 +2,9 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useRouter } from "next/router";
import { useAddUserToOrg } from "@app/hooks/api";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import { usePopUp } from "@app/hooks/usePopUp";
import addUserToOrg from "@app/pages/api/organization/addUserToOrg";
import { Button, EmailServiceSetupModal } from "../v2";
@@ -12,10 +12,12 @@ import { Button, EmailServiceSetupModal } from "../v2";
* This is the last step of the signup flow. People can optionally invite their teammates here.
*/
export default function TeamInviteStep(): JSX.Element {
const [emails, setEmails] = useState("");
const { t } = useTranslation();
const router = useRouter();
const [emails, setEmails] = useState("");
const { data: serverDetails } = useFetchServerStatus();
const { mutateAsync } = useAddUserToOrg();
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
// Redirect user to the getting started page
@@ -27,7 +29,12 @@ export default function TeamInviteStep(): JSX.Element {
inviteEmails
.split(",")
.map((email) => email.trim())
.map(async (email) => addUserToOrg(email, String(localStorage.getItem("orgData.id"))));
.map(async (email) => {
mutateAsync({
inviteeEmail: email,
organizationId: String(localStorage.getItem("orgData.id"))
});
});
await redirectToHome();
};

View File

@@ -9,8 +9,8 @@ import nacl from "tweetnacl";
import { encodeBase64 } from "tweetnacl-util";
import { useGetCommonPasswords } from "@app/hooks/api";
import completeAccountInformationSignup from "@app/pages/api/auth/CompleteAccountInformationSignup";
import getOrganizations from "@app/pages/api/organization/getOrgs";
import { completeAccountSignup } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import ProjectService from "@app/services/ProjectService";
import InputField from "../basic/InputField";
@@ -159,7 +159,7 @@ export default function UserInfoStep({
secret: Buffer.from(derivedKey.hash)
});
const response = await completeAccountInformationSignup({
const response = await completeAccountSignup({
email,
firstName: name.split(" ")[0],
lastName: name.split(" ").slice(1).join(" "),
@@ -190,7 +190,8 @@ export default function UserInfoStep({
privateKey
});
const userOrgs = await getOrganizations();
const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]?._id;
const project = await ProjectService.initProject({
organizationId: orgId,

View File

@@ -3,8 +3,9 @@ import crypto from "crypto";
import jsrp from "jsrp";
import changePassword2 from "@app/pages/api/auth/ChangePassword2";
import SRP1 from "@app/pages/api/auth/SRP1";
import {
changePassword,
srp1} from "@app/hooks/api/auth/queries";
import Aes256Gcm from "./cryptography/aes-256-gcm";
import { deriveArgonKey } from "./cryptography/crypto";
@@ -27,7 +28,7 @@ const attemptChangePassword = ({ email, currentPassword, newPassword }: Params):
try {
const clientPublicKey = clientOldPassword.getPublicKey();
const res = await SRP1({ clientPublicKey });
const res = await srp1({ clientPublicKey });
serverPublicKey = res.serverPublicKey;
salt = res.salt;
@@ -71,7 +72,7 @@ const attemptChangePassword = ({ email, currentPassword, newPassword }: Params):
secret: Buffer.from(derivedKey.hash)
});
await changePassword2({
await changePassword({
clientProof,
protectedKey,
protectedKeyIV,

View File

@@ -1,10 +1,9 @@
/* eslint-disable prefer-destructuring */
import jsrp from "jsrp";
import login1 from "@app/pages/api/auth/Login1";
import login2 from "@app/pages/api/auth/Login2";
import getOrganizations from "@app/pages/api/organization/getOrgs";
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
import { login1, login2 } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
import KeyService from "@app/services/KeyService";
import Telemetry from "./telemetry/Telemetry";
@@ -125,13 +124,11 @@ const attemptLogin = async (
privateKey
});
const userOrgs = await getOrganizations();
const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]._id;
localStorage.setItem("orgData.id", orgId);
const orgUserProjects = await getOrganizationUserProjects({
orgId
});
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
if (orgUserProjects.length > 0) {
localStorage.setItem("projectData.id", orgUserProjects[0]._id);

View File

@@ -1,10 +1,10 @@
/* eslint-disable prefer-destructuring */
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 { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
// import verifyMfaToken from "@app/pages/api/auth/verifyMfaToken";
import KeyService from "@app/services/KeyService";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
@@ -65,7 +65,7 @@ const attemptLoginMfa = async ({
tag
} = await verifyMfaToken({
email,
mfaToken
mfaCode: mfaToken
});
// unset temporary (MFA) JWT token and set JWT token
@@ -96,13 +96,11 @@ const attemptLoginMfa = async ({
// 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 userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]._id;
localStorage.setItem("orgData.id", orgId);
const orgUserProjects = await getOrganizationUserProjects({
orgId
});
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
resolve({

View File

@@ -1,10 +1,9 @@
/* eslint-disable prefer-destructuring */
import jsrp from "jsrp";
import login1 from "@app/pages/api/auth/Login1";
import login2 from "@app/pages/api/auth/Login2";
import getOrganizations from "@app/pages/api/organization/getOrgs";
import getOrganizationUserProjects from "@app/pages/api/organization/GetOrgUserProjects";
import { login1, login2 } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
import KeyService from "@app/services/KeyService";
import Telemetry from "./telemetry/Telemetry";
@@ -36,7 +35,6 @@ const attemptLogin = async (
providerAuthToken?: string;
}
): Promise<IsLoginSuccessful> => {
const telemetry = new Telemetry().getInstance();
return new Promise((resolve, reject) => {
client.init(
@@ -47,12 +45,13 @@ const attemptLogin = async (
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
@@ -124,14 +123,12 @@ const attemptLogin = async (
// 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 userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]._id;
localStorage.setItem("orgData.id", orgId);
const orgUserProjects = await getOrganizationUserProjects({
orgId
});
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
if (orgUserProjects.length > 0) {
localStorage.setItem("projectData.id", orgUserProjects[0]._id);

View File

@@ -1,10 +1,9 @@
/* eslint-disable prefer-destructuring */
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 { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { fetchMyOrganizationProjects } from "@app/hooks/api/users/queries";
import KeyService from "@app/services/KeyService";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
@@ -56,7 +55,7 @@ const attemptLoginMfa = async ({
tag
} = await verifyMfaToken({
email,
mfaToken
mfaCode: mfaToken
});
// unset temporary (MFA) JWT token and set JWT token
@@ -87,13 +86,11 @@ const attemptLoginMfa = async ({
// 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 userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]._id;
localStorage.setItem("orgData.id", orgId);
const orgUserProjects = await getOrganizationUserProjects({
orgId
});
const orgUserProjects = await fetchMyOrganizationProjects(orgId);
localStorage.setItem("projectData.id", orgUserProjects[0]._id);
resolve(true);

View File

@@ -1,5 +1,4 @@
import getOrganizationUsers from "@app/pages/api/organization/GetOrgUsers";
import checkUserAction from "@app/pages/api/userActions/checkUserAction";
import { fetchOrgUsers,fetchUserAction } from "@app/hooks/api/users/queries";
interface OnboardingCheckProps {
setTotalOnboardingActionsDone?: (value: number) => void;
@@ -20,34 +19,31 @@ const onboardingCheck = async ({
setUsersInOrg
}: OnboardingCheckProps) => {
let countActions = 0;
const userActionSlack = await checkUserAction({
action: "slack_cta_clicked"
});
const userActionSlack = await fetchUserAction(
"slack_cta_clicked"
);
if (userActionSlack) {
countActions += 1;
}
if (setHasUserClickedSlack) setHasUserClickedSlack(!!userActionSlack);
const userActionSecrets = await checkUserAction({
action: "first_time_secrets_pushed"
});
const userActionSecrets = await fetchUserAction("first_time_secrets_pushed");
if (userActionSecrets) {
countActions += 1;
}
if (setHasUserPushedSecrets) setHasUserPushedSecrets(!!userActionSecrets);
const userActionIntro = await checkUserAction({
action: "intro_cta_clicked"
});
const userActionIntro = await fetchUserAction("intro_cta_clicked");
if (userActionIntro) {
countActions += 1;
}
if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro);
const orgId = localStorage.getItem("orgData.id");
const orgUsers = await getOrganizationUsers({
orgId: orgId || ""
});
const orgUsers = await fetchOrgUsers(orgId || "");
if (orgUsers.length > 1) {
countActions += 1;
}

View File

@@ -1,147 +0,0 @@
/* eslint-disable new-cap */
import crypto from "crypto";
import jsrp from "jsrp";
import changePassword2 from "@app/pages/api/auth/ChangePassword2";
import SRP1 from "@app/pages/api/auth/SRP1";
import { saveTokenToLocalStorage } from "../saveTokenToLocalStorage";
import Aes256Gcm from "./aes-256-gcm";
import { deriveArgonKey } from "./crypto";
const clientOldPassword = new jsrp.client();
const clientNewPassword = new jsrp.client();
/**
* This function loggs in the user (whether it's right after signup, or a normal login)
* @param {*} email
* @param {*} password
* @param {*} setErrorLogin
* @param {*} router
* @param {*} isSignUp
* @returns
*/
const changePassword = async (
email: string,
currentPassword: string,
newPassword: string,
setCurrentPasswordError: (arg: boolean) => void,
setPasswordChanged: (arg: boolean) => void,
setCurrentPassword: (arg: string) => void,
setNewPassword: (arg: string) => void
) => {
try {
setPasswordChanged(false);
setCurrentPasswordError(false);
clientOldPassword.init(
{
username: email,
password: currentPassword
},
async () => {
const clientPublicKey = clientOldPassword.getPublicKey();
let serverPublicKey;
let salt;
try {
const res = await SRP1({
clientPublicKey
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setCurrentPasswordError(true);
console.log("Wrong current password", err, 1);
}
clientOldPassword.setSalt(salt);
clientOldPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientOldPassword.getProof(); // called M1
clientNewPassword.init(
{
username: email,
password: newPassword
},
async () => {
clientNewPassword.createVerifier(async (err, result) => {
const derivedKey = await deriveArgonKey({
password: newPassword,
salt: result.salt,
mem: 65536,
time: 3,
parallelism: 1,
hashLen: 32
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = crypto.randomBytes(32);
// create encrypted private key by encrypting the private
// key with the symmetric key [key]
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: localStorage.getItem("PRIVATE_KEY") as string,
secret: key
});
// create the protected key by encrypting the symmetric key
// [key] with the derived key
const {
ciphertext: protectedKey,
iv: protectedKeyIV,
tag: protectedKeyTag
} = Aes256Gcm.encrypt({
text: key.toString("hex"),
secret: Buffer.from(derivedKey.hash)
});
try {
await changePassword2({
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier
});
saveTokenToLocalStorage({
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
});
setPasswordChanged(true);
setCurrentPassword("");
setNewPassword("");
window.location.href = "/login";
// move to login page
} catch (error) {
setCurrentPasswordError(true);
console.log(error);
}
});
}
);
}
);
} catch (error) {
console.log("Something went wrong during changing the password");
}
return true;
};
export default changePassword;

View File

@@ -3,8 +3,9 @@ import crypto from "crypto";
import jsrp from "jsrp";
import issueBackupPrivateKey from "@app/pages/api/auth/IssueBackupPrivateKey";
import SRP1 from "@app/pages/api/auth/SRP1";
import { issueBackupPrivateKey ,
srp1
} from "@app/hooks/api/auth/queries";
import generateBackupPDF from "../generateBackupPDF";
import Aes256Gcm from "./aes-256-gcm";
@@ -51,7 +52,7 @@ const issueBackupKey = async ({
let serverPublicKey;
let salt;
try {
const res = await SRP1({
const res = await srp1({
clientPublicKey
});
serverPublicKey = res.serverPublicKey;
@@ -61,8 +62,8 @@ const issueBackupKey = async ({
console.log("Wrong current password", err, 1);
}
clientPassword.setSalt(salt);
clientPassword.setServerPublicKey(serverPublicKey);
clientPassword.setSalt(salt as string);
clientPassword.setServerPublicKey(serverPublicKey as string);
const clientProof = clientPassword.getProof(); // called M1
const generatedKey = crypto.randomBytes(16).toString("hex");
@@ -80,24 +81,25 @@ const issueBackupKey = async ({
secret: generatedKey
});
const res = await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof
});
try {
await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof
});
if (res?.status === 400) {
setBackupKeyError(true);
} else if (res?.status === 200) {
generateBackupPDF({
personalName,
personalEmail: email,
generatedKey
});
setBackupKeyIssued(true);
} catch {
setBackupKeyError(true);
}
}
);

View File

@@ -2,7 +2,7 @@ import crypto from "crypto";
import { SecretDataProps, Tag } from "public/data/frequentInterfaces";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto";
@@ -42,19 +42,21 @@ const encryptSecrets = async ({
}) => {
let secrets;
try {
const sharedKey = await getLatestFileKey({ workspaceId });
// const sharedKey = await getLatestFileKey({ workspaceId });
const wsKey = await fetchUserWsKey(workspaceId);
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
let randomBytes: string;
if (Object.keys(sharedKey).length > 0) {
if (wsKey) {
// case: a (shared) key exists for the workspace
randomBytes = decryptAssymmetric({
ciphertext: sharedKey.latestKey.encryptedKey,
nonce: sharedKey.latestKey.nonce,
publicKey: sharedKey.latestKey.sender.publicKey,
ciphertext: wsKey.encryptedKey,
nonce: wsKey.nonce,
publicKey: wsKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
} else {
// case: a (shared) key does not exist for the workspace
randomBytes = crypto.randomBytes(16).toString("hex");
@@ -114,6 +116,7 @@ const encryptSecrets = async ({
return result;
});
} catch (error) {
console.log("Error while encrypting secrets");
}

View File

@@ -1,160 +0,0 @@
import { Tag } from "public/data/frequentInterfaces";
import getSecrets from "@app/pages/api/files/GetSecrets";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
import { decryptAssymmetric, decryptSymmetric } from "../cryptography/crypto";
interface EncryptedSecretProps {
_id: string;
createdAt: string;
environment: string;
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
type: "personal" | "shared";
tags: Tag[];
}
interface SecretProps {
key: string;
value: string | undefined;
type: "personal" | "shared";
comment: string;
id: string;
tags: Tag[];
}
interface FunctionProps {
env: string;
setIsKeyAvailable?: any;
setData?: any;
workspaceId: string;
}
/**
* Gets the secrets for a certain project
* @param {object} obj
* @param {string} obj.env - environment for which we are getting secrets
* @param {boolean} obj.isKeyAvailable - if a person is able to create new key pairs
* @param {function} obj.setData - state function that manages the state of secrets in the dashboard
* @param {string} obj.workspaceId - id of a workspace for which we are getting secrets
*/
const getSecretsForProject = async ({
env,
setIsKeyAvailable,
setData,
workspaceId
}: FunctionProps) => {
try {
let encryptedSecrets;
try {
encryptedSecrets = await getSecrets(workspaceId, env);
} catch (error) {
console.log("ERROR: Not able to access the latest version of secrets");
}
const latestKey = await getLatestFileKey({ workspaceId });
// This is called isKeyAvailable but what it really means is if a person is able to create new key pairs
if (typeof setIsKeyAvailable === "function") {
setIsKeyAvailable(!latestKey ? encryptedSecrets.length === 0 : true);
}
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
const tempDecryptedSecrets: SecretProps[] = [];
if (latestKey) {
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: latestKey.latestKey.encryptedKey,
nonce: latestKey.latestKey.nonce,
publicKey: latestKey.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
// decrypt secret keys, values, and comments
encryptedSecrets.forEach((secret: EncryptedSecretProps) => {
const plainTextKey = decryptSymmetric({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key
});
let plainTextValue;
if (secret.secretValueCiphertext !== undefined) {
plainTextValue = decryptSymmetric({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key
});
} else {
plainTextValue = undefined;
}
let plainTextComment;
if (secret.secretCommentCiphertext) {
plainTextComment = decryptSymmetric({
ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV,
tag: secret.secretCommentTag,
key
});
} else {
plainTextComment = "";
}
tempDecryptedSecrets.push({
id: secret._id,
key: plainTextKey,
value: plainTextValue,
type: secret.type,
comment: plainTextComment,
tags: secret.tags
});
});
}
const secretKeys = [...new Set(tempDecryptedSecrets.map((secret) => secret.key))];
const result = secretKeys.map((key, index) => ({
id: tempDecryptedSecrets.filter((secret) => secret.key === key && secret.type === "shared")[0]
?.id,
idOverride: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === "personal"
)[0]?.id,
pos: index,
key,
value: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === "shared"
)[0]?.value,
valueOverride: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === "personal"
)[0]?.value,
comment: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === "shared"
)[0]?.comment,
tags: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === "shared"
)[0]?.tags
}));
if (typeof setData === "function") {
setData(result);
}
return result;
} catch (error) {
console.log("Something went wrong during accessing or decripting secrets.");
}
return [];
};
export default getSecretsForProject;

View File

@@ -1,6 +1,6 @@
import { createContext, ReactNode, useContext, useMemo } from "react";
import { useGetOrganization } from "@app/hooks/api";
import { useGetOrganizations } from "@app/hooks/api";
import { Organization } from "@app/hooks/api/types";
@@ -17,8 +17,8 @@ type Props = {
};
export const OrgProvider = ({ children }: Props): JSX.Element => {
const { data: userOrgs, isLoading } = useGetOrganization();
const { data: userOrgs, isLoading } = useGetOrganizations();
// const currentWsOrgID = currentWorkspace?.organization;
const currentWsOrgID = localStorage.getItem("orgData.id");

View File

@@ -8,7 +8,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import getActionData from "@app/ee/api/secrets/GetActionData";
import patienceDiff from "@app/ee/utilities/findTextDifferences";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
import {
useGetUserWsKey
} from "@app/hooks/api";
import {
decryptAssymmetric,
@@ -59,25 +61,24 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
const [actionData, setActionData] = useState<DecryptedSecretProps[]>();
const [actionMetaData, setActionMetaData] = useState<ActionProps>();
const [isLoading, setIsLoading] = useState(false);
const { data: wsKey } = useGetUserWsKey(String(router.query.id));
useEffect(() => {
const getLogData = async () => {
setIsLoading(true);
const tempActionData = await getActionData({ actionId: currentAction });
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
// #TODO: make this a separate function and reuse across the app
let decryptedLatestKey: string;
if (latestKey) {
if (wsKey) {
// assymmetrically decrypt symmetric key with local private key
decryptedLatestKey = decryptAssymmetric({
ciphertext: latestKey.latestKey.encryptedKey,
nonce: latestKey.latestKey.nonce,
publicKey: latestKey.latestKey.sender.publicKey,
ciphertext: wsKey.encryptedKey,
nonce: wsKey.nonce,
publicKey: wsKey.sender.publicKey,
privateKey: String(PRIVATE_KEY)
});
}
const decryptedSecretVersions = tempActionData.payload.secretVersions.map(
(encryptedSecretVersion: {
@@ -122,9 +123,10 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
setActionData(decryptedSecretVersions);
setActionMetaData({ name: tempActionData.name });
setIsLoading(false);
}
};
getLogData();
}, [currentAction]);
}, [currentAction, wsKey]);
return (
<div

View File

@@ -1,287 +0,0 @@
/* eslint-disable no-nested-ternary */
import { useEffect, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tag } from "public/data/frequentInterfaces";
import Button from "@app/components/basic/buttons/Button";
import {
decryptAssymmetric,
decryptSymmetric
} from "@app/components/utilities/cryptography/crypto";
import getProjectSecretShanpshots from "@app/ee/api/secrets/GetProjectSercetShanpshots";
import getSecretSnapshotData from "@app/ee/api/secrets/GetSecretSnapshotData";
import timeSince from "@app/ee/utilities/timeSince";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
export interface SecretDataProps {
pos: number;
key: string;
value: string;
type: string;
id: string;
environment: string;
}
interface SideBarProps {
toggleSidebar: (value: boolean) => void;
setSnapshotData: (value: any) => void;
chosenSnapshot: string;
}
interface SnaphotProps {
_id: string;
createdAt: string;
secretVersions: string[];
}
interface EncrypetedSecretVersionListProps {
_id: string;
createdAt: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
environment: string;
type: "personal" | "shared";
tags: Tag[];
}
/**
* @param {object} obj
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
* @param {function} obj.setSnapshotData - state manager for snapshot data
* @param {string} obj.chosenSnaphshot - the snapshot id which is currently selected
* @returns the sidebar with the options for point-in-time recovery (commits)
*/
const PITRecoverySidebar = ({ toggleSidebar, setSnapshotData, chosenSnapshot }: SideBarProps) => {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [secretSnapshotsMetadata, setSecretSnapshotsMetadata] = useState<SnaphotProps[]>([]);
const [currentOffset, setCurrentOffset] = useState(0);
const currentLimit = 15;
const loadMoreSnapshots = () => {
setCurrentOffset(currentOffset + currentLimit);
};
useEffect(() => {
const getLogData = async () => {
setIsLoading(true);
const results = await getProjectSecretShanpshots({
workspaceId: String(router.query.id),
limit: currentLimit,
offset: currentOffset
});
setSecretSnapshotsMetadata(secretSnapshotsMetadata.concat(results));
setIsLoading(false);
};
getLogData();
}, [currentOffset]);
const exploreSnapshot = async ({ snapshotId }: { snapshotId: string }) => {
const secretSnapshotData = await getSecretSnapshotData({ secretSnapshotId: snapshotId });
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let decryptedLatestKey: string;
if (latestKey) {
// assymmetrically decrypt symmetric key with local private key
decryptedLatestKey = decryptAssymmetric({
ciphertext: latestKey.latestKey.encryptedKey,
nonce: latestKey.latestKey.nonce,
publicKey: latestKey.latestKey.sender.publicKey,
privateKey: String(PRIVATE_KEY)
});
}
const decryptedSecretVersions = secretSnapshotData.secretVersions
.filter(
(sv: EncrypetedSecretVersionListProps) =>
sv.type !== undefined && sv.environment !== undefined
)
.map((encryptedSecretVersion: EncrypetedSecretVersionListProps, pos: number) => ({
id: encryptedSecretVersion._id,
pos,
type: encryptedSecretVersion.type,
environment: encryptedSecretVersion.environment,
tags: encryptedSecretVersion.tags,
key: decryptSymmetric({
ciphertext: encryptedSecretVersion.secretKeyCiphertext,
iv: encryptedSecretVersion.secretKeyIV,
tag: encryptedSecretVersion.secretKeyTag,
key: decryptedLatestKey
}),
value: decryptSymmetric({
ciphertext: encryptedSecretVersion.secretValueCiphertext,
iv: encryptedSecretVersion.secretValueIV,
tag: encryptedSecretVersion.secretValueTag,
key: decryptedLatestKey
})
}));
const secretKeys = [
...new Set(
decryptedSecretVersions
.filter((dsv: any) => dsv.type !== undefined || dsv.environemnt !== undefined)
.map((secret: SecretDataProps) => secret.key)
)
];
const result = secretKeys.map((key, index) =>
decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
)[0]?.id
? {
id: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
)[0].id,
pos: index,
key,
environment: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
)[0].environment,
tags: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
)[0].tags,
value: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
)[0]?.value,
valueOverride: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
)[0]?.value
}
: {
id: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
)[0].id,
pos: index,
key,
environment: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
)[0].environment,
tags: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
)[0].tags,
value: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "shared"
)[0]?.value,
valueOverride: decryptedSecretVersions.filter(
(secret: SecretDataProps) => secret.key === key && secret.type === "personal"
)[0]?.value
}
);
setSnapshotData({
id: secretSnapshotData._id,
version: secretSnapshotData.version,
createdAt: secretSnapshotData.createdAt,
secretVersions: result,
comment: ""
});
};
return (
<div
className={`min-w-sm absolute w-full max-w-sm border-l border-mineshaft-500 ${
isLoading ? "bg-bunker-800" : "bg-bunker"
} fixed sticky right-0 top-0 z-[40] flex h-full flex-col justify-between shadow-xl`}
>
{isLoading ? (
<div className="mb-8 flex h-full items-center justify-center">
<Image
src="/images/loading/loading.gif"
height={60}
width={100}
alt="infisical loading indicator"
/>
</div>
) : (
<div className="h-min">
<div className="flex flex-row items-center justify-between border-b border-mineshaft-500 px-4 py-3">
<p className="text-lg font-semibold text-bunker-200">Point In Recovery</p>
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
className="p-1"
onClick={() => toggleSidebar(false)}
>
<FontAwesomeIcon icon={faXmark} className="h-4 w-4 cursor-pointer text-bunker-300" />
</div>
</div>
<div className="flex h-[calc(100vh-115px)] w-96 flex-col overflow-y-auto border-l border-mineshaft-600 bg-bunker px-2 py-2">
<span className="px-2 pb-2 text-sm text-bunker-200">
Note: This will recover secrets for all enviroments in this project.
</span>
{secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) => (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
key={snapshot._id}
onClick={() => exploreSnapshot({ snapshotId: snapshot._id })}
className={`${
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
? "pointer-events-none bg-primary text-black"
: "cursor-pointer bg-mineshaft-700 duration-200 hover:bg-mineshaft-500"
} mb-2 flex flex-row items-center justify-between rounded-md py-3 px-4`}
>
<div className="flex flex-row items-start">
<div
className={`${
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
? "text-bunker-800"
: "text-bunker-200"
} mr-1.5 text-sm`}
>
{timeSince(new Date(snapshot.createdAt))}
</div>
<div
className={`${
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
? "text-bunker-900"
: "text-bunker-300"
} text-sm `}
>{` - ${snapshot.secretVersions.length} Secrets`}</div>
</div>
<div
className={`${
chosenSnapshot === snapshot._id || (id === 0 && chosenSnapshot === "")
? "pointer-events-none text-bunker-800"
: "cursor-pointer text-bunker-200 duration-200 hover:text-primary"
} text-sm`}
>
{id === 0
? "Current Version"
: chosenSnapshot === snapshot._id
? "Currently Viewing"
: "Explore"}
</div>
</div>
))}
<div className="mb-14 flex w-full justify-center">
<div className="w-40 items-center">
<Button
text="View More"
textDisabled="End of History"
active={secretSnapshotsMetadata.length % 15 === 0}
onButtonPressed={loadMoreSnapshots}
size="md"
color="mineshaft"
/>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default PITRecoverySidebar;

View File

@@ -1,138 +0,0 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Image from "next/image";
import { useRouter } from "next/router";
import { faCircle, faDotCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
decryptAssymmetric,
decryptSymmetric
} from "@app/components/utilities/cryptography/crypto";
import getSecretVersions from "@app/ee/api/secrets/GetSecretVersions";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
interface DecryptedSecretVersionListProps {
createdAt: string;
value: string;
}
interface EncrypetedSecretVersionListProps {
createdAt: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
}
/**
* @param {string} secretId - the id of a secret for which are querying version history
* @returns a list of versions for a specific secret
*/
const SecretVersionList = ({ secretId }: { secretId: string }) => {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const [secretVersions, setSecretVersions] = useState<DecryptedSecretVersionListProps[]>([]);
useEffect(() => {
const getSecretVersionHistory = async () => {
setIsLoading(true);
try {
const encryptedSecretVersions = await getSecretVersions({ secretId, offset: 0, limit: 10 });
const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let decryptedLatestKey: string;
if (latestKey) {
// assymmetrically decrypt symmetric key with local private key
decryptedLatestKey = decryptAssymmetric({
ciphertext: latestKey.latestKey.encryptedKey,
nonce: latestKey.latestKey.nonce,
publicKey: latestKey.latestKey.sender.publicKey,
privateKey: String(PRIVATE_KEY)
});
}
const decryptedSecretVersions = encryptedSecretVersions?.secretVersions.map(
(encryptedSecretVersion: EncrypetedSecretVersionListProps) => ({
createdAt: encryptedSecretVersion.createdAt,
value: decryptSymmetric({
ciphertext: encryptedSecretVersion.secretValueCiphertext,
iv: encryptedSecretVersion.secretValueIV,
tag: encryptedSecretVersion.secretValueTag,
key: decryptedLatestKey
})
})
);
setSecretVersions(decryptedSecretVersions);
setIsLoading(false);
} catch (error) {
console.log(error);
}
};
getSecretVersionHistory();
}, [secretId]);
return (
<div className="min-w-40 overflow-x-none dark mt-4 h-[12.4rem] w-full px-4 text-sm text-bunker-300">
<p className="">{t("dashboard.sidebar.version-history")}</p>
<div className="overflow-x-none h-full rounded-md border border-mineshaft-500 bg-bunker-800 py-0.5 pl-1">
{isLoading ? (
<div className="flex h-full items-center justify-center">
<Image
src="/images/loading/loading.gif"
height={60}
width={100}
alt="infisical loading indicator"
/>
</div>
) : (
<div className="overflow-x-none h-48 overflow-y-auto dark:[color-scheme:dark]">
{secretVersions ? (
secretVersions
?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
.map((version: DecryptedSecretVersionListProps, index: number) => (
<div key={`${version.createdAt}.${index + 1}`} className="flex flex-row">
<div className="flex flex-col items-center pr-1">
<div className="p-1">
<FontAwesomeIcon icon={index === 0 ? faDotCircle : faCircle} />
</div>
<div className="mt-1 h-full w-0 border-l border-bunker-300" />
</div>
<div className="flex w-full max-w-[calc(100%-2.3rem)] flex-col">
<div className="pr-2 text-bunker-300/90">
{new Date(version.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
})}
</div>
<div className="">
<p className="ph-no-capture break-words">
<span className="mr-1.5 rounded-sm bg-primary-500/30 py-0.5 px-1">
Value:
</span>
<span className="font-mono">{version.value}</span>
</p>
</div>
</div>
</div>
))
) : (
<div className="flex h-full w-full items-center justify-center text-bunker-400">
No version history yet.
</div>
)}
</div>
)}
</div>
</div>
);
};
export default SecretVersionList;

View File

@@ -2,10 +2,10 @@ import crypto from "crypto";
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets";
import addSecrets from "@app/pages/api/files/AddSecrets";
import getUser from "@app/pages/api/user/getUser";
import createWorkspace from "@app/pages/api/workspace/createWorkspace";
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
import { uploadWsKey } from "@app/hooks/api/keys/queries";
import { createSecret } from "@app/hooks/api/secrets/queries";
import { fetchUserDetails } from "@app/hooks/api/users/queries";
import { createWorkspace } from "@app/hooks/api/workspace/queries";
const secretsToBeAdded = [
{
@@ -91,49 +91,60 @@ const initProjectHelper = async ({
organizationId: string;
projectName: string;
}) => {
let project;
try {
// create new project
const { data: { workspace } } = await createWorkspace({
workspaceName: projectName,
organizationId
});
// create and upload new (encrypted) project key
const randomBytes = crypto.randomBytes(16).toString("hex");
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
if (!PRIVATE_KEY) throw new Error("Failed to find private key");
// create new project
project = await createWorkspace({
workspaceName: projectName,
organizationId
});
const user = await fetchUserDetails();
// create and upload new (encrypted) project key
const randomBytes = crypto.randomBytes(16).toString("hex");
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
if (!PRIVATE_KEY) throw new Error("Failed to find private key");
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: user.publicKey,
privateKey: PRIVATE_KEY
});
const user = await getUser();
await uploadWsKey({
workspaceId: workspace._id,
userId: user._id,
encryptedKey: ciphertext,
nonce
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: user.publicKey,
privateKey: PRIVATE_KEY
});
await uploadKeys(project._id, user._id, ciphertext, nonce);
// encrypt and upload secrets to new project
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeAdded,
workspaceId: project._id,
env: "dev"
});
await addSecrets({
secrets: secrets ?? [],
env: "dev",
workspaceId: project._id
});
} catch (err) {
console.error("Failed to init project in organization", err);
}
return project;
// encrypt and upload secrets to new project
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeAdded,
workspaceId: workspace._id,
env: "dev"
});
secrets?.forEach((secret) => {
createSecret({
workspaceId: workspace._id,
environment: secret.environment,
type: secret.type,
secretKey: secret.secretName,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
secretPath: "/"
});
});
return workspace;
}
export {

View File

@@ -1,6 +1,10 @@
export {
useGetAuthToken,
useGetCommonPasswords,
useResetPassword,
useSendMfaToken,
useVerifyMfaToken
} from "./queries"
useSendPasswordResetEmail,
useSendVerificationEmail,
useVerifyEmailVerificationCode,
useVerifyMfaToken,
useVerifyPasswordResetCode} from "./queries"

View File

@@ -4,16 +4,86 @@ import { apiRequest } from "@app/config/request";
import { setAuthToken } from "@app/reactQuery";
import {
ChangePasswordDTO,
CompleteAccountDTO,
CompleteAccountSignupDTO,
GetAuthTokenAPI,
GetBackupEncryptedPrivateKeyDTO,
IssueBackupPrivateKeyDTO,
Login1DTO,
Login1Res,
Login2DTO,
Login2Res,
ResetPasswordDTO,
SendMfaTokenDTO,
SRP1DTO,
SRPR1Res,
VerifyMfaTokenDTO,
VerifyMfaTokenRes} from "./types";
VerifyMfaTokenRes,
VerifySignupInviteDTO} from "./types";
const authKeys = {
getAuthToken: ["token"] as const,
commonPasswords: ["common-passwords"] as const
};
export const login1 = async (loginDetails: Login1DTO) => {
const { data } = await apiRequest.post<Login1Res>("/api/v3/auth/login1", loginDetails);
return data;
}
export const login2 = async (loginDetails: Login2DTO) => {
const { data } = await apiRequest.post<Login2Res>("/api/v3/auth/login2", loginDetails);
return data;
}
export const useLogin1 = () => {
return useMutation({
mutationFn: async (details: {
email: string;
clientPublicKey: string;
providerAuthToken?: string;
}) => {
return login1(details);
}
});
}
export const useLogin2 = () => {
return useMutation({
mutationFn: async (details: {
email: string;
clientProof: string;
providerAuthToken?: string;
}) => {
return login2(details);
}
});
}
export const srp1 = async (details: SRP1DTO) => {
const { data } = await apiRequest.post<SRPR1Res>("/api/v1/password/srp1", details);
return data;
}
export const completeAccountSignup = async (details: CompleteAccountSignupDTO) => {
const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", details);
return data;
}
export const completeAccountSignupInvite = async (details: CompleteAccountDTO) => {
const { data } = await apiRequest.post("/api/v2/signup/complete-account/invite", details);
return data;
}
export const useCompleteAccountSignup = () => {
return useMutation({
mutationFn: async (details: CompleteAccountSignupDTO) => {
return completeAccountSignup(details);
}
});
}
export const useSendMfaToken = () => {
return useMutation<{}, {}, SendMfaTokenDTO>({
mutationFn: async ({ email }) => {
@@ -23,18 +93,161 @@ export const useSendMfaToken = () => {
});
}
export const verifyMfaToken = async ({
email,
mfaCode
}: {
email: string;
mfaCode: string;
}) => {
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
email,
mfaToken: mfaCode
});
return data;
}
export const useVerifyMfaToken = () => {
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
mutationFn: async ({ email, mfaCode }) => {
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
return verifyMfaToken({
email,
mfaToken: mfaCode
mfaCode
});
}
});
}
export const verifySignupInvite = async (details: VerifySignupInviteDTO) => {
const { data } = await apiRequest.post("/api/v1/invite-org/verify", details);
return data;
}
export const useSendVerificationEmail = () => {
return useMutation({
mutationFn: async ({
email
}: {
email: string;
}) => {
const { data } = await apiRequest.post("/api/v1/signup/email/signup", {
email
});
return data;
}
});
}
export const useVerifyEmailVerificationCode = () => {
return useMutation({
mutationFn: async ({
email,
code
}: {
email: string;
code: string;
}) => {
const { data } = await apiRequest.post("/api/v1/signup/email/verify", {
email,
code
});
return data;
}
});
}
export const useSendPasswordResetEmail = () => {
return useMutation({
mutationFn: async ({
email
}: {
email: string;
}) => {
const { data } = await apiRequest.post("/api/v1/password/email/password-reset", {
email
});
return data;
}
});
}
export const useVerifyPasswordResetCode = () => {
return useMutation({
mutationFn: async ({
email,
code
}: {
email: string;
code: string;
}) => {
const { data } = await apiRequest.post("/api/v1/password/email/password-reset-verify", {
email,
code
});
return data;
}
});
}
export const issueBackupPrivateKey = async (details: IssueBackupPrivateKeyDTO) => {
const { data } = await apiRequest.post("/api/v1/password/backup-private-key", details);
return data;
}
export const getBackupEncryptedPrivateKey = async ({
verificationToken
}: GetBackupEncryptedPrivateKeyDTO) => {
const { data } = await apiRequest.get("/api/v1/password/backup-private-key", {
headers: {
Authorization: `Bearer ${verificationToken}`
}
});
return data.backupPrivateKey;
}
export const useResetPassword = () => {
return useMutation({
mutationFn: async (details: ResetPasswordDTO) => {
const { data } = await apiRequest.post("/api/v1/password/password-reset", {
protectedKey: details.protectedKey,
protectedKeyIV: details.protectedKeyIV,
protectedKeyTag: details.protectedKeyTag,
encryptedPrivateKey: details.encryptedPrivateKey,
encryptedPrivateKeyIV: details.encryptedPrivateKeyIV,
encryptedPrivateKeyTag: details.encryptedPrivateKeyTag,
salt: details.salt,
verifier: details.verifier
}, {
headers: {
Authorization: `Bearer ${details.verificationToken}`
}
});
return data;
}
});
}
export const changePassword = async (details: ChangePasswordDTO) => {
const { data } = await apiRequest.post("/api/v1/password/change-password", details);
return data;
}
export const useChangePassword = () => {
// note: use after srp1
return useMutation({
mutationFn: async (details: ChangePasswordDTO) => {
return changePassword(details);
}
});
}
// Refresh token is set as cookie when logged in
// Using that we fetch the auth bearer token needed for auth calls
const fetchAuthToken = async () => {

View File

@@ -21,4 +21,107 @@ export type VerifyMfaTokenRes = {
encryptedPrivateKey: string;
iv: string;
tag: string;
}
export type Login1DTO = {
email: string;
clientPublicKey: string;
providerAuthToken?: string;
}
export type Login2DTO = {
email: string;
clientProof: string;
providerAuthToken?: string;
}
export type Login1Res = {
serverPublicKey: string;
salt: string;
}
export type Login2Res = {
mfaEnabled: boolean;
token: string;
encryptionVersion?: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
}
export type SRP1DTO = {
clientPublicKey: string;
}
export type SRPR1Res = {
serverPublicKey: string;
salt: string;
}
export type CompleteAccountDTO = {
email: string;
firstName: string;
lastName: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
}
export type CompleteAccountSignupDTO = CompleteAccountDTO & {
providerAuthToken?: string;
attributionSource?: string;
organizationName: string;
}
export type VerifySignupInviteDTO = {
email: string;
code: string;
organizationId: string;
}
export type ChangePasswordDTO = {
clientProof: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
}
export type ResetPasswordDTO = {
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
verificationToken: string;
}
export type IssueBackupPrivateKeyDTO = {
encryptedPrivateKey: string;
iv: string;
tag: string;
salt: string;
verifier: string;
clientProof: string;
}
export type GetBackupEncryptedPrivateKeyDTO = {
verificationToken: string;
}

View File

@@ -8,29 +8,26 @@ const queryKeys = {
getBot: (workspaceId: string) => [{ workspaceId }, "bot"] as const
};
const fetchWorkspaceBot = async (workspaceId: string) => {
const { data } = await apiRequest.get<{ bot: TBot }>(`/api/v1/bot/${workspaceId}`);
return data.bot;
};
export const useGetWorkspaceBot = (workspaceId: string) =>
useQuery({
queryKey: queryKeys.getBot(workspaceId),
queryFn: () => fetchWorkspaceBot(workspaceId),
queryFn: async () => {
const { data: { bot } } = await apiRequest.get<{ bot: TBot }>(`/api/v1/bot/${workspaceId}`);
return bot;
},
enabled: Boolean(workspaceId)
});
// mutation
export const useUpdateBotActiveStatus = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TSetBotActiveStatusDto>({
mutationFn: ({ botId, isActive, botKey }) =>
apiRequest.patch(`/api/v1/bot/${botId}/active`, {
mutationFn: ({ botId, isActive, botKey }) => {
return apiRequest.patch(`/api/v1/bot/${botId}/active`, {
isActive,
botKey
}),
});
},
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(queryKeys.getBot(workspaceId));
}

View File

@@ -8,18 +8,16 @@ const incidentContactKeys = {
getAllContact: (orgId: string) => ["org-incident-contacts", { orgId }] as const
};
const fetchOrgIncidentContacts = async (orgId: string) => {
const { data } = await apiRequest.get<{ incidentContactsOrg: IncidentContact[] }>(
`/api/v1/organization/${orgId}/incidentContactOrg`
);
return data.incidentContactsOrg;
};
export const useGetOrgIncidentContact = (orgId: string) =>
useQuery({
queryKey: incidentContactKeys.getAllContact(orgId),
queryFn: () => fetchOrgIncidentContacts(orgId),
queryFn: async () => {
const { data } = await apiRequest.get<{ incidentContactsOrg: IncidentContact[] }>(
`/api/v1/organization/${orgId}/incidentContactOrg`
);
return data.incidentContactsOrg;
},
enabled: Boolean(orgId)
});

View File

@@ -1,4 +1,5 @@
export {
useAuthorizeIntegration,
useDeleteIntegrationAuth,
useGetIntegrationAuthApps,
useGetIntegrationAuthBitBucketWorkspaces,
@@ -7,4 +8,6 @@ export {
useGetIntegrationAuthRailwayEnvironments,
useGetIntegrationAuthRailwayServices,
useGetIntegrationAuthTeams,
useGetIntegrationAuthVercelBranches} from "./queries";
useGetIntegrationAuthVercelBranches,
useSaveIntegrationAccessToken
} from "./queries";

View File

@@ -312,6 +312,69 @@ export const useGetIntegrationAuthNorthflankSecretGroups = ({
});
};
export const useAuthorizeIntegration = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
workspaceId,
code,
integration
}: {
workspaceId: string;
code: string;
integration: string;
}) => {
const { data: { integrationAuth } } = await apiRequest.post("/api/v1/integration-auth/oauth-token", {
workspaceId,
code,
integration
});
return integrationAuth;
},
onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceAuthorization(res.workspace));
}
});
};
export const useSaveIntegrationAccessToken = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
workspaceId,
integration,
accessId,
accessToken,
url,
namespace
}: {
workspaceId: string | null;
integration: string | undefined;
accessId: string | null;
accessToken: string;
url: string | null;
namespace: string | null;
}) => {
const { data: { integrationAuth } } = await apiRequest.post("/api/v1/integration-auth/access-token", {
workspaceId,
integration,
accessId,
accessToken,
url,
namespace
});
return integrationAuth;
},
onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceAuthorization(res.workspace));
}
});
};
export const useDeleteIntegrationAuth = () => {
const queryClient = useQueryClient();

View File

@@ -1 +1,5 @@
export { useDeleteIntegration,useGetCloudIntegrations } from "./queries";
export {
useCreateIntegration,
useDeleteIntegration,
useGetCloudIntegrations
} from "./queries";

View File

@@ -23,6 +23,63 @@ export const useGetCloudIntegrations = () =>
queryFn: () => fetchIntegrations()
});
export const useCreateIntegration = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
integrationAuthId,
isActive,
app,
appId,
sourceEnvironment,
targetEnvironment,
targetEnvironmentId,
targetService,
targetServiceId,
owner,
path,
region,
secretPath
}: {
integrationAuthId: string;
isActive: boolean;
secretPath: string;
app: string | null;
appId: string | null;
sourceEnvironment: string;
targetEnvironment: string | null;
targetEnvironmentId: string | null;
targetService: string | null;
targetServiceId: string | null;
owner: string | null;
path: string | null;
region: string | null;
}) => {
const { data: { integration } } = await apiRequest.post("/api/v1/integration", {
integrationAuthId,
isActive,
app,
appId,
sourceEnvironment,
targetEnvironment,
targetEnvironmentId,
targetService,
targetServiceId,
owner,
path,
region,
secretPath
});
return integration;
},
onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(res.workspace));
}
});
};
export const useDeleteIntegration = () => {
const queryClient = useQueryClient();
@@ -32,4 +89,4 @@ export const useDeleteIntegration = () => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(workspaceId));
}
});
};
};

View File

@@ -8,7 +8,7 @@ const encKeyKeys = {
getUserWorkspaceKey: (workspaceID: string) => ["workspace-key-pair", { workspaceID }] as const
};
const fetchUserWsKey = async (workspaceID: string) => {
export const fetchUserWsKey = async (workspaceID: string) => {
const { data } = await apiRequest.get<{ latestKey: UserWsKeyPair }>(
`/api/v1/key/${workspaceID}/latest`
);
@@ -24,8 +24,23 @@ export const useGetUserWsKey = (workspaceID: string) =>
});
// mutations
export const uploadWsKey = async ({
workspaceId,
userId,
encryptedKey,
nonce
}: UploadWsKeyDTO) => {
return apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } })
}
export const useUploadWsKey = () =>
useMutation<{}, {}, UploadWsKeyDTO>({
mutationFn: ({ encryptedKey, nonce, userId, workspaceId }) =>
apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } })
mutationFn: async ({ encryptedKey, nonce, userId, workspaceId }) => {
return uploadWsKey({
workspaceId,
userId,
encryptedKey,
nonce
});
}
});

View File

@@ -4,7 +4,7 @@ export {
useCreateCustomerPortalSession,
useDeleteOrgPmtMethod,
useDeleteOrgTaxId,
useGetOrganization,
useGetOrganizations,
useGetOrgBillingDetails,
useGetOrgInvoices,
useGetOrgLicenses,

View File

@@ -12,10 +12,11 @@ import {
PmtMethod,
ProductsTable,
RenameOrgDTO,
TaxID} from "./types";
TaxID
} from "./types";
const organizationKeys = {
getUserOrganization: ["organization"] as const,
getUserOrganizations: ["organization"] as const,
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const,
getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const,
getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => [{ orgId, billingCycle }, "organization-plans-table"] as const,
@@ -26,13 +27,16 @@ const organizationKeys = {
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const
};
export const useGetOrganization = () => {
return useQuery({
queryKey: organizationKeys.getUserOrganization,
queryFn: async () => {
const { data } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
export const fetchOrganizations = async () => {
const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
return organizations;
}
return data.organizations;
export const useGetOrganizations = () => {
return useQuery({
queryKey: organizationKeys.getUserOrganizations,
queryFn: async () => {
return fetchOrganizations();
}
});
}
@@ -41,10 +45,11 @@ export const useRenameOrg = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, RenameOrgDTO>({
mutationFn: ({ newOrgName, orgId }) =>
apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }),
mutationFn: ({ newOrgName, orgId }) => {
return apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName });
},
onSuccess: () => {
queryClient.invalidateQueries(organizationKeys.getUserOrganization);
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
}
});
};

View File

@@ -11,13 +11,13 @@ import { apiRequest } from "@app/config/request";
import { secretSnapshotKeys } from "../secretSnapshots/queries";
import {
BatchSecretDTO,
CreateSecretDTO,
DecryptedSecret,
EncryptedSecret,
EncryptedSecretVersion,
GetProjectSecretsDTO,
GetSecretVersionsDTO,
TGetProjectSecretsAllEnvDTO
} from "./types";
TGetProjectSecretsAllEnvDTO} from "./types";
export const secretKeys = {
// this is also used in secretSnapshot part
@@ -324,3 +324,24 @@ export const useBatchSecretsOp = () => {
}
});
};
export const createSecret = async (dto: CreateSecretDTO) => {
const { data } = await apiRequest.post(`/api/v3/secrets/${dto.secretKey}`, dto);
return data;
}
export const useCreateSecret = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, CreateSecretDTO>({
mutationFn: async (dto) => {
const data = createSecret(dto);
return data;
},
onSuccess: (_, dto) => {
queryClient.invalidateQueries(
secretKeys.getProjectSecret(dto.workspaceId, dto.environment)
);
}
});
};

View File

@@ -147,3 +147,22 @@ export type TDeleteSecretsV3DTO = {
secretPath: string;
secretName: string;
};
// --- v3
export type CreateSecretDTO = {
workspaceId: string;
environment: string;
type: "shared" | "personal";
secretKey: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
secretPath: string;
}

View File

@@ -23,12 +23,13 @@ const fetchWorkspaceServiceTokens = async (workspaceID: string) => {
type UseGetWorkspaceServiceTokensProps = { workspaceID: string };
export const useGetUserWsServiceTokens = ({ workspaceID }: UseGetWorkspaceServiceTokensProps) =>
useQuery({
export const useGetUserWsServiceTokens = ({ workspaceID }: UseGetWorkspaceServiceTokensProps) => {
return useQuery({
queryKey: serviceTokenKeys.getAllWorkspaceServiceToken(workspaceID),
queryFn: () => fetchWorkspaceServiceTokens(workspaceID),
enabled: Boolean(workspaceID)
});
}
// mutation
export const useCreateServiceToken = () => {
@@ -36,6 +37,7 @@ export const useCreateServiceToken = () => {
return useMutation<CreateServiceTokenRes, {}, CreateServiceTokenDTO>({
mutationFn: async (body) => {
console.log("useCreateServiceToken");
const { data } = await apiRequest.post("/api/v2/service-token/", body);
data.serviceToken += `.${body.randomBytes}`;
return data;
@@ -51,6 +53,7 @@ export const useDeleteServiceToken = () => {
return useMutation<DeleteServiceTokenRes, {}, string>({
mutationFn: async (serviceTokenId) => {
console.log("useDeleteServiceToken");
const { data } = await apiRequest.delete(`/api/v2/service-token/${serviceTokenId}`);
return data;
},

View File

@@ -10,7 +10,6 @@ import {
UserWsTags
} from "./types";
const workspaceTags = {
getWsTags: (workspaceID: string) => ["workspace-tags", { workspaceID }] as const
};
@@ -23,12 +22,13 @@ const fetchWsTag = async (workspaceID: string) => {
return data.workspaceTags;
};
export const useGetWsTags = (workspaceID: string) =>
useQuery({
export const useGetWsTags = (workspaceID: string) => {
return useQuery({
queryKey: workspaceTags.getWsTags(workspaceID),
queryFn: () => fetchWsTag(workspaceID),
enabled: Boolean(workspaceID)
});
}
export const useCreateWsTag = () => {
const queryClient = useQueryClient();
@@ -59,4 +59,4 @@ export const useDeleteWsTag = () => {
queryClient.invalidateQueries(workspaceTags.getWsTags(tagData?.workspace));
}
});
};
};

View File

@@ -7,6 +7,7 @@ export {
useDeleteOrgMembership,
useGetMyAPIKeys,
useGetMyIp,
useGetMyOrganizationProjects,
useGetMySessions,
useGetOrgUsers,
useGetUser,
@@ -14,6 +15,6 @@ export {
useLogoutUser,
useRegisterUserAction,
useRevokeMySessions,
useUpdateMfaEnabled,
useUpdateOrgUserRole,
useUpdateUserAuthProvider
} from "./queries";
useUpdateUserAuthProvider} from "./queries";

View File

@@ -19,7 +19,8 @@ import {
RenameUserDTO,
TokenVersion,
UpdateOrgUserRoleDTO,
User} from "./types";
User
} from "./types";
const userKeys = {
getUser: ["user"] as const,
@@ -27,7 +28,8 @@ const userKeys = {
getOrgUsers: (orgId: string) => [{ orgId }, "user"],
myIp: ["ip"] as const,
myAPIKeys: ["api-keys"] as const,
mySessions: ["sessions"] as const
mySessions: ["sessions"] as const,
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
};
export const fetchUserDetails = async () => {
@@ -38,7 +40,7 @@ export const fetchUserDetails = async () => {
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
const fetchUserAction = async (action: string) => {
export const fetchUserAction = async (action: string) => {
const { data } = await apiRequest.get<{ userAction: string }>("/api/v1/user-action", {
params: {
action
@@ -146,7 +148,9 @@ export const useAddUserToOrg = () => {
}
return useMutation<Response, {}, AddUserToOrgDTO>({
mutationFn: (dto) => apiRequest.post("/api/v1/invite-org/signup", dto),
mutationFn: (dto) => {
return apiRequest.post("/api/v1/invite-org/signup", dto);
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
}
@@ -157,8 +161,9 @@ export const useDeleteOrgMembership = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, DeletOrgMembershipDTO>({
mutationFn: ({ membershipId, orgId }) =>
apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`),
mutationFn: ({ membershipId, orgId }) => {
return apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`)
},
onSuccess: (_, { orgId }) => {
queryClient.invalidateQueries(userKeys.getOrgUsers(orgId));
}
@@ -169,10 +174,11 @@ export const useUpdateOrgUserRole = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdateOrgUserRoleDTO>({
mutationFn: ({ organizationId, membershipId, role }) =>
apiRequest.patch(`/api/v2/organizations/${organizationId}/memberships/${membershipId}`, {
mutationFn: ({ organizationId, membershipId, role }) => {
return apiRequest.patch(`/api/v2/organizations/${organizationId}/memberships/${membershipId}`, {
role
}),
});
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
},
@@ -195,7 +201,9 @@ export const useRegisterUserAction = () => {
export const useLogoutUser = () =>
useMutation({
mutationFn: () => apiRequest.post("/api/v1/auth/logout"),
mutationFn: async () => {
await apiRequest.post("/api/v1/auth/logout");
},
onSuccess: () => {
setAuthToken("");
// Delete the cookie by not setting a value; Alternatively clear the local storage
@@ -303,4 +311,45 @@ export const useRevokeMySessions = () => {
queryClient.invalidateQueries(userKeys.mySessions);
}
});
}
export const useUpdateMfaEnabled = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
isMfaEnabled
}: {
isMfaEnabled: boolean;
}) => {
const { data: { user } } = await apiRequest.patch(
"/api/v2/users/me/mfa",
{
isMfaEnabled
}
);
return user;
},
onSuccess() {
queryClient.invalidateQueries(userKeys.getUser);
}
});
}
export const fetchMyOrganizationProjects = async (orgId: string) => {
const { data: { workspaces } } = await apiRequest.get(
`/api/v1/organization/${orgId}/my-workspaces`
);
return workspaces;
}
export const useGetMyOrganizationProjects = (orgId: string) => {
return useQuery({
queryKey: userKeys.myOrganizationProjects(orgId),
queryFn: async () => {
return fetchMyOrganizationProjects(orgId);
},
enabled: true
});
}

View File

@@ -9,7 +9,7 @@ export enum AuthProvider {
export type User = {
createdAt: Date;
updatedAt: Date;
email?: string;
email: string;
firstName?: string;
lastName?: string;
authProvider?: AuthProvider;

View File

@@ -1,6 +1,8 @@
export {
useAddUserToWorkspace,
useCreateWorkspace,
useCreateWsEnvironment,
useDeleteUserFromWorkspace,
useDeleteWorkspace,
useDeleteWsEnvironment,
useGetUserWorkspaceMemberships,
@@ -11,8 +13,9 @@ export {
useGetWorkspaceIndexStatus,
useGetWorkspaceIntegrations,
useGetWorkspaceSecrets,
useGetWorkspaceUsers,
useNameWorkspaceSecrets,
useRenameWorkspace,
useToggleAutoCapitalization,
useUpdateWsEnvironment
} from "./queries";
useUpdateUserWorkspaceRole,
useUpdateWsEnvironment} from "./queries";

View File

@@ -29,7 +29,8 @@ export const workspaceKeys = {
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
getAllUserWorkspace: ["workspaces"] as const,
getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const,
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const,
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const
};
const fetchWorkspaceById = async (workspaceId: string) => {
@@ -166,13 +167,21 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
enabled: Boolean(workspaceId)
});
// mutation
export const createWorkspace = ({
organizationId,
workspaceName
}: CreateWorkspaceDTO): Promise<{ data: { workspace: Workspace } }> => {
return apiRequest.post("/api/v1/workspace", { workspaceName, organizationId });
}
export const useCreateWorkspace = () => {
const queryClient = useQueryClient();
return useMutation<{ data: { workspace: Workspace } }, {}, CreateWorkspaceDTO>({
mutationFn: async ({ organizationId, workspaceName }) =>
apiRequest.post("/api/v1/workspace", { workspaceName, organizationId }),
mutationFn: async ({ organizationId, workspaceName }) => createWorkspace({
organizationId,
workspaceName
}),
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
@@ -183,8 +192,9 @@ export const useRenameWorkspace = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, RenameWorkspaceDTO>({
mutationFn: ({ workspaceID, newWorkspaceName }) =>
apiRequest.post(`/api/v1/workspace/${workspaceID}/name`, { name: newWorkspaceName }),
mutationFn: ({ workspaceID, newWorkspaceName }) => {
return apiRequest.post(`/api/v1/workspace/${workspaceID}/name`, { name: newWorkspaceName });
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
@@ -209,7 +219,9 @@ export const useDeleteWorkspace = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, DeleteWorkspaceDTO>({
mutationFn: ({ workspaceID }) => apiRequest.delete(`/api/v1/workspace/${workspaceID}`),
mutationFn: ({ workspaceID }) => {
return apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
@@ -220,11 +232,12 @@ export const useCreateWsEnvironment = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, CreateEnvironmentDTO>({
mutationFn: ({ workspaceID, environmentName, environmentSlug }) =>
apiRequest.post(`/api/v2/workspace/${workspaceID}/environments`, {
mutationFn: ({ workspaceID, environmentName, environmentSlug }) => {
return apiRequest.post(`/api/v2/workspace/${workspaceID}/environments`, {
environmentName,
environmentSlug
}),
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
@@ -235,12 +248,13 @@ export const useUpdateWsEnvironment = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdateEnvironmentDTO>({
mutationFn: ({ workspaceID, environmentName, environmentSlug, oldEnvironmentSlug }) =>
apiRequest.put(`/api/v2/workspace/${workspaceID}/environments`, {
mutationFn: ({ workspaceID, environmentName, environmentSlug, oldEnvironmentSlug }) => {
return apiRequest.put(`/api/v2/workspace/${workspaceID}/environments`, {
environmentName,
environmentSlug,
oldEnvironmentSlug
}),
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
@@ -251,13 +265,85 @@ export const useDeleteWsEnvironment = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, DeleteEnvironmentDTO>({
mutationFn: ({ workspaceID, environmentSlug }) =>
apiRequest.delete(`/api/v2/workspace/${workspaceID}/environments`, {
mutationFn: ({ workspaceID, environmentSlug }) => {
return apiRequest.delete(`/api/v2/workspace/${workspaceID}/environments`, {
data: { environmentSlug }
}),
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
});
};
export const useGetWorkspaceUsers = (workspaceId: string) => {
return useQuery({
queryKey: workspaceKeys.getWorkspaceUsers(workspaceId),
queryFn: async () => {
const { data: { users } } = await apiRequest.get(
`/api/v1/workspace/${workspaceId}/users`
);
return users;
},
enabled: true
});
}
export const useAddUserToWorkspace = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
email,
workspaceId
}: {
email: string;
workspaceId: string;
}) => {
const { data: { invitee, latestKey } } = await apiRequest.post(`/api/v1/workspace/${workspaceId}/invite-signup`, { email });
return ({
invitee,
latestKey
});
},
onSuccess: (_, dto) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(dto.workspaceId));
}
});
};
export const useDeleteUserFromWorkspace = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (membershipId: string) => {
const { data: { deletedMembership } } = await apiRequest.delete(`/api/v1/membership/${membershipId}`);
return deletedMembership;
},
onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.workspace));
}
});
};
export const useUpdateUserWorkspaceRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
membershipId,
role
}: {
membershipId: string;
role: string;
}) => {
const { data: { membership } } = await apiRequest.post(`/api/v1/membership/${membershipId}/change-role`, {
role
});
return membership;
},
onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.workspace));
}
});
};

View File

@@ -1,46 +0,0 @@
import { apiRequest } from "@app/config/request";
interface Props {
clientProof: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
}
/**
* This is the second step of the change password process (pake)
* @param {*} clientPublicKey
* @returns
*/
const changePassword2 = async ({
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
}: Props) => {
const { data } = await apiRequest.post("/api/v1/password/change-password", {
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
});
return data;
}
export default changePassword2;

View File

@@ -4,12 +4,13 @@ import SecurityClient from "@app/components/utilities/SecurityClient";
* This function is used to check if the user is authenticated.
* To do that, we get their tokens from cookies, and verify if they are good.
*/
const checkAuth = async () =>
SecurityClient.fetchCall("/api/v1/auth/checkAuth", {
const checkAuth = async () => {
return SecurityClient.fetchCall("/api/v1/auth/checkAuth", {
method: "POST",
headers: {
"Content-Type": "application/json"
}
}).then((res) => res);
}
export default checkAuth;

View File

@@ -1,24 +0,0 @@
interface Props {
email: string;
code: string;
}
/**
* This route check the verification code from the email that user just recieved
* @param {object} obj
* @param {string} obj.email
* @param {string} obj.code
* @returns
*/
const checkEmailVerificationCode = ({ email, code }: Props) => fetch("/api/v1/signup/email/verify", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email,
code
})
});
export default checkEmailVerificationCode;

View File

@@ -1,79 +0,0 @@
import { apiRequest } from "@app/config/request";
interface Props {
email: string;
firstName: string;
lastName: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
providerAuthToken?: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
organizationName: string;
salt: string;
verifier: string;
attributionSource?: string;
}
/**
* This function is called in the end of the signup process.
* It sends all the necessary nformation to the server.
* @param {object} obj
* @param {string} obj.email - email of the user completing signup
* @param {string} obj.firstName - first name of the user completing signup
* @param {string} obj.lastName - last name of the user completing sign up
* @param {string} obj.protectedKey - protected key in encryption version 2
* @param {string} obj.protectedKeyIV - IV of protected key in encryption version 2
* @param {string} obj.protectedKeyTag - tag of protected key in encryption version 2
* @param {string} obj.organizationName - organization name for this user (usually, [FIRST_NAME]'s organization)
* @param {string} obj.publicKey - public key of the user completing signup
* @param {string} obj.ciphertext
* @param {string} obj.iv
* @param {string} obj.tag
* @param {string} obj.salt
* @param {string} obj.verifier
* @returns
*/
const completeAccountInformationSignup = async ({
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName,
providerAuthToken,
attributionSource
}: Props) => {
const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName,
providerAuthToken,
...(attributionSource ? { attributionSource } : {})
});
return data;
}
export default completeAccountInformationSignup;

View File

@@ -1,70 +0,0 @@
import { apiRequest } from "@app/config/request";
interface Props {
email: string;
firstName: string;
lastName: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: 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.
* @param {object} obj
* @param {string} obj.email - email of the user completing signupinvite flow
* @param {string} obj.firstName - first name of the user completing signupinvite flow
* @param {string} obj.lastName - last name of the user completing signupinvite flow
* @param {string} obj.publicKey - public key of the user completing signupinvite flow
* @param {string} obj.ciphertext
* @param {string} obj.iv
* @param {string} obj.tag
* @param {string} obj.salt
* @param {string} obj.verifier
* @param {string} obj.token - token that confirms a user's identity
* @returns
*/
const completeAccountInformationSignupInvite = async ({
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
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;

View File

@@ -1,34 +0,0 @@
interface Props {
email: string;
code: string;
}
/**
* This is the second part of the account recovery step (a user needs to verify their email).
* A user need to click on a button in a magic link page
* @param {object} obj
* @param {object} obj.email - email of a user that is trying to recover access to their account
* @param {object} obj.code - token that a use received via the magic link
* @returns
*/
const EmailVerifyOnPasswordReset = async ({ email, code }: Props) => {
const response = await fetch("/api/v1/password/email/password-reset-verify", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email,
code
})
});
if (response?.status === 200) {
return response;
}
throw new Error(
"Something went wrong during email verification on password reset."
);
};
export default EmailVerifyOnPasswordReset;

View File

@@ -1,51 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
encryptedPrivateKey: string;
iv: string;
tag: string;
salt: string;
verifier: string;
clientProof: string;
}
/**
* This is the route that issues a backup private key that will afterwards be added into a pdf
* @param {object} obj
* @param {string} obj.encryptedPrivateKey
* @param {string} obj.iv
* @param {string} obj.tag
* @param {string} obj.salt
* @param {string} obj.verifier
* @param {string} obj.clientProof
* @returns
*/
const issueBackupPrivateKey = ({
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
clientProof
}: Props) =>
SecurityClient.fetchCall("/api/v1/password/backup-private-key", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
clientProof,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
})
}).then((res) => {
if (res?.status !== 200) {
console.log("Failed to issue the backup key");
}
return res;
});
export default issueBackupPrivateKey;

View File

@@ -1,33 +0,0 @@
interface Login1 {
serverPublicKey: string;
salt: string;
}
/**
* This is the first step of the login process (pake)
* @param {*} email
* @param {*} clientPublicKey
* @returns
*/
const login1 = async (loginDetails: {
email: string;
clientPublicKey: string;
providerAuthToken?: string;
}) => {
const response = await fetch("/api/v3/auth/login1", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(loginDetails),
});
// need precise error handling about the status code
if (response?.status === 200) {
const data = (await response.json()) as unknown as Login1;
return data;
}
throw new Error("Wrong password");
};
export default login1;

View File

@@ -1,42 +0,0 @@
interface Login2Response {
mfaEnabled: boolean;
token: string;
encryptionVersion?: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
}
/**
* This is the second step of the login process
* @param {*} email
* @param {*} clientPublicKey
* @returns
*/
const login2 = async (loginDetails: {
email: string;
clientProof: string;
providerAuthToken?: string;
}) => {
const response = await fetch("/api/v3/auth/login2", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(loginDetails),
credentials: "include"
});
// need precise error handling about the status code
if (response.status === 200) {
const data = (await response.json()) as unknown as Login2Response;
return data;
}
throw new Error("Password verification failed");
};
export default login2;

View File

@@ -1,41 +0,0 @@
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 () => {
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
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");
return res;
}
} catch (error) {
console.log("Error logging out", error);
}
return undefined;
};
export default logout;

View File

@@ -1,29 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
clientPublicKey: string;
}
/**
* This is the first step of the change password process (pake)
* @param {string} clientPublicKey
* @returns
*/
const SRP1 = ({ clientPublicKey }: Props) =>
SecurityClient.fetchCall("/api/v1/password/srp1", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
clientPublicKey
})
}).then(async (res) => {
if (res && res.status === 200) {
return res.json();
}
console.log("Failed to do the first step of SRP");
return undefined;
});
export default SRP1;

View File

@@ -1,33 +0,0 @@
interface Props {
email: string;
}
/**
* This is the first of the account recovery step (a user needs to verify their email).
* It will send an email containing a magic link to start the account recovery flow.
* @param {object} obj
* @param {object} obj.email - email of a user that is trying to recover access to their account
* @returns
*/
const SendEmailOnPasswordReset = async ({ email }: Props) => {
const response = await fetch("/api/v1/password/email/password-reset", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email
})
});
// need precise error handling about the status code
if (response?.status === 200) {
const data = await response.json();
return data;
}
throw new Error(
"Something went wrong while sending the email verification for password reset."
);
};
export default SendEmailOnPasswordReset;

View File

@@ -1,17 +0,0 @@
/**
* This route send the verification email to the user's email (contains a 6-digit verification code)
* @param {*} email
*/
const sendVerificationEmail = (email: string) => {
fetch("/api/v1/signup/email/signup", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email
})
});
};
export default sendVerificationEmail;

View File

@@ -1,16 +0,0 @@
const token = async () =>
fetch("/api/v1/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
credentials: "include"
}).then(async (res) => {
if (res.status === 200) {
return (await res.json()).token;
}
console.log("Getting a new token failed");
return undefined;
});
export default token;

View File

@@ -1,27 +0,0 @@
interface Props {
email: string;
code: string;
organizationId: string;
}
/**
* This route verifies the signup invite link
* @param {object} obj
* @param {string} obj.email - email that a user is trying to verify
* @param {string} obj.organizationId - id of organization that a user is trying to verify for
* @param {string} obj.code - code that a user received to the abovementioned email
* @returns
*/
const verifySignupInvite = ({ email, organizationId, code }: Props) => fetch("/api/v1/invite-org/verify", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email,
organizationId,
code
})
});
export default verifySignupInvite;

View File

@@ -1,24 +0,0 @@
/**
* This is the route that get an encrypted private key (will be decrypted with a backup key)
* @param {object} obj
* @param {object} obj.verificationToken - this is the token that confirms that a user is the right one
* @returns
*/
const getBackupEncryptedPrivateKey = ({
verificationToken
}: {
verificationToken: string;
}) => fetch("/api/v1/password/backup-private-key", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${ verificationToken}`
}
}).then(async (res) => {
if (res?.status !== 200) {
console.log("Failed to get the backup key");
}
return (await res?.json())?.backupPrivateKey;
});
export default getBackupEncryptedPrivateKey;

View File

@@ -1,8 +0,0 @@
const publicKeyInfisical = () => fetch("/api/v1/key/publicKey/infisical", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
export default publicKeyInfisical;

View File

@@ -1,57 +0,0 @@
interface Props {
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
verificationToken: string;
}
/**
* This is the route that resets the account password if all the previus steps were passed
* @param {object} obj
* @param {object} obj.verificationToken - this is the token that confirms that a user is the right one
* @param {object} obj.encryptedPrivateKey - the new encrypted private key (encrypted using the new password)
* @param {object} obj.iv
* @param {object} obj.tag
* @param {object} obj.salt
* @param {object} obj.verifier
* @returns
*/
const resetPasswordOnAccountRecovery = ({
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
verificationToken,
}: Props) => fetch("/api/v1/password/password-reset", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${verificationToken}`
},
body: JSON.stringify({
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
})
}).then(async (res) => {
if (res?.status !== 200) {
console.log("Failed to get the backup key");
}
return res;
});
export default resetPasswordOnAccountRecovery;

View File

@@ -1,25 +0,0 @@
import { apiRequest } from "@app/config/request";
/**
* Verify MFA token [mfaToken] for user with email [email]
* @param {object} obj
* @param {string} obj.email - email of user
* @param {string} obj.mfaToken - MFA cod/token to verify
* @returns
*/
const verifyMfaToken = async ({
email,
mfaToken
}: {
email: string;
mfaToken: string;
}) => {
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
email,
mfaToken
});
return data;
}
export default verifyMfaToken;

View File

@@ -1,27 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
workspaceId: string;
}
/**
* This function fetches the bot for a project
* @param {Object} obj
* @param {String} obj.workspaceId
* @returns
*/
const getBot = async ({ workspaceId }: Props) =>
SecurityClient.fetchCall(`/api/v1/bot/${workspaceId}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).bot;
}
console.log("Failed to get bot for project");
return undefined;
});
export default getBot;

View File

@@ -1,42 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface BotKey {
encryptedKey: string;
nonce: string;
}
interface Props {
botId: string;
isActive: boolean;
botKey?: BotKey;
}
/**
* This function sets the active status of a bot and shares a copy of
* the project key (encrypted under the bot's public key) with the
* project's bot
* @param {Object} obj
* @param {String} obj.botId
* @param {String} obj.isActive
* @param {Object} obj.botKey
* @returns
*/
const setBotActiveStatus = async ({ botId, isActive, botKey }: Props) =>
SecurityClient.fetchCall(`/api/v1/bot/${botId}/active`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
isActive,
botKey
})
}).then(async (res) => {
if (res && res.status === 200) {
return res.json();
}
console.log("Failed to get bot for project");
return undefined;
});
export default setBotActiveStatus;

View File

@@ -1,28 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
type NewEnvironmentInfo = {
environmentSlug: string;
environmentName: string;
};
/**
* This route deletes a specified workspace.
* @param {*} workspaceId
* @returns
*/
const createEnvironment = (workspaceId: string, newEnv: NewEnvironmentInfo) =>
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(newEnv)
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to create environment");
return undefined;
});
export default createEnvironment;

View File

@@ -1,22 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route deletes a specified env.
* @param {*} workspaceId
* @returns
*/
const deleteEnvironment = (workspaceId: string, environmentSlug: string) =>
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ environmentSlug })
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to delete environment");
return undefined;
});
export default deleteEnvironment;

Some files were not shown because too many files have changed in this diff Show More