mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Ghost user and migration finished!
This commit is contained in:
126
backend/src/lib/secret/index.ts
Normal file
126
backend/src/lib/secret/index.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretKeyEncoding, TProjectKeys } from "@app/db/schemas";
|
||||
|
||||
import { decryptAsymmetric, decryptSymmetric } from "../crypto";
|
||||
import { decryptSymmetric128BitHexKeyUTF8, TDecryptSymmetricInput } from "../crypto/encryption";
|
||||
|
||||
export enum SecretDocType {
|
||||
Secret = "secret",
|
||||
SecretVersion = "secretVersion",
|
||||
ApprovalSecret = "approvalSecret"
|
||||
}
|
||||
|
||||
const PartialSecretSchema = z.object({
|
||||
id: z.string(),
|
||||
secretKeyCiphertext: z.string(),
|
||||
secretKeyIV: z.string(),
|
||||
secretKeyTag: z.string(),
|
||||
|
||||
secretValueCiphertext: z.string(),
|
||||
secretValueIV: z.string(),
|
||||
secretValueTag: z.string(),
|
||||
|
||||
secretCommentCiphertext: z.string().nullish(),
|
||||
secretCommentIV: z.string().nullish(),
|
||||
secretCommentTag: z.string().nullish(),
|
||||
|
||||
docType: z.nativeEnum(SecretDocType),
|
||||
|
||||
keyEncoding: z.string()
|
||||
});
|
||||
|
||||
const PartialDecryptedSecretSchema = z.object({
|
||||
id: z.string(),
|
||||
secretKey: z.string(),
|
||||
secretValue: z.string(),
|
||||
secretComment: z.string().optional(),
|
||||
|
||||
docType: z.nativeEnum(SecretDocType)
|
||||
});
|
||||
|
||||
export type TPartialSecret = z.infer<typeof PartialSecretSchema>;
|
||||
export type TPartialDecryptedSecret = z.infer<typeof PartialDecryptedSecretSchema>;
|
||||
|
||||
const symmetricDecrypt = ({
|
||||
keyEncoding,
|
||||
ciphertext,
|
||||
tag,
|
||||
iv,
|
||||
key,
|
||||
isApprovalSecret
|
||||
}: TDecryptSymmetricInput & { keyEncoding: SecretKeyEncoding; isApprovalSecret: boolean }) => {
|
||||
if (keyEncoding === SecretKeyEncoding.UTF8 || isApprovalSecret) {
|
||||
const data = decryptSymmetric128BitHexKeyUTF8({ key, iv, tag, ciphertext });
|
||||
return data;
|
||||
}
|
||||
if (keyEncoding === SecretKeyEncoding.BASE64) {
|
||||
const data = decryptSymmetric({ key, iv, tag, ciphertext });
|
||||
return data;
|
||||
}
|
||||
|
||||
throw new Error("Missing both encryption keys");
|
||||
};
|
||||
|
||||
export const decryptSecrets = (
|
||||
encryptedSecrets: TPartialSecret[],
|
||||
privateKey: string,
|
||||
latestKey: TProjectKeys & {
|
||||
sender: {
|
||||
publicKey: string;
|
||||
};
|
||||
}
|
||||
) => {
|
||||
const key = decryptAsymmetric({
|
||||
ciphertext: latestKey.encryptedKey,
|
||||
nonce: latestKey.nonce,
|
||||
publicKey: latestKey.sender.publicKey,
|
||||
privateKey
|
||||
});
|
||||
|
||||
const secrets: TPartialDecryptedSecret[] = [];
|
||||
|
||||
encryptedSecrets.forEach((encSecret) => {
|
||||
const secretKey = symmetricDecrypt({
|
||||
ciphertext: encSecret.secretKeyCiphertext,
|
||||
iv: encSecret.secretKeyIV,
|
||||
tag: encSecret.secretKeyTag,
|
||||
key,
|
||||
keyEncoding: encSecret.keyEncoding as SecretKeyEncoding,
|
||||
isApprovalSecret: encSecret.docType === SecretDocType.ApprovalSecret
|
||||
});
|
||||
|
||||
const secretValue = symmetricDecrypt({
|
||||
ciphertext: encSecret.secretValueCiphertext,
|
||||
iv: encSecret.secretValueIV,
|
||||
tag: encSecret.secretValueTag,
|
||||
key,
|
||||
keyEncoding: encSecret.keyEncoding as SecretKeyEncoding,
|
||||
isApprovalSecret: encSecret.docType === SecretDocType.ApprovalSecret
|
||||
});
|
||||
|
||||
const secretComment =
|
||||
encSecret.secretCommentCiphertext && encSecret.secretCommentIV && encSecret.secretCommentTag
|
||||
? symmetricDecrypt({
|
||||
ciphertext: encSecret.secretCommentCiphertext,
|
||||
iv: encSecret.secretCommentIV,
|
||||
tag: encSecret.secretCommentTag,
|
||||
key,
|
||||
keyEncoding: encSecret.keyEncoding as SecretKeyEncoding,
|
||||
isApprovalSecret: encSecret.docType === SecretDocType.ApprovalSecret
|
||||
})
|
||||
: "";
|
||||
|
||||
const decryptedSecret: TPartialDecryptedSecret = {
|
||||
id: encSecret.id,
|
||||
secretKey,
|
||||
secretValue,
|
||||
secretComment,
|
||||
docType: encSecret.docType
|
||||
};
|
||||
|
||||
secrets.push(decryptedSecret);
|
||||
});
|
||||
|
||||
return secrets;
|
||||
};
|
||||
@@ -327,7 +327,12 @@ export const registerRoutes = async (
|
||||
identityProjectDAL,
|
||||
identityOrgMembershipDAL,
|
||||
projectBotDAL,
|
||||
secretDAL,
|
||||
orgDAL,
|
||||
secretApprovalRequestDAL,
|
||||
secretApprovalSecretDAL: sarSecretDAL,
|
||||
projectKeyDAL,
|
||||
secretVersionDAL,
|
||||
userDAL,
|
||||
projectEnvDAL,
|
||||
orgService,
|
||||
|
||||
@@ -33,7 +33,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
|
||||
projectId: req.params.projectId
|
||||
});
|
||||
|
||||
if (!project.e2ee) {
|
||||
if (project.version === "v2") {
|
||||
throw new BadRequestError({ message: "Failed to find bot, project has E2EE disabled" });
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
|
||||
handler: async (req) => {
|
||||
const project = await server.services.projectBot.findProjectByBotId(req.params.botId);
|
||||
|
||||
if (project?.e2ee === false) {
|
||||
if (project?.version === "v2") {
|
||||
throw new BadRequestError({ message: "Failed to set bot active, project has E2EE disabled" });
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const projectWithEnv = ProjectsSchema.merge(
|
||||
export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
/* Get project key */
|
||||
server.route({
|
||||
url: "/:workspaceId/encrypted-key",
|
||||
url: "/:projectId/encrypted-key",
|
||||
method: "GET",
|
||||
schema: {
|
||||
description: "Return encrypted project key",
|
||||
@@ -27,7 +27,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
projectId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: ProjectKeysSchema.merge(
|
||||
@@ -50,7 +50,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: req.params.workspaceId,
|
||||
projectId: req.params.projectId,
|
||||
event: {
|
||||
type: EventType.GET_WORKSPACE_KEY,
|
||||
metadata: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { registerLoginRouter } from "./login-router";
|
||||
import { registerProjectRouter } from "./project-router";
|
||||
import { registerSecretBlindIndexRouter } from "./secret-blind-index-router";
|
||||
import { registerSecretRouter } from "./secret-router";
|
||||
import { registerSignupRouter } from "./signup-router";
|
||||
@@ -11,5 +10,4 @@ export const registerV3Routes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerUserRouter, { prefix: "/users" });
|
||||
await server.register(registerSecretRouter, { prefix: "/secrets" });
|
||||
await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" });
|
||||
await server.register(registerProjectRouter, { prefix: "/projects" });
|
||||
};
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectMembershipsSchema, ProjectsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||
|
||||
const projectWithEnv = ProjectsSchema.merge(
|
||||
z.object({
|
||||
_id: z.string(),
|
||||
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array()
|
||||
})
|
||||
);
|
||||
|
||||
export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
/* Create new project */
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: authRateLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
projectName: z.string().trim(),
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
project: projectWithEnv
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const project = await server.services.project.createProject({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.body.organizationId,
|
||||
workspaceName: req.body.projectName
|
||||
});
|
||||
|
||||
return { project };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:projectId/memberships",
|
||||
config: {
|
||||
rateLimit: authRateLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
emails: z.string().email().array()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
memberships: ProjectMembershipsSchema.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const memberships = await server.services.projectMembership.addUsersToProjectNonE2EE({
|
||||
projectId: req.params.projectId,
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
emails: req.body.emails
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId: req.params.projectId,
|
||||
...req.auditLogInfo,
|
||||
event: {
|
||||
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
|
||||
metadata: memberships.map(({ userId, id }) => ({
|
||||
userId: userId || "",
|
||||
membershipId: id,
|
||||
email: ""
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
return { memberships };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
@@ -6,10 +8,18 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
||||
import { RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { createSecretBlindIndex } from "@app/lib/crypto";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import {
|
||||
decryptAsymmetric,
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
infisicalSymmetricDecrypt,
|
||||
infisicalSymmetricEncypt
|
||||
} from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
@@ -17,11 +27,14 @@ import { TProjectPermission } from "@app/lib/types";
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
import { TOrgServiceFactory } from "../org/org-service";
|
||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
||||
import { TSecretDALFactory } from "../secret/secret-dal";
|
||||
import { TSecretVersionDALFactory } from "../secret/secret-version-dal";
|
||||
import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-index-dal";
|
||||
import { ROOT_FOLDER_NAME, TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
@@ -65,13 +78,18 @@ export const projectServiceFactory = ({
|
||||
projectDAL,
|
||||
projectQueue,
|
||||
projectKeyDAL,
|
||||
secretApprovalRequestDAL,
|
||||
secretApprovalSecretDAL,
|
||||
permissionService,
|
||||
userDAL,
|
||||
folderDAL,
|
||||
orgService,
|
||||
orgDAL,
|
||||
identityProjectDAL,
|
||||
secretVersionDAL,
|
||||
projectBotDAL,
|
||||
identityOrgMembershipDAL,
|
||||
secretDAL,
|
||||
secretBlindIndexDAL,
|
||||
projectMembershipDAL,
|
||||
projectEnvDAL,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -14,6 +14,8 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
|
||||
import NavHeader from "@app/components/navigation/NavHeader";
|
||||
import { PermissionDeniedBanner } from "@app/components/permissions";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
Button,
|
||||
EmptyState,
|
||||
IconButton,
|
||||
@@ -38,7 +40,8 @@ import {
|
||||
useGetFoldersByEnv,
|
||||
useGetProjectSecretsAllEnv,
|
||||
useGetUserWsKey,
|
||||
useUpdateSecretV3
|
||||
useUpdateSecretV3,
|
||||
useUpgradeProject
|
||||
} from "@app/hooks/api";
|
||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
|
||||
@@ -106,6 +109,7 @@ export const SecretOverviewPage = () => {
|
||||
environments: userAvailableEnvs.map(({ slug }) => slug)
|
||||
});
|
||||
|
||||
const upgradeProject = useUpgradeProject();
|
||||
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
||||
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
|
||||
const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3();
|
||||
@@ -199,6 +203,24 @@ export const SecretOverviewPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onUpgradeProject = useCallback(async () => {
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
if (!PRIVATE_KEY) {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Private key not found"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await upgradeProject.mutateAsync({
|
||||
projectId: workspaceId,
|
||||
privateKey: PRIVATE_KEY
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleResetSearch = () => setSearchFilter("");
|
||||
|
||||
const handleFolderClick = (path: string) => {
|
||||
|
||||
Reference in New Issue
Block a user