Requested changes

This commit is contained in:
Daniel Hougaard
2024-02-17 01:02:30 +01:00
parent 419916ee0c
commit a6e263eded
21 changed files with 53 additions and 41 deletions

View File

@@ -3,30 +3,30 @@ import { Knex } from "knex";
import { ProjectVersion, TableName } from "../schemas"; import { ProjectVersion, TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
const hasGhostUserColumn = await knex.schema.hasColumn(TableName.Users, "ghost"); const hasGhostUserColumn = await knex.schema.hasColumn(TableName.Users, "isGhost");
const hasProjectVersionColumn = await knex.schema.hasColumn(TableName.Project, "version"); const hasProjectVersionColumn = await knex.schema.hasColumn(TableName.Project, "version");
if (!hasGhostUserColumn) { if (!hasGhostUserColumn) {
await knex.schema.alterTable(TableName.Users, (t) => { await knex.schema.alterTable(TableName.Users, (t) => {
t.boolean("ghost").defaultTo(false).notNullable(); t.boolean("isGhost").defaultTo(false).notNullable();
}); });
} }
if (!hasProjectVersionColumn) { if (!hasProjectVersionColumn) {
await knex.schema.alterTable(TableName.Project, (t) => { await knex.schema.alterTable(TableName.Project, (t) => {
t.string("version").defaultTo(ProjectVersion.V1).notNullable(); t.integer("version").defaultTo(ProjectVersion.V1).notNullable();
t.text("upgradeStatus").nullable(); t.string("upgradeStatus").nullable();
}); });
} }
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
const hasGhostUserColumn = await knex.schema.hasColumn(TableName.Users, "ghost"); const hasGhostUserColumn = await knex.schema.hasColumn(TableName.Users, "isGhost");
const hasProjectVersionColumn = await knex.schema.hasColumn(TableName.Project, "version"); const hasProjectVersionColumn = await knex.schema.hasColumn(TableName.Project, "version");
if (hasGhostUserColumn) { if (hasGhostUserColumn) {
await knex.schema.alterTable(TableName.Users, (t) => { await knex.schema.alterTable(TableName.Users, (t) => {
t.dropColumn("ghost"); t.dropColumn("isGhost");
}); });
} }

View File

@@ -113,8 +113,8 @@ export enum SecretType {
} }
export enum ProjectVersion { export enum ProjectVersion {
V1 = "v1", V1 = 1,
V2 = "v2" V2 = 2
} }
export enum ProjectUpgradeStatus { export enum ProjectUpgradeStatus {

View File

@@ -15,7 +15,7 @@ export const ProjectsSchema = z.object({
orgId: z.string().uuid(), orgId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
version: z.string().default("v1"), version: z.number().default(1),
upgradeStatus: z.string().nullable().optional() upgradeStatus: z.string().nullable().optional()
}); });

View File

@@ -20,7 +20,7 @@ export const UsersSchema = z.object({
devices: z.unknown().nullable().optional(), devices: z.unknown().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
ghost: z.boolean().default(false) isGhost: z.boolean().default(false)
}); });
export type TUsers = z.infer<typeof UsersSchema>; export type TUsers = z.infer<typeof UsersSchema>;

View File

@@ -339,7 +339,7 @@ export const samlConfigServiceFactory = ({
firstName, firstName,
lastName, lastName,
authMethods: [AuthMethod.EMAIL], authMethods: [AuthMethod.EMAIL],
ghost: false isGhost: false
}, },
tx tx
); );

View File

@@ -275,7 +275,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
if (isOauthSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" }); if (isOauthSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" });
if (!user) { if (!user) {
user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod], ghost: false }); user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod], isGhost: false });
} }
const isLinkingRequired = !user?.authMethods?.includes(authMethod); const isLinkingRequired = !user?.authMethods?.includes(authMethod);
const isUserCompleted = user.isAccepted; const isUserCompleted = user.isAccepted;

View File

@@ -50,7 +50,7 @@ export const authSignupServiceFactory = ({
throw new Error("Failed to send verification code for complete account"); throw new Error("Failed to send verification code for complete account");
} }
if (!user) { if (!user) {
user = await userDAL.create({ authMethods: [AuthMethod.EMAIL], email, ghost: false }); user = await userDAL.create({ authMethods: [AuthMethod.EMAIL], email, isGhost: false });
} }
if (!user) throw new Error("Failed to create user"); if (!user) throw new Error("Failed to create user");

View File

