feat(infisical-pg): completed checklist run for dashboard

This commit is contained in:
Akhil Mohan
2024-01-09 23:11:39 +05:30
parent 16f0ac6d43
commit 0d3f09d668
28 changed files with 341 additions and 433 deletions

View File

@@ -12,8 +12,8 @@ export const createJunctionTable = (
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
table.uuid(`${table1Name}Id`).unsigned().notNullable(); // Foreign key for table1
table.uuid(`${table2Name}Id`).unsigned().notNullable(); // Foreign key for table2
table.foreign(`${table1Name}Id`).references("id").inTable(table2Name);
table.foreign(`${table2Name}Id`).references("id").inTable(table1Name);
table.foreign(`${table1Name}Id`).references("id").inTable(table1Name);
table.foreign(`${table2Name}Id`).references("id").inTable(table2Name);
});
// one time logic

View File

@@ -413,6 +413,8 @@ interface UpdateEnvironmentEvent {
newName: string;
oldSlug: string;
newSlug: string;
oldPos: number;
newPos: number;
};
}

View File

@@ -88,6 +88,7 @@ export const sarSecretDalFactory = (db: TDbClient) => {
secret: el.secretId
? {
id: el.secretId,
version: orgSecVersion,
secretBlindIndex: orgSecBlindIndex,
secretKeyIV: orgSecKeyIV,
secretKeyTag: orgSecKeyTag,

View File

@@ -5,20 +5,20 @@ import {
SecretEncryptionAlgo,
SecretKeyEncoding,
SecretType,
TSaRequestSecretsInsert,
TSecrets
TSaRequestSecretsInsert
} from "@app/db/schemas";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { groupBy, pick } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ActorType } from "@app/services/auth/auth-type";
import { TSecretBlindIndexDalFactory } from "@app/services/secret/secret-blind-index-dal";
import { TSecretDalFactory } from "@app/services/secret/secret-dal";
import { generateSecretBlindIndexBySalt } from "@app/services/secret/secret-service";
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal";
import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
import { TSarReviewerDalFactory } from "./sar-reviewer-dal";
import { TSarSecretDalFactory } from "./sar-secret-dal";
import { TSecretApprovalRequestDalFactory } from "./secret-approval-request-dal";
@@ -38,12 +38,20 @@ import {
type TSecretApprovalRequestServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
secretApprovalRequestDal: TSecretApprovalRequestDalFactory;
secretDal: TSecretDalFactory;
sarSecretDal: TSarSecretDalFactory;
sarReviewerDal: TSarReviewerDalFactory;
secretVersionDal: Pick<TSecretVersionDalFactory, "findLatestVersionMany" | "insertMany">;
folderDal: Pick<TSecretFolderDalFactory, "findBySecretPath">;
secretBlindIndexDal: Pick<TSecretBlindIndexDalFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDal: Pick<TSecretVersionDalFactory, "findLatestVersionMany">;
secretService: Pick<
TSecretServiceFactory,
| "fnSecretBulkInsert"
| "fnSecretBulkUpdate"
| "fnSecretBlindIndexCheck"
| "fnSecretBulkDelete"
| "fnSecretBlindIndexCheckV2"
>;
};
export type TSecretApprovalRequestServiceFactory = ReturnType<
@@ -53,12 +61,13 @@ export type TSecretApprovalRequestServiceFactory = ReturnType<
export const secretApprovalRequestServiceFactory = ({
secretApprovalRequestDal,
folderDal,
secretDal,
sarReviewerDal,
sarSecretDal,
secretVersionDal,
secretBlindIndexDal,
permissionService
permissionService,
snapshotService,
secretService,
secretVersionDal
}: TSecretApprovalRequestServiceFactoryDep) => {
const requestCount = async ({ projectId, actor, actorId }: TApprovalRequestCountDTO) => {
const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId);
@@ -194,11 +203,11 @@ export const secretApprovalRequestServiceFactory = ({
throw new BadRequestError({ message: "Secret approval request not found" });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const { policy, folderId } = secretApprovalRequest;
const { policy, folderId, projectId } = secretApprovalRequest;
const { membership } = await permissionService.getProjectPermission(
ActorType.USER,
actorId,
secretApprovalRequest.projectId
projectId
);
if (
membership.role !== ProjectMembershipRole.Admin &&
@@ -225,17 +234,11 @@ export const secretApprovalRequestServiceFactory = ({
const conflicts: Array<{ secretId: string; op: CommitType }> = [];
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Create);
if (secretCreationCommits.length) {
const conflictedSecrets = await secretDal.findByBlindIndexes(
folderId,
secretCreationCommits.map(({ secretBlindIndex }) => ({
type: SecretType.Shared,
blindIndex: secretBlindIndex
}))
);
const conflictGroupByBlindIndex = conflictedSecrets.reduce<Record<string, boolean>>(
(prev, curr) => ({ ...prev, [curr.secretBlindIndex || ""]: true }),
{}
);
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } =
await secretService.fnSecretBlindIndexCheckV2({
folderId,
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => ({ secretBlindIndex }))
});
secretCreationCommits
.filter(({ secretBlindIndex }) => conflictGroupByBlindIndex[secretBlindIndex || ""])
.forEach((el) => {
@@ -248,22 +251,16 @@ export const secretApprovalRequestServiceFactory = ({
let secretUpdationCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Update);
if (secretUpdationCommits.length) {
const conflictedByNewBlindIndex = await secretDal.findByBlindIndexes(
folderId,
secretUpdationCommits
.filter(
({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex
)
.map(({ secretBlindIndex }) => ({
type: SecretType.Shared,
blindIndex: secretBlindIndex
}))
);
const conflictGroupByBlindIndex = conflictedByNewBlindIndex.reduce<Record<string, boolean>>(
(prev, curr) =>
curr?.secretBlindIndex ? { ...prev, [curr.secretBlindIndex]: true } : prev,
{}
);
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } =
await secretService.fnSecretBlindIndexCheckV2({
folderId,
inputSecrets: secretUpdationCommits
.filter(
({ secretBlindIndex, secret }) =>
secret && secret.secretBlindIndex !== secretBlindIndex
)
.map(({ secretBlindIndex }) => ({ secretBlindIndex }))
});
secretUpdationCommits
.filter(
({ secretBlindIndex, secretId }) =>
@@ -284,122 +281,78 @@ export const secretApprovalRequestServiceFactory = ({
({ op }) => op === CommitType.Delete
);
const mergeStatus = await secretDal.transaction(async (tx) => {
const mergeStatus = await secretApprovalRequestDal.transaction(async (tx) => {
const newSecrets = secretCreationCommits.length
? await secretDal.insertMany(
secretCreationCommits.map(
({
secretBlindIndex,
metadata,
secretKeyIV,
secretKeyTag,
secretKeyCiphertext,
secretValueIV,
secretValueTag,
secretValueCiphertext,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretReminderNote,
secretReminderRepeatDays
}) => ({
secretBlindIndex,
metadata,
secretKeyIV,
secretKeyTag,
secretKeyCiphertext,
secretValueIV,
secretValueTag,
secretValueCiphertext,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretReminderNote,
secretReminderRepeatDays,
version: 1,
folderId,
type: SecretType.Shared,
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8
})
),
tx
)
? await secretService.fnSecretBulkInsert({
tx,
folderId,
inputSecrets: secretCreationCommits.map((el) => ({
...pick(el, [
"secretCommentCiphertext",
"secretCommentTag",
"secretCommentIV",
"secretValueIV",
"secretValueTag",
"secretValueCiphertext",
"secretKeyCiphertext",
"secretKeyTag",
"secretKeyIV",
"metadata",
"skipMultilineEncoding",
"secretReminderNote",
"secretReminderRepeatDays",
"version",
"algorithm",
"keyEncoding",
"secretBlindIndex"
]),
type: SecretType.Shared
}))
})
: [];
const updatedSecrets = secretUpdationCommits.length
? await secretDal.bulkUpdate(
secretUpdationCommits.map(
({
version,
secretId,
secretBlindIndex,
metadata,
secretKeyIV,
secretKeyTag,
secretKeyCiphertext,
secretValueIV,
secretValueTag,
secretValueCiphertext,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretReminderNote,
secretReminderRepeatDays
}) => ({
folderId,
version: (version || 0) + 1,
id: secretId as string,
type: SecretType.Shared,
secretBlindIndex,
metadata,
secretKeyIV,
secretKeyTag,
secretKeyCiphertext,
secretValueIV,
secretValueTag,
secretValueCiphertext,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretReminderNote,
secretReminderRepeatDays
})
),
tx
)
? await secretService.fnSecretBulkUpdate({
folderId,
projectId,
tx,
inputSecrets: secretUpdationCommits.map((el) => ({
...pick(el, [
"secretCommentCiphertext",
"secretCommentTag",
"secretCommentIV",
"secretValueIV",
"secretValueTag",
"secretValueCiphertext",
"secretKeyCiphertext",
"secretKeyTag",
"secretKeyIV",
"metadata",
"skipMultilineEncoding",
"secretReminderNote",
"secretReminderRepeatDays",
"version",
"algorithm",
"keyEncoding",
"secretBlindIndex"
]),
version: (el.secret?.version || 0) + 1,
id: el.secretId,
type: SecretType.Shared
}))
})
: [];
const deletedSecret = secretDeletionCommits.length
? await secretDal.deleteMany(
secretDeletionCommits.map(({ secretBlindIndex }) => ({
blindIndex: secretBlindIndex,
type: SecretType.Shared
})),
? await secretService.fnSecretBulkDelete({
projectId,
folderId,
actorId,
tx
)
: [];
if (newSecrets.length || updatedSecrets.length) {
await secretVersionDal.insertMany(
newSecrets
.map(({ id, updatedAt, createdAt, ...el }) => ({
...el,
secretId: id
tx,
actorId: "",
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => ({
secretBlindIndex,
type: SecretType.Shared
}))
.concat(
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({
...el,
secretId: id
}))
),
tx
);
}
})
: [];
const updatedSecretApproval = await secretApprovalRequestDal.updateById(
secretApprovalRequest.id,
{
@@ -415,6 +368,7 @@ export const secretApprovalRequestServiceFactory = ({
approval: updatedSecretApproval
};
});
await snapshotService.performSnapshot(folderId);
return mergeStatus;
};
@@ -444,42 +398,27 @@ export const secretApprovalRequestServiceFactory = ({
throw new BadRequestError({ message: "Folder not found", name: "GenSecretApproval" });
const folderId = folder.id;
const blindIndexDoc = await secretBlindIndexDal.findOne({ projectId });
if (!blindIndexDoc)
const blindIndexCfg = await secretBlindIndexDal.findOne({ projectId });
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
const commits: Omit<TSaRequestSecretsInsert, "requestId">[] = [];
// for created secret approval change
const createdSecrets = data[CommitType.Create];
if (createdSecrets && createdSecrets?.length) {
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point
const secretBlindIndexes = await Promise.all(
createdSecrets.map(({ secretName }) =>
generateSecretBlindIndexBySalt(secretName, blindIndexDoc)
)
).then((blindIndexes) =>
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
// eslint-disable-next-line
prev[createdSecrets[i].secretName] = curr;
secretBlindIndexToKey[curr] = createdSecrets[i].secretName;
return prev;
}, {})
);
const exists = await secretDal.findByBlindIndexes(
const { keyName2BlindIndex } = await secretService.fnSecretBlindIndexCheck({
inputSecrets: createdSecrets,
folderId,
createdSecrets.map(({ secretName }) => ({
blindIndex: secretBlindIndexes[secretName],
type: SecretType.Shared
}))
);
if (exists.length) throw new BadRequestError({ message: "Secret already exist" });
isNew: true,
blindIndexCfg
});
commits.push(
...createdSecrets.map((el) => ({
...createdSecrets.map(({ secretName, ...el }) => ({
...el,
op: CommitType.Create as const,
version: 0,
secretBlindIndex: secretBlindIndexes[el.secretName],
secretBlindIndex: keyName2BlindIndex[secretName],
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.BASE64
}))
@@ -491,83 +430,49 @@ export const secretApprovalRequestServiceFactory = ({
// get all blind index
// Find all those secrets
// if not throw not found
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point
const secretBlindIndexes = await Promise.all(
updatedSecrets.map(({ secretName }) =>
generateSecretBlindIndexBySalt(secretName, blindIndexDoc)
)
).then((blindIndexes) =>
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
// eslint-disable-next-line
prev[updatedSecrets[i].secretName] = curr;
secretBlindIndexToKey[curr] = updatedSecrets[i].secretName;
return prev;
}, {})
);
const secretsToBeUpdated = await secretDal.findByBlindIndexes(
folderId,
updatedSecrets.map(({ secretName }) => ({
blindIndex: secretBlindIndexes[secretName],
type: SecretType.Shared
}))
);
if (secretsToBeUpdated.length !== updatedSecrets.length)
throw new BadRequestError({ message: "Secret not found" });
const { keyName2BlindIndex, secrets: secretsToBeUpdated } =
await secretService.fnSecretBlindIndexCheck({
inputSecrets: updatedSecrets,
folderId,
isNew: false,
blindIndexCfg
});
// now find any secret that needs to update its name
// same process as above
const nameUpdatedSecrets = updatedSecrets.filter(({ newSecretName }) =>
Boolean(newSecretName)
);
const newSecretBlindIndexes = await Promise.all(
nameUpdatedSecrets.map(({ newSecretName }) =>
generateSecretBlindIndexBySalt(newSecretName as string, blindIndexDoc)
)
).then((blindIndexes) =>
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
// eslint-disable-next-line
prev[nameUpdatedSecrets[i].secretName] = curr;
return prev;
}, {})
);
const secretsWithNewName = await secretDal.findByBlindIndexes(
folderId,
nameUpdatedSecrets.map(({ newSecretName }) => ({
blindIndex: newSecretBlindIndexes[newSecretName as string],
type: SecretType.Shared
}))
);
if (secretsWithNewName.length)
throw new BadRequestError({ message: "Secret with new name already exist" });
const { keyName2BlindIndex: newKeyName2BlindIndex } =
await secretService.fnSecretBlindIndexCheck({
inputSecrets: nameUpdatedSecrets,
folderId,
isNew: true,
blindIndexCfg
});
const secretsGroupedByBlindIndex = secretsToBeUpdated.reduce<Record<string, TSecrets>>(
(prev, curr) => {
// eslint-disable-next-line
if (curr.secretBlindIndex) prev[curr.secretBlindIndex] = curr;
return prev;
},
{}
);
const secsGroupedByBlindIndex = groupBy(secretsToBeUpdated, (el) => el.secretBlindIndex);
const updatedSecretIds = updatedSecrets.map(
(el) => secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].id
(el) => secsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id
);
const latestSecretVersions = await secretVersionDal.findLatestVersionMany(
folderId,
updatedSecretIds
);
commits.push(
...updatedSecrets.map((el) => {
const secretId = secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].id;
...updatedSecrets.map(({ newSecretName, secretName, ...el }) => {
const secretId = secsGroupedByBlindIndex[keyName2BlindIndex[secretName]][0].id;
return {
...latestSecretVersions[secretId],
...el,
op: CommitType.Update as const,
secret: secretId,
secretVersion: latestSecretVersions[secretId].id,
...el,
secretBlindIndex:
newSecretBlindIndexes?.[el.secretName] || secretBlindIndexes[el.secretName],
version: secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].version || 1
newSecretName && newKeyName2BlindIndex[newSecretName]
? newKeyName2BlindIndex?.[secretName]
: keyName2BlindIndex[secretName],
version: secsGroupedByBlindIndex[keyName2BlindIndex[secretName]][0].version || 1
};
})
);
@@ -578,39 +483,15 @@ export const secretApprovalRequestServiceFactory = ({
// get all blind index
// Find all those secrets
// if not throw not found
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point
const secretBlindIndexes = await Promise.all(
deletedSecrets.map(({ secretName }) =>
generateSecretBlindIndexBySalt(secretName, blindIndexDoc)
)
).then((blindIndexes) =>
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => {
// eslint-disable-next-line
prev[deletedSecrets[i].secretName] = curr;
secretBlindIndexToKey[curr] = deletedSecrets[i].secretName;
return prev;
}, {})
);
// not find those secrets. if any of them not found throw an not found error
const secretsToBeDeleted = await secretDal.findByBlindIndexes(
const { keyName2BlindIndex, secrets } = await secretService.fnSecretBlindIndexCheck({
inputSecrets: deletedSecrets,
folderId,
deletedSecrets.map(({ secretName }) => ({
blindIndex: secretBlindIndexes[secretName],
type: SecretType.Shared
}))
);
if (secretsToBeDeleted.length !== deletedSecrets.length)
throw new BadRequestError({ message: "Secret not found" });
const secretsGroupedByBlindIndex = secretsToBeDeleted.reduce<Record<string, TSecrets>>(
(prev, curr) => {
// eslint-disable-next-line
if (curr.secretBlindIndex) prev[curr.secretBlindIndex] = curr;
return prev;
},
{}
);
isNew: false,
blindIndexCfg
});
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => i.secretBlindIndex);
const deletedSecretIds = deletedSecrets.map(
(el) => secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].id
(el) => secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id
);
const latestSecretVersions = await secretVersionDal.findLatestVersionMany(
folderId,
@@ -618,7 +499,7 @@ export const secretApprovalRequestServiceFactory = ({
);
commits.push(
...deletedSecrets.map((el) => {
const secretId = secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].id;
const secretId = secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id;
return {
op: CommitType.Delete as const,
...latestSecretVersions[secretId],

View File

@@ -173,16 +173,6 @@ export const registerRoutes = async (
userDal,
samlConfigDal
});
const sarService = secretApprovalRequestServiceFactory({
permissionService,
folderDal,
secretDal,
sarSecretDal,
sarReviewerDal,
secretVersionDal,
secretBlindIndexDal,
secretApprovalRequestDal
});
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal });
const userService = userServiceFactory({ userDal });
@@ -282,6 +272,17 @@ export const registerRoutes = async (
});
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDal });
const sarService = secretApprovalRequestServiceFactory({
permissionService,
folderDal,
sarSecretDal,
sarReviewerDal,
secretVersionDal,
secretBlindIndexDal,
secretApprovalRequestDal,
secretService,
snapshotService
});
const secretRotationQueue = secretRotationQueueFactory({
secretRotationDal,
queue: queueService,

View File

@@ -52,7 +52,6 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { user, token } = await server.services.org.verifyUserToOrg({
orgId: req.body.organizationId,

View File

@@ -4,7 +4,6 @@ import {
IncidentContactsSchema,
OrganizationsSchema,
OrgMembershipsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -67,7 +66,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
}).merge(z.object({ publicKey: z.string().nullable() }))
})
)
.omit({ createdAt: true, updatedAt: true })

View File

@@ -63,7 +63,8 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}),
body: z.object({
slug: z.string().trim().optional(),
name: z.string().trim().optional()
name: z.string().trim().optional(),
position: z.number().optional()
}),
response: {
200: z.object({
@@ -91,8 +92,10 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
metadata: {
oldName: old.name,
oldSlug: old.slug,
newName: old.name,
newSlug: old.slug
oldPos: old.position,
newName: environment.name,
newSlug: environment.slug,
newPos: environment.position
}
}
});

View File

@@ -1,6 +1,11 @@
import { z } from "zod";
import { SecretApprovalRequestsSchema, SecretsSchema, SecretType } from "@app/db/schemas";
import {
SecretApprovalRequestsSchema,
SecretsSchema,
SecretTagsSchema,
SecretType
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -22,7 +27,18 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
secrets: SecretsSchema.omit({ secretBlindIndex: true })
.merge(
z.object({
tags: SecretTagsSchema.pick({
id: true,
slug: true,
name: true,
color: true
}).array()
})
)
.array()
})
}
},

View File

@@ -61,7 +61,7 @@ export const identityServiceFactory = ({
};
const updateIdentity = async ({ id, role, name, actor, actorId }: TUpdateIdentityDTO) => {
const identityOrgMembership = await identityOrgMembershipDal.findById(id);
const identityOrgMembership = await identityOrgMembershipDal.findOne({ identityId: id });
if (!identityOrgMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
@@ -97,7 +97,9 @@ export const identityServiceFactory = ({
}
const identity = await identityDal.transaction(async (tx) => {
const newIdentity = await identityDal.updateById(id, { name }, tx);
const newIdentity = name
? await identityDal.updateById(id, { name }, tx)
: await identityDal.findById(id, tx);
if (role) {
await identityOrgMembershipDal.update(
{ identityId: id },
@@ -115,7 +117,7 @@ export const identityServiceFactory = ({
};
const deleteIdentity = async ({ actorId, actor, id }: TDeleteIdentityDTO) => {
const identityOrgMembership = await identityOrgMembershipDal.findById(id);
const identityOrgMembership = await identityOrgMembershipDal.findOne({ identityId: id });
if (!identityOrgMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${id}` });

View File

@@ -46,7 +46,7 @@ export const orgDalFactory = (db: TDbClient) => {
const members = await db(TableName.OrgMembership)
.where({ orgId })
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.join(
.leftJoin(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
`${TableName.Users}.id`

View File

@@ -253,6 +253,7 @@ export const orgServiceFactory = ({
await orgDal.createMembership({
inviteEmail: inviteeEmail,
orgId,
userId: user.id,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
});

View File

@@ -26,38 +26,49 @@ export const projectEnvDalFactory = (db: TDbClient) => {
const findLastEnvPosition = async (projectId: string, tx?: Knex) => {
const lastPos = await (tx || db)(TableName.Environment)
.where({ projectId })
.max("position")
.max({ position: "position" })
.first();
return lastPos?.position || 1;
return lastPos?.position || 0;
};
const incrementLastPosition = async (
const updateAllPosition = async (
projectId: string,
startPos: number,
increment = 1,
pos: number,
targetPos: number,
tx?: Knex
) =>
(tx || db)(TableName.Environment)
.where("projectId", projectId)
.where("postion", ">=", startPos)
.increment("position", increment);
) => {
try {
if (targetPos === -1) {
// this means delete
await (tx || db)(TableName.Environment)
.where({ projectId })
.andWhere("position", ">", pos)
.decrement("position", 1);
return;
}
const decrementLastPosition = async (
projectId: string,
startPos: number,
decrement = 1,
tx?: Knex
) =>
(tx || db)(TableName.Environment)
.where("projectId", projectId)
.where("postion", ">", startPos)
.decrement("position", decrement);
if (targetPos > pos) {
await (tx || db)(TableName.Environment)
.where({ projectId })
.where("position", "<=", targetPos)
.andWhere("position", ">", pos)
.decrement("position", 1);
} else {
await (tx || db)(TableName.Environment)
.where({ projectId })
.where("position", ">=", targetPos)
.andWhere("position", "<", pos)
.increment("position", 1);
}
} catch (error) {
throw new DatabaseError({ error, name: "UpdateEnvPos" });
}
};
return {
...projectEnvOrm,
findBySlugs,
findLastEnvPosition,
decrementLastPosition,
incrementLastPosition
updateAllPosition
};
};

View File

@@ -8,7 +8,7 @@ import {
import { BadRequestError } from "@app/lib/errors";
import { TProjectEnvDalFactory } from "./project-env-dal";
import { TCreateEnvDTO, TDeleteEnvDTO, TReorderEnvDTO, TUpdateEnvDTO } from "./project-env-types";
import { TCreateEnvDTO, TDeleteEnvDTO, TUpdateEnvDTO } from "./project-env-types";
type TProjectEnvServiceFactoryDep = {
projectEnvDal: TProjectEnvDalFactory;
@@ -38,7 +38,7 @@ export const projectEnvServiceFactory = ({
const env = await projectEnvDal.transaction(async (tx) => {
const lastPos = await projectEnvDal.findLastEnvPosition(projectId, tx);
const doc = await projectEnvDal.create({ slug, name, projectId, position: lastPos }, tx);
const doc = await projectEnvDal.create({ slug, name, projectId, position: lastPos + 1 }, tx);
return doc;
});
return env;
@@ -50,7 +50,8 @@ export const projectEnvServiceFactory = ({
actor,
actorId,
name,
id
id,
position
}: TUpdateEnvDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
@@ -71,7 +72,12 @@ export const projectEnvServiceFactory = ({
}
}
const env = await projectEnvDal.updateById(oldEnv.id, { name, slug });
const env = await projectEnvDal.transaction(async (tx) => {
if (position) {
await projectEnvDal.updateAllPosition(projectId, oldEnv.position, position, tx);
}
return projectEnvDal.updateById(oldEnv.id, { name, slug, position }, tx);
});
return { environment: env, old: oldEnv };
};
@@ -90,36 +96,15 @@ export const projectEnvServiceFactory = ({
name: "Re-order env"
});
await projectEnvDal.decrementLastPosition(projectId, doc.position, 1, tx);
await projectEnvDal.updateAllPosition(projectId, doc.position, -1, tx);
return doc;
});
return env;
};
const reorderEnvironment = async ({ projectId, id, actorId, actor, pos }: TReorderEnvDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments
);
const [env] = await projectEnvDal.transaction(async (tx) => {
await projectEnvDal.incrementLastPosition(projectId, pos, 1, tx);
return projectEnvDal.update({ id, projectId }, { position: pos }, tx);
});
if (!env)
throw new BadRequestError({
message: "Env doesn't exist",
name: "Re-order env"
});
return env;
};
return {
createEnvironment,
updateEnvironment,
deleteEnvironment,
reorderEnvironment
deleteEnvironment
};
};

View File

@@ -9,6 +9,7 @@ export type TUpdateEnvDTO = {
id: string;
name?: string;
slug?: string;
position?: number;
} & TProjectPermission;
export type TDeleteEnvDTO = {

View File

@@ -1,9 +1,16 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { SecretType, TableName, TSecrets, TSecretsInsert, TSecretsUpdate } from "@app/db/schemas";
import {
SecretsSchema,
SecretType,
TableName,
TSecrets,
TSecretsInsert,
TSecretsUpdate
} from "@app/db/schemas";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { mergeOneToManyRelation, ormify, selectAllTableCols } from "@app/lib/knex";
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
export type TSecretDalFactory = ReturnType<typeof secretDalFactory>;
@@ -90,19 +97,23 @@ export const secretDalFactory = (db: TDbClient) => {
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
const formatedSecs = mergeOneToManyRelation(
secs,
"id",
({ tagColor, tagId, tagName, tagSlug, ...data }) => data,
({ tagSlug: slug, tagName: name, tagId: id, tagColor: color }) => ({
id,
slug,
name,
color
}),
"tags"
);
return formatedSecs;
return sqlNestRelationships({
data: secs,
key: "id",
parentMapper: (el) => SecretsSchema.parse(el),
childrenMapper: [
{
key: "tagId",
label: "tags" as const,
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
id,
color,
slug,
name
})
}
]
});
} catch (error) {
throw new DatabaseError({ error, name: "get all secret" });
}

View File

@@ -29,6 +29,7 @@ import {
TDeleteBulkSecretDTO,
TDeleteSecretDTO,
TFnSecretBlindIndexCheck,
TFnSecretBlindIndexCheckV2,
TFnSecretBulkDelete,
TFnSecretBulkInsert,
TFnSecretBulkUpdate,
@@ -109,8 +110,8 @@ export const secretServiceFactory = ({
const newSecretGroupByBlindIndex = groupBy(newSecrets, (item) => item.secretBlindIndex);
const newSecretTags = inputSecrets.flatMap(({ tags: secretTags = [], secretBlindIndex }) =>
secretTags.map((tag) => ({
[`${TableName.SecretTag}Id`]: tag,
[`${TableName.Secret}Id`]: newSecretGroupByBlindIndex[secretBlindIndex][0].id
[`${TableName.SecretTag}Id` as const]: tag,
[`${TableName.Secret}Id` as const]: newSecretGroupByBlindIndex[secretBlindIndex][0].id
}))
);
if (newSecretTags.length) {
@@ -147,8 +148,8 @@ export const secretServiceFactory = ({
);
const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], id }) =>
secretTags.map((tag) => ({
[`${TableName.SecretTag}Id`]: tag,
[`${TableName.Secret}Id`]: id
[`${TableName.SecretTag}Id` as const]: tag,
[`${TableName.Secret}Id` as const]: id
}))
);
await secretTagDal.saveTagsToSecret(newSecretTags, tx);
@@ -229,6 +230,29 @@ export const secretServiceFactory = ({
return { blindIndex2KeyName, keyName2BlindIndex, secrets };
};
// this is used when secret blind index already exist
// mainly for secret approval
const fnSecretBlindIndexCheckV2 = async ({
inputSecrets,
folderId,
userId
}: TFnSecretBlindIndexCheckV2) => {
if (inputSecrets.some(({ type }) => type === SecretType.Personal) && !userId) {
throw new BadRequestError({ message: "Missing user id for personal secret" });
}
const secrets = await secretDal.findByBlindIndexes(
folderId,
inputSecrets.map(({ secretBlindIndex, type }) => ({
blindIndex: secretBlindIndex,
type: type || SecretType.Shared
})),
userId
);
const secsGroupedByBlindIndex = groupBy(secrets, (i) => i.secretBlindIndex);
return { secsGroupedByBlindIndex, secrets };
};
const createSecret = async ({
path,
actor,
@@ -540,7 +564,7 @@ export const secretServiceFactory = ({
fnSecretBulkInsert({
inputSecrets: inputSecrets.map(({ secretName, ...el }) => ({
...el,
version:0,
version: 0,
secretBlindIndex: keyName2BlindIndex[secretName],
type: SecretType.Shared,
algorithm: SecretEncryptionAlgo.AES_256_GCM,
@@ -717,6 +741,7 @@ export const secretServiceFactory = ({
fnSecretBulkDelete,
fnSecretBulkUpdate,
fnSecretBlindIndexCheck,
fnSecretBulkInsert
fnSecretBulkInsert,
fnSecretBlindIndexCheckV2
};
};

View File

@@ -153,3 +153,10 @@ export type TFnSecretBlindIndexCheck = {
inputSecrets: Array<{ secretName: string; type?: SecretType }>;
isNew: boolean;
};
// when blind index is already present
export type TFnSecretBlindIndexCheckV2 = {
folderId: string;
userId?: string;
inputSecrets: Array<{ secretBlindIndex: string; type?: SecretType }>;
};

View File

@@ -35,7 +35,7 @@ export const useAddUserToWs = () => {
workspaceEncryptedNonce: inviteeNonce
};
});
const { data } = await apiRequest.post(`/api/v2/workspace/${workspaceId}/memberships`, {
const { data } = await apiRequest.post(`/api/v1/workspace/${workspaceId}/memberships`, {
members: newWsMembers
});
return data;

View File

@@ -49,7 +49,7 @@ export type OrgUser = {
role: "owner" | "admin" | "member" | "no-access" | "custom";
status: "invited" | "accepted" | "verified" | "completed";
deniedPermissions: any[];
customRole: string;
roleId: string;
};
export type TWorkspaceUser = OrgUser;

View File

@@ -14,7 +14,6 @@ import {
DeleteWorkspaceDTO,
NameWorkspaceSecretsDTO,
RenameWorkspaceDTO,
ReorderEnvironmentsDTO,
ToggleAutoCapitalizationDTO,
UpdateEnvironmentDTO,
Workspace
@@ -29,8 +28,9 @@ export const workspaceKeys = {
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
getAllUserWorkspace: ["workspaces"] as const,
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const,
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const,
getWorkspaceAuditLogs: (workspaceId: string) =>
[{ workspaceId }, "workspace-audit-logs"] as const,
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
getWorkspaceIdentityMemberships: (workspaceId: string) =>
[{ workspaceId }, "workspace-identity-memberships"] as const
};
@@ -234,38 +234,15 @@ export const useCreateWsEnvironment = () => {
});
};
export const useReorderWsEnvironment = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, ReorderEnvironmentsDTO>({
mutationFn: ({
workspaceId,
environmentSlug,
environmentName,
otherEnvironmentSlug,
otherEnvironmentName
}) => {
return apiRequest.patch(`/api/v1/workspace/${workspaceId}/environments`, {
environmentSlug,
environmentName,
otherEnvironmentSlug,
otherEnvironmentName
});
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
});
};
export const useUpdateWsEnvironment = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdateEnvironmentDTO>({
mutationFn: ({ workspaceId, id, name, slug }) => {
mutationFn: ({ workspaceId, id, name, slug, position }) => {
return apiRequest.patch(`/api/v1/workspace/${workspaceId}/environments/${id}`, {
name,
slug
slug,
position
});
},
onSuccess: () => {
@@ -335,7 +312,7 @@ export const useDeleteUserFromWorkspace = () => {
}) => {
const {
data: { deletedMembership }
} = await apiRequest.delete(`/api/v1/${workspaceId}/membership/${membershipId}`);
} = await apiRequest.delete(`/api/v1/workspace/${workspaceId}/memberships/${membershipId}`);
return deletedMembership;
},
onSuccess: (_, { workspaceId }) => {
@@ -358,13 +335,16 @@ export const useUpdateUserWorkspaceRole = () => {
}) => {
const {
data: { membership }
} = await apiRequest.post(`/api/v1/${workspaceId}/membership/${membershipId}`, {
role
});
} = await apiRequest.patch<{ membership: { projectId: string } }>(
`/api/v1/workspace/${workspaceId}/memberships/${membershipId}`,
{
role
}
);
return membership;
},
onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.workspace));
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.projectId));
}
});
};

View File

@@ -53,6 +53,7 @@ export type UpdateEnvironmentDTO = {
id: string;
name?: string;
slug?: string;
position?: number;
};
export type DeleteEnvironmentDTO = { workspaceId: string; id: string };

View File

@@ -54,7 +54,7 @@ export default function SignupInvite() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const token = parsedUrl.token as string;
const organizationId = parsedUrl.organizationid as string;
const organizationId = parsedUrl.organization_id as string;
const email = (parsedUrl.to as string)?.replace(" ", "+").trim();
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.

View File

@@ -57,7 +57,6 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
const orgId = currentOrg?.id || "";
const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId);
console.log(roles);
const [searchMemberFilter, setSearchMemberFilter] = useState("");
@@ -172,7 +171,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
{isLoading && <TableSkeleton columns={5} innerKey="org-members" />}
{!isLoading &&
filterdUser?.map(
({ user: u, inviteEmail, role, customRole, id: orgMembershipId, status }) => {
({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => {
const name = u ? `${u.firstName} ${u.lastName}` : "-";
const email = u?.email || inviteEmail;
return (
@@ -188,9 +187,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
<>
{status === "accepted" && (
<Select
value={
role === "custom" ? findRoleFromId(customRole)?.slug : role
}
value={role === "custom" ? findRoleFromId(roleId)?.slug : role}
isDisabled={userId === u?.id || !isAllowed}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"

View File

@@ -349,8 +349,8 @@ export const OrgMembersTable = ({ roles = [], isRolesLoading }: Props) => {
{isLoading && <TableSkeleton columns={5} innerKey="org-members" />}
{!isLoading &&
filterdUser?.map(
({ user: u, inviteEmail, role, customRole, id: orgMembershipId, status }) => {
const name = u ? `${u.firstName} ${u.lastName}` : "-";
({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => {
const name = u ? `${u.firstName || "-"} ${u.lastName || ""}` : "-";
const email = u?.email || inviteEmail;
const userWs = workspaceMemberships?.[u?.id];
@@ -368,7 +368,7 @@ export const OrgMembersTable = ({ roles = [], isRolesLoading }: Props) => {
{status === "accepted" && (
<Select
defaultValue={
role === "custom" ? findRoleFromId(customRole)?.slug : role
role === "custom" ? findRoleFromId(roleId)?.slug : role
}
isDisabled={userId === u?.id || !isAllowed}
className="w-40 bg-mineshaft-600"

View File

@@ -158,6 +158,7 @@ export const MemberListTab = () => {
() => members?.find(({ user: u }) => userId === u?.id)?.role === "owner",
[userId, members]
);
console.log(members);
const findRoleFromId = useCallback(
(roleId: string) => {
@@ -290,7 +291,7 @@ export const MemberListTab = () => {
{isLoading && <TableSkeleton columns={4} innerKey="project-members" />}
{!isLoading &&
filterdUsers?.map(
({ user: u, inviteEmail, id: membershipId, status, customRole, role }) => {
({ user: u, inviteEmail, id: membershipId, status, roleId, role }) => {
const name = u ? `${u.firstName} ${u.lastName}` : "-";
const email = u?.email || inviteEmail;
@@ -306,9 +307,7 @@ export const MemberListTab = () => {
{(isAllowed) => (
<>
<Select
value={
role === "custom" ? findRoleFromId(customRole)?.slug : role
}
value={role === "custom" ? findRoleFromId(roleId)?.slug : role}
isDisabled={userId === u?.id || !isAllowed}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"

View File

@@ -30,6 +30,7 @@ export const DeleteProjectSection = () => {
try {
if (!currentWorkspace?.id) return;
const orgId = currentOrg?.id;
await deleteWorkspace.mutateAsync({
workspaceID: currentWorkspace?.id
});
@@ -39,7 +40,7 @@ export const DeleteProjectSection = () => {
type: "success"
});
router.push(`/org/${currentOrg?.id}/overview`);
router.push(`/org/${orgId}/overview`);
handlePopUpClose("deleteWorkspace");
} catch (err) {
console.error(err);

View File

@@ -16,7 +16,7 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useReorderWsEnvironment } from "@app/hooks/api";
import { useUpdateWsEnvironment } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -37,32 +37,16 @@ type Props = {
export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace, isLoading } = useWorkspace();
const { createNotification } = useNotificationContext();
const reorderWsEnvironment = useReorderWsEnvironment();
const updateEnvironment = useUpdateWsEnvironment();
const handleReorderEnv = async (shouldMoveUp: boolean, name: string, slug: string) => {
const handleReorderEnv = async (id: string, position: number) => {
try {
if (!currentWorkspace?.id) return;
const indexOfEnv = currentWorkspace.environments.findIndex(
(env) => env.name === name && env.slug === slug
);
// check that this reordering is possible
if (
(indexOfEnv === 0 && shouldMoveUp) ||
(indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp)
) {
return;
}
const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1;
await reorderWsEnvironment.mutateAsync({
await updateEnvironment.mutateAsync({
workspaceId: currentWorkspace.id,
environmentSlug: slug,
environmentName: name,
otherEnvironmentSlug: currentWorkspace.environments[indexToSwap].slug,
otherEnvironmentName: currentWorkspace.environments[indexToSwap].name
id,
position
});
createNotification({
@@ -104,9 +88,12 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
{(isAllowed) => (
<IconButton
className="mr-3 py-2"
onClick={() => {
handleReorderEnv(false, name, slug);
}}
onClick={() =>
handleReorderEnv(
id,
Math.min(currentWorkspace.environments.length, pos + 2)
)
}
colorSchema="primary"
variant="plain"
ariaLabel="update"
@@ -123,9 +110,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
{(isAllowed) => (
<IconButton
className="mr-3 py-2"
onClick={() => {
handleReorderEnv(true, name, slug);
}}
onClick={() => handleReorderEnv(id, Math.max(1, pos))}
colorSchema="primary"
variant="plain"
ariaLabel="update"