Ghost user and migration finished!

This commit is contained in:
Daniel Hougaard
2024-02-09 22:28:37 +04:00
parent 7f1963f1ac
commit 0f98fc94f0
8 changed files with 179 additions and 98 deletions

View 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;
};

View File

@@ -327,7 +327,12 @@ export const registerRoutes = async (
identityProjectDAL,
identityOrgMembershipDAL,
projectBotDAL,
secretDAL,
orgDAL,
secretApprovalRequestDAL,
secretApprovalSecretDAL: sarSecretDAL,
projectKeyDAL,
secretVersionDAL,
userDAL,
projectEnvDAL,
orgService,

View File

@@ -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" });
}

View File

@@ -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: {

View File

@@ -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" });
};

View File

@@ -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 };
}
});
};

View File

@@ -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,

View File

@@ -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) => {