@@ -77,7 +77,7 @@ export const orgDALFactory = (db: TDbClient) => {
db.ref("id").withSchema(TableName.Users).as("userId"), db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("publicKey").withSchema(TableName.UserEncryptionKey) db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
) )
.where({ ghost: false }); // MAKE SURE USER IS NOT A GHOST USER .where({ isGhost: false }); // MAKE SURE USER IS NOT A GHOST USER
return members.map(({ email, firstName, lastName, userId, publicKey, ...data }) => ({ return members.map(({ email, firstName, lastName, userId, publicKey, ...data }) => ({
...data, ...data,
user: { email, firstName, lastName, id: userId, publicKey } user: { email, firstName, lastName, id: userId, publicKey }
@@ -136,7 +136,7 @@ export const orgDALFactory = (db: TDbClient) => {
db.ref("id").withSchema(TableName.Users).as("userId"), db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("publicKey").withSchema(TableName.UserEncryptionKey) db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
) )
.where({ ghost: true }); .where({ isGhost: true });
return member; return member;
} catch (error) { } catch (error) {
return null; return null;
@@ -150,7 +150,7 @@ export const orgDALFactory = (db: TDbClient) => {
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.UserEncryptionKey, `${TableName.UserEncryptionKey}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.UserEncryptionKey, `${TableName.UserEncryptionKey}.userId`, `${TableName.Users}.id`)
.select(db.ref("id").withSchema(TableName.Users).as("userId")) .select(db.ref("id").withSchema(TableName.Users).as("userId"))
.where({ ghost: true }); .where({ isGhost: true });
return !!member; return !!member;
} catch (error) { } catch (error) {
return false; return false;

View File

@@ -138,7 +138,7 @@ export const orgServiceFactory = ({
const user = await userDAL.create( const user = await userDAL.create(
{ {
ghost: true, isGhost: true,
authMethods: [AuthMethod.EMAIL], authMethods: [AuthMethod.EMAIL],
email, email,
isAccepted: true isAccepted: true
@@ -401,7 +401,7 @@ export const orgServiceFactory = ({
email: inviteeEmail, email: inviteeEmail,
isAccepted: false, isAccepted: false,
authMethods: [AuthMethod.EMAIL], authMethods: [AuthMethod.EMAIL],
ghost: false isGhost: false
}, },
tx tx
); );

View File

@@ -119,7 +119,7 @@ export const projectBotServiceFactory = ({
throw new BadRequestError({ message: "Failed to find project by bot ID" }); throw new BadRequestError({ message: "Failed to find project by bot ID" });
} }
if (project.version === "v2") { if (project.version === ProjectVersion.V2) {
throw new BadRequestError({ message: "Failed to set bot active, project has a default bot enabled" }); throw new BadRequestError({ message: "Failed to set bot active, project has a default bot enabled" });
} }

View File

@@ -24,17 +24,17 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
db.ref("projectId").withSchema(TableName.ProjectMembership), db.ref("projectId").withSchema(TableName.ProjectMembership),
db.ref("role").withSchema(TableName.ProjectMembership), db.ref("role").withSchema(TableName.ProjectMembership),
db.ref("roleId").withSchema(TableName.ProjectMembership), db.ref("roleId").withSchema(TableName.ProjectMembership),
db.ref("ghost").withSchema(TableName.Users), db.ref("isGhost").withSchema(TableName.Users),
db.ref("email").withSchema(TableName.Users), db.ref("email").withSchema(TableName.Users),
db.ref("publicKey").withSchema(TableName.UserEncryptionKey), db.ref("publicKey").withSchema(TableName.UserEncryptionKey),
db.ref("firstName").withSchema(TableName.Users), db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users), db.ref("lastName").withSchema(TableName.Users),
db.ref("id").withSchema(TableName.Users).as("userId") db.ref("id").withSchema(TableName.Users).as("userId")
) )
.where({ ghost: false }); .where({ isGhost: false });
return members.map(({ email, firstName, lastName, publicKey, ghost, ...data }) => ({ return members.map(({ email, firstName, lastName, publicKey, isGhost, ...data }) => ({
...data, ...data,
user: { email, firstName, lastName, id: data.userId, publicKey, ghost } user: { email, firstName, lastName, id: data.userId, publicKey, isGhost }
})); }));
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Find all project members" }); throw new DatabaseError({ error, name: "Find all project members" });
@@ -47,7 +47,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
.where({ projectId }) .where({ projectId })
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.select(selectAllTableCols(TableName.Users)) .select(selectAllTableCols(TableName.Users))
.where({ ghost: true }) .where({ isGhost: true })
.first(); .first();
return ghostUser; return ghostUser;
@@ -72,7 +72,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
db.ref("email").withSchema(TableName.Users) db.ref("email").withSchema(TableName.Users)
) )
.whereIn("email", emails) .whereIn("email", emails)
.where({ ghost: false }); .where({ isGhost: false });
return members.map(({ userId, email, ...data }) => ({ return members.map(({ userId, email, ...data }) => ({
...data, ...data,
user: { id: userId, email } user: { id: userId, email }

View File

@@ -16,11 +16,11 @@ import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { createWsMembers } from "@app/lib/project";
import { ActorType } from "../auth/auth-type"; import { ActorType } from "../auth/auth-type";
import { TOrgDALFactory } from "../org/org-dal"; import { TOrgDALFactory } from "../org/org-dal";
import { TProjectDALFactory } from "../project/project-dal"; import { TProjectDALFactory } from "../project/project-dal";
import { createWsMembers } from "../project/project-fns";
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal"; import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "../project-key/project-key-dal"; import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
import { TProjectRoleDALFactory } from "../project-role/project-role-dal"; import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
@@ -320,7 +320,7 @@ export const projectMembershipServiceFactory = ({
const membershipUser = await userDAL.findUserByProjectMembershipId(membershipId); const membershipUser = await userDAL.findUserByProjectMembershipId(membershipId);
if (membershipUser?.ghost) { if (membershipUser?.isGhost) {
throw new BadRequestError({ throw new BadRequestError({
message: "Unauthorized member update", message: "Unauthorized member update",
name: "Update project membership" name: "Update project membership"
@@ -365,7 +365,7 @@ export const projectMembershipServiceFactory = ({
const member = await userDAL.findUserByProjectMembershipId(membershipId); const member = await userDAL.findUserByProjectMembershipId(membershipId);
if (member?.ghost) { if (member?.isGhost) {
throw new BadRequestError({ throw new BadRequestError({
message: "Unauthorized member delete", message: "Unauthorized member delete",
name: "Delete project membership" name: "Delete project membership"

View File

@@ -60,7 +60,7 @@ export const projectDALFactory = (db: TDbClient) => {
.where({ projectId }) .where({ projectId })
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.select(selectAllTableCols(TableName.Users)) .select(selectAllTableCols(TableName.Users))
.where({ ghost: true }) .where({ isGhost: true })
.first(); .first();
return ghostUser; return ghostUser;
} catch (error) { } catch (error) {

View File

@@ -70,7 +70,7 @@ export const superAdminServiceFactory = ({
lastName, lastName,
email, email,
superAdmin: true, superAdmin: true,
ghost: false, isGhost: false,
isAccepted: true, isAccepted: true,
authMethods: [AuthMethod.EMAIL] authMethods: [AuthMethod.EMAIL]
}, },

View File

@@ -23,7 +23,7 @@ export const userDALFactory = (db: TDbClient) => {
const findUserEncKeyByEmail = async (email: string) => { const findUserEncKeyByEmail = async (email: string) => {
try { try {
return await db(TableName.Users) return await db(TableName.Users)
.where({ email, ghost: false }) .where({ email, isGhost: false })
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`) .join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
.first(); .first();
} catch (error) { } catch (error) {

View File

@@ -5,6 +5,7 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
import { useProjectPermission } from "@app/context"; import { useProjectPermission } from "@app/context";
import { useGetUpgradeProjectStatus, useUpgradeProject } from "@app/hooks/api"; import { useGetUpgradeProjectStatus, useUpgradeProject } from "@app/hooks/api";
import { Workspace } from "@app/hooks/api/types"; import { Workspace } from "@app/hooks/api/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { Alert } from "../Alert"; import { Alert } from "../Alert";
import { Button } from "../Button"; import { Button } from "../Button";
@@ -55,7 +56,7 @@ export const UpgradeProjectAlert = ({ project }: UpgradeProjectAlertProps): JSX.
let interval: NodeJS.Timeout | null = null; let interval: NodeJS.Timeout | null = null;
if (membership.role === "admin") { if (membership.role === "admin") {
if (project.version === "v1") { if (project.version === ProjectVersion.V1) {
getLatestProjectStatus(); getLatestProjectStatus();
} }
@@ -72,7 +73,7 @@ export const UpgradeProjectAlert = ({ project }: UpgradeProjectAlertProps): JSX.
} }
interval = setInterval(() => { interval = setInterval(() => {
if (project.version === "v1") { if (project.version === ProjectVersion.V1) {
getLatestProjectStatus(); getLatestProjectStatus();
} }
}, 5_000); }, 5_000);
@@ -92,7 +93,7 @@ export const UpgradeProjectAlert = ({ project }: UpgradeProjectAlertProps): JSX.
(currentStatus === null && statusIsLoading)) && (currentStatus === null && statusIsLoading)) &&
projectStatus?.status !== "FAILED"; projectStatus?.status !== "FAILED";
if (project.version !== "v1") return null; if (project.version !== ProjectVersion.V1) return null;
if (membership.role !== "admin") return null; if (membership.role !== "admin") return null;
return ( return (

View File

@@ -1,9 +1,14 @@
export enum ProjectVersion {
V1 = 1,
V2 = 2
}
export type Workspace = { export type Workspace = {
__v: number; __v: number;
id: string; id: string;
name: string; name: string;
orgId: string; orgId: string;
version: "v1" | "v2"; version: ProjectVersion;
upgradeStatus: string | null; upgradeStatus: string | null;
autoCapitalization: boolean; autoCapitalization: boolean;
environments: WorkspaceEnv[]; environments: WorkspaceEnv[];

View File

@@ -17,6 +17,7 @@ import {
useUpdateBotActiveStatus useUpdateBotActiveStatus
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IntegrationAuth } from "@app/hooks/api/types"; import { IntegrationAuth } from "@app/hooks/api/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { CloudIntegrationSection } from "./components/CloudIntegrationSection"; import { CloudIntegrationSection } from "./components/CloudIntegrationSection";
import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSection"; import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSection";
@@ -92,7 +93,7 @@ export const IntegrationsPage = withProjectPermission(
isIntegrationsAuthorizedEmpty && isIntegrationsAuthorizedEmpty &&
isIntegrationsEmpty isIntegrationsEmpty
) { ) {
if (bot?.id && currentWorkspace?.version === "v1") if (bot?.id && currentWorkspace?.version === ProjectVersion.V1)
updateBotActiveStatusSync({ updateBotActiveStatusSync({
isActive: false, isActive: false,
botId: bot.id, botId: bot.id,
@@ -113,7 +114,7 @@ export const IntegrationsPage = withProjectPermission(
if (!selectedCloudIntegration) return; if (!selectedCloudIntegration) return;
try { try {
if (bot && !bot.isActive && currentWorkspace?.version === "v1") { if (bot && !bot.isActive && currentWorkspace?.version === ProjectVersion.V1) {
const botKey = generateBotKey(bot.publicKey, latestWsKey!); const botKey = generateBotKey(bot.publicKey, latestWsKey!);
await updateBotActiveStatus({ await updateBotActiveStatus({
workspaceId, workspaceId,

View File

@@ -55,6 +55,7 @@ import {
useUploadWsKey useUploadWsKey
} from "@app/hooks/api"; } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
const addMemberFormSchema = z.object({ const addMemberFormSchema = z.object({
orgMembershipId: z.string().trim() orgMembershipId: z.string().trim()
@@ -117,14 +118,14 @@ export const MemberListTab = () => {
if (!orgUser) return; if (!orgUser) return;
try { try {
if (currentWorkspace.version === "v1") { if (currentWorkspace.version === ProjectVersion.V1) {
await addUserToWorkspace({ await addUserToWorkspace({
workspaceId, workspaceId,
userPrivateKey, userPrivateKey,
decryptKey: wsKey, decryptKey: wsKey,
members: [{ orgMembershipId, userPublicKey: orgUser.user.publicKey }] members: [{ orgMembershipId, userPublicKey: orgUser.user.publicKey }]
}); });
} else if (currentWorkspace.version === "v2") { } else if (currentWorkspace.version === ProjectVersion.V2) {
await addUserToWorkspaceNonE2EE({ await addUserToWorkspaceNonE2EE({
projectId: workspaceId, projectId: workspaceId,
emails: [orgUser.user.email] emails: [orgUser.user.email]

View File

@@ -40,6 +40,7 @@ import {
useGetUserWsKey, useGetUserWsKey,
useUpdateSecretV3 useUpdateSecretV3
} from "@app/hooks/api"; } from "@app/hooks/api";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs"; import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection"; import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection";
@@ -317,7 +318,9 @@ export const SecretOverviewPage = () => {
</p> </p>
</div> </div>
{currentWorkspace?.version === "v1" && <UpgradeProjectAlert project={currentWorkspace} />} {currentWorkspace?.version === ProjectVersion.V1 && (
<UpgradeProjectAlert project={currentWorkspace} />
)}
<div className="mt-8 flex items-center justify-between"> <div className="mt-8 flex items-center justify-between">
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} /> <FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
<div className="w-80"> <div className="w-80">

View File

@@ -6,6 +6,7 @@ import {
import { Alert, AlertDescription, Checkbox } from "@app/components/v2"; import { Alert, AlertDescription, Checkbox } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useGetUserWsKey, useGetWorkspaceBot, useUpdateBotActiveStatus } from "@app/hooks/api"; import { useGetUserWsKey, useGetWorkspaceBot, useUpdateBotActiveStatus } from "@app/hooks/api";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
export const E2EESection = () => { export const E2EESection = () => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@@ -78,7 +79,7 @@ export const E2EESection = () => {
if (!currentWorkspace) return null; if (!currentWorkspace) return null;
return bot && currentWorkspace.version === "v1" ? ( return bot && currentWorkspace.version === ProjectVersion.V1 ? (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-3 text-xl font-semibold">End-to-End Encryption</p> <p className="mb-3 text-xl font-semibold">End-to-End Encryption</p>
<p className="mb-8 text-gray-400"> <p className="mb-8 text-gray-400">