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

This commit is contained in:
Akhil Mohan
2024-01-27 12:38:23 +05:30
parent 16f0ac6d43
commit 0d3f09d668
28 changed files with 341 additions and 433 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ export const createJunctionTable = (
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
table.uuid(`${table1Name}Id`).unsigned().notNullable(); // Foreign key for table1 table.uuid(`${table1Name}Id`).unsigned().notNullable(); // Foreign key for table1
table.uuid(`${table2Name}Id`).unsigned().notNullable(); // Foreign key for table2 table.uuid(`${table2Name}Id`).unsigned().notNullable(); // Foreign key for table2
table.foreign(`${table1Name}Id`).references("id").inTable(table2Name); table.foreign(`${table1Name}Id`).references("id").inTable(table1Name);
table.foreign(`${table2Name}Id`).references("id").inTable(table1Name); table.foreign(`${table2Name}Id`).references("id").inTable(table2Name);
}); });
// one time logic // one time logic
@@ -413,6 +413,8 @@ interface UpdateEnvironmentEvent {
newName: string; newName: string;
oldSlug: string; oldSlug: string;
newSlug: string; newSlug: string;
oldPos: number;
newPos: number;
}; };
} }
@@ -88,6 +88,7 @@ export const sarSecretDalFactory = (db: TDbClient) => {
secret: el.secretId secret: el.secretId
? { ? {
id: el.secretId, id: el.secretId,
version: orgSecVersion,
secretBlindIndex: orgSecBlindIndex, secretBlindIndex: orgSecBlindIndex,
secretKeyIV: orgSecKeyIV, secretKeyIV: orgSecKeyIV,
secretKeyTag: orgSecKeyTag, secretKeyTag: orgSecKeyTag,
@@ -5,20 +5,20 @@ import {
SecretEncryptionAlgo, SecretEncryptionAlgo,
SecretKeyEncoding, SecretKeyEncoding,
SecretType, SecretType,
TSaRequestSecretsInsert, TSaRequestSecretsInsert
TSecrets
} from "@app/db/schemas"; } from "@app/db/schemas";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { groupBy, pick } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TSecretBlindIndexDalFactory } from "@app/services/secret/secret-blind-index-dal"; import { TSecretBlindIndexDalFactory } from "@app/services/secret/secret-blind-index-dal";
import { TSecretDalFactory } from "@app/services/secret/secret-dal"; import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { generateSecretBlindIndexBySalt } from "@app/services/secret/secret-service";
import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal"; import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal";
import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
import { TSarReviewerDalFactory } from "./sar-reviewer-dal"; import { TSarReviewerDalFactory } from "./sar-reviewer-dal";
import { TSarSecretDalFactory } from "./sar-secret-dal"; import { TSarSecretDalFactory } from "./sar-secret-dal";
import { TSecretApprovalRequestDalFactory } from "./secret-approval-request-dal"; import { TSecretApprovalRequestDalFactory } from "./secret-approval-request-dal";
@@ -38,12 +38,20 @@ import {
type TSecretApprovalRequestServiceFactoryDep = { type TSecretApprovalRequestServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
secretApprovalRequestDal: TSecretApprovalRequestDalFactory; secretApprovalRequestDal: TSecretApprovalRequestDalFactory;
secretDal: TSecretDalFactory;
sarSecretDal: TSarSecretDalFactory; sarSecretDal: TSarSecretDalFactory;
sarReviewerDal: TSarReviewerDalFactory; sarReviewerDal: TSarReviewerDalFactory;
secretVersionDal: Pick<TSecretVersionDalFactory, "findLatestVersionMany" | "insertMany">;
folderDal: Pick<TSecretFolderDalFactory, "findBySecretPath">; folderDal: Pick<TSecretFolderDalFactory, "findBySecretPath">;
secretBlindIndexDal: Pick<TSecretBlindIndexDalFactory, "findOne">; secretBlindIndexDal: Pick<TSecretBlindIndexDalFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDal: Pick<TSecretVersionDalFactory, "findLatestVersionMany">;
secretService: Pick<
TSecretServiceFactory,
| "fnSecretBulkInsert"
| "fnSecretBulkUpdate"
| "fnSecretBlindIndexCheck"
| "fnSecretBulkDelete"
| "fnSecretBlindIndexCheckV2"
>;
}; };
export type TSecretApprovalRequestServiceFactory = ReturnType< export type TSecretApprovalRequestServiceFactory = ReturnType<
@@ -53,12 +61,13 @@ export type TSecretApprovalRequestServiceFactory = ReturnType<
export const secretApprovalRequestServiceFactory = ({ export const secretApprovalRequestServiceFactory = ({
secretApprovalRequestDal, secretApprovalRequestDal,
folderDal, folderDal,
secretDal,
sarReviewerDal, sarReviewerDal,
sarSecretDal, sarSecretDal,
secretVersionDal,
secretBlindIndexDal, secretBlindIndexDal,
permissionService permissionService,
snapshotService,
secretService,
secretVersionDal
}: TSecretApprovalRequestServiceFactoryDep) => { }: TSecretApprovalRequestServiceFactoryDep) => {
const requestCount = async ({ projectId, actor, actorId }: TApprovalRequestCountDTO) => { const requestCount = async ({ projectId, actor, actorId }: TApprovalRequestCountDTO) => {
const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId);
@@ -194,11 +203,11 @@ export const secretApprovalRequestServiceFactory = ({
throw new BadRequestError({ message: "Secret approval request not found" }); throw new BadRequestError({ message: "Secret approval request not found" });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); 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( const { membership } = await permissionService.getProjectPermission(
ActorType.USER, ActorType.USER,
actorId, actorId,
secretApprovalRequest.projectId projectId
); );
if ( if (
membership.role !== ProjectMembershipRole.Admin && membership.role !== ProjectMembershipRole.Admin &&
@@ -225,17 +234,11 @@ export const secretApprovalRequestServiceFactory = ({
const conflicts: Array<{ secretId: string; op: CommitType }> = []; const conflicts: Array<{ secretId: string; op: CommitType }> = [];
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Create); let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Create);
if (secretCreationCommits.length) { if (secretCreationCommits.length) {
const conflictedSecrets = await secretDal.findByBlindIndexes( const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } =
folderId, await secretService.fnSecretBlindIndexCheckV2({
secretCreationCommits.map(({ secretBlindIndex }) => ({ folderId,
type: SecretType.Shared, inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => ({ secretBlindIndex }))
blindIndex: secretBlindIndex });
}))
);
const conflictGroupByBlindIndex = conflictedSecrets.reduce<Record<string, boolean>>(
(prev, curr) => ({ ...prev, [curr.secretBlindIndex || ""]: true }),
{}
);
secretCreationCommits secretCreationCommits
.filter(({ secretBlindIndex }) => conflictGroupByBlindIndex[secretBlindIndex || ""]) .filter(({ secretBlindIndex }) => conflictGroupByBlindIndex[secretBlindIndex || ""])
.forEach((el) => { .forEach((el) => {
@@ -248,22 +251,16 @@ export const secretApprovalRequestServiceFactory = ({
let secretUpdationCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Update); let secretUpdationCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Update);
if (secretUpdationCommits.length) { if (secretUpdationCommits.length) {
const conflictedByNewBlindIndex = await secretDal.findByBlindIndexes( const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } =
folderId, await secretService.fnSecretBlindIndexCheckV2({
secretUpdationCommits folderId,
.filter( inputSecrets: secretUpdationCommits
({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex .filter(
) ({ secretBlindIndex, secret }) =>
.map(({ secretBlindIndex }) => ({ secret && secret.secretBlindIndex !== secretBlindIndex
type: SecretType.Shared, )
blindIndex: secretBlindIndex .map(({ secretBlindIndex }) => ({ secretBlindIndex }))
})) });
);
const conflictGroupByBlindIndex = conflictedByNewBlindIndex.reduce<Record<string, boolean>>(
(prev, curr) =>
curr?.secretBlindIndex ? { ...prev, [curr.secretBlindIndex]: true } : prev,
{}
);
secretUpdationCommits secretUpdationCommits
.filter( .filter(
({ secretBlindIndex, secretId }) => ({ secretBlindIndex, secretId }) =>
@@ -284,122 +281,78 @@ export const secretApprovalRequestServiceFactory = ({
({ op }) => op === CommitType.Delete ({ op }) => op === CommitType.Delete
); );
const mergeStatus = await secretDal.transaction(async (tx) => { const mergeStatus = await secretApprovalRequestDal.transaction(async (tx) => {
const newSecrets = secretCreationCommits.length const newSecrets = secretCreationCommits.length
? await secretDal.insertMany( ? await secretService.fnSecretBulkInsert({
secretCreationCommits.map( tx,
({ folderId,
secretBlindIndex, inputSecrets: secretCreationCommits.map((el) => ({
metadata, ...pick(el, [
secretKeyIV, "secretCommentCiphertext",
secretKeyTag, "secretCommentTag",
secretKeyCiphertext, "secretCommentIV",
secretValueIV, "secretValueIV",
secretValueTag, "secretValueTag",
secretValueCiphertext, "secretValueCiphertext",
secretCommentIV, "secretKeyCiphertext",
secretCommentTag, "secretKeyTag",
secretCommentCiphertext, "secretKeyIV",
skipMultilineEncoding, "metadata",
secretReminderNote, "skipMultilineEncoding",
secretReminderRepeatDays "secretReminderNote",
}) => ({ "secretReminderRepeatDays",
secretBlindIndex, "version",
metadata, "algorithm",
secretKeyIV, "keyEncoding",
secretKeyTag, "secretBlindIndex"
secretKeyCiphertext, ]),
secretValueIV, type: SecretType.Shared
secretValueTag, }))
secretValueCiphertext, })
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretReminderNote,
secretReminderRepeatDays,
version: 1,
folderId,
type: SecretType.Shared,
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8
})
),
tx
)
: []; : [];
const updatedSecrets = secretUpdationCommits.length const updatedSecrets = secretUpdationCommits.length
? await secretDal.bulkUpdate( ? await secretService.fnSecretBulkUpdate({
secretUpdationCommits.map( folderId,
({ projectId,
version, tx,
secretId, inputSecrets: secretUpdationCommits.map((el) => ({
secretBlindIndex, ...pick(el, [
metadata, "secretCommentCiphertext",
secretKeyIV, "secretCommentTag",
secretKeyTag, "secretCommentIV",
secretKeyCiphertext, "secretValueIV",
secretValueIV, "secretValueTag",
secretValueTag, "secretValueCiphertext",
secretValueCiphertext, "secretKeyCiphertext",
secretCommentIV, "secretKeyTag",
secretCommentTag, "secretKeyIV",
secretCommentCiphertext, "metadata",
skipMultilineEncoding, "skipMultilineEncoding",
secretReminderNote, "secretReminderNote",
secretReminderRepeatDays "secretReminderRepeatDays",
}) => ({ "version",
folderId, "algorithm",
version: (version || 0) + 1, "keyEncoding",
id: secretId as string, "secretBlindIndex"
type: SecretType.Shared, ]),
secretBlindIndex, version: (el.secret?.version || 0) + 1,
metadata, id: el.secretId,
secretKeyIV, type: SecretType.Shared
secretKeyTag, }))
secretKeyCiphertext, })
secretValueIV,
secretValueTag,
secretValueCiphertext,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretReminderNote,
secretReminderRepeatDays
})
),
tx
)
: []; : [];
const deletedSecret = secretDeletionCommits.length const deletedSecret = secretDeletionCommits.length
? await secretDal.deleteMany( ? await secretService.fnSecretBulkDelete({
secretDeletionCommits.map(({ secretBlindIndex }) => ({ projectId,
blindIndex: secretBlindIndex,
type: SecretType.Shared
})),
folderId, folderId,
actorId, tx,
tx actorId: "",
) inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => ({
: []; secretBlindIndex,
if (newSecrets.length || updatedSecrets.length) { type: SecretType.Shared
await secretVersionDal.insertMany(
newSecrets
.map(({ id, updatedAt, createdAt, ...el }) => ({
...el,
secretId: id
})) }))
.concat( })
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({ : [];
...el,
secretId: id
}))
),
tx
);
}
const updatedSecretApproval = await secretApprovalRequestDal.updateById( const updatedSecretApproval = await secretApprovalRequestDal.updateById(
secretApprovalRequest.id, secretApprovalRequest.id,
{ {
@@ -415,6 +368,7 @@ export const secretApprovalRequestServiceFactory = ({
approval: updatedSecretApproval approval: updatedSecretApproval
}; };
}); });
await snapshotService.performSnapshot(folderId);
return mergeStatus; return mergeStatus;
}; };
@@ -444,42 +398,27 @@ export const secretApprovalRequestServiceFactory = ({
throw new BadRequestError({ message: "Folder not found", name: "GenSecretApproval" }); throw new BadRequestError({ message: "Folder not found", name: "GenSecretApproval" });
const folderId = folder.id; const folderId = folder.id;
const blindIndexDoc = await secretBlindIndexDal.findOne({ projectId }); const blindIndexCfg = await secretBlindIndexDal.findOne({ projectId });
if (!blindIndexDoc) if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "Update secret" }); throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
const commits: Omit<TSaRequestSecretsInsert, "requestId">[] = []; const commits: Omit<TSaRequestSecretsInsert, "requestId">[] = [];
// for created secret approval change // for created secret approval change
const createdSecrets = data[CommitType.Create]; const createdSecrets = data[CommitType.Create];
if (createdSecrets && createdSecrets?.length) { if (createdSecrets && createdSecrets?.length) {
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point const { keyName2BlindIndex } = await secretService.fnSecretBlindIndexCheck({
const secretBlindIndexes = await Promise.all( inputSecrets: createdSecrets,
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(
folderId, folderId,
createdSecrets.map(({ secretName }) => ({ isNew: true,
blindIndex: secretBlindIndexes[secretName], blindIndexCfg
type: SecretType.Shared });
}))
);
if (exists.length) throw new BadRequestError({ message: "Secret already exist" });
commits.push( commits.push(
...createdSecrets.map((el) => ({ ...createdSecrets.map(({ secretName, ...el }) => ({
...el, ...el,
op: CommitType.Create as const, op: CommitType.Create as const,
version: 0, version: 0,
secretBlindIndex: secretBlindIndexes[el.secretName], secretBlindIndex: keyName2BlindIndex[secretName],
algorithm: SecretEncryptionAlgo.AES_256_GCM, algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.BASE64 keyEncoding: SecretKeyEncoding.BASE64
})) }))
@@ -491,83 +430,49 @@ export const secretApprovalRequestServiceFactory = ({
// get all blind index // get all blind index
// Find all those secrets // Find all those secrets
// if not throw not found // if not throw not found
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point const { keyName2BlindIndex, secrets: secretsToBeUpdated } =
const secretBlindIndexes = await Promise.all( await secretService.fnSecretBlindIndexCheck({
updatedSecrets.map(({ secretName }) => inputSecrets: updatedSecrets,
generateSecretBlindIndexBySalt(secretName, blindIndexDoc) folderId,
) isNew: false,
).then((blindIndexes) => blindIndexCfg
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" });
// now find any secret that needs to update its name // now find any secret that needs to update its name
// same process as above // same process as above
const nameUpdatedSecrets = updatedSecrets.filter(({ newSecretName }) => const nameUpdatedSecrets = updatedSecrets.filter(({ newSecretName }) =>
Boolean(newSecretName) Boolean(newSecretName)
); );
const newSecretBlindIndexes = await Promise.all( const { keyName2BlindIndex: newKeyName2BlindIndex } =
nameUpdatedSecrets.map(({ newSecretName }) => await secretService.fnSecretBlindIndexCheck({
generateSecretBlindIndexBySalt(newSecretName as string, blindIndexDoc) inputSecrets: nameUpdatedSecrets,
) folderId,
).then((blindIndexes) => isNew: true,
blindIndexes.reduce<Record<string, string>>((prev, curr, i) => { blindIndexCfg
// 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 secretsGroupedByBlindIndex = secretsToBeUpdated.reduce<Record<string, TSecrets>>( const secsGroupedByBlindIndex = groupBy(secretsToBeUpdated, (el) => el.secretBlindIndex);
(prev, curr) => {
// eslint-disable-next-line
if (curr.secretBlindIndex) prev[curr.secretBlindIndex] = curr;
return prev;
},
{}
);
const updatedSecretIds = updatedSecrets.map( const updatedSecretIds = updatedSecrets.map(
(el) => secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].id (el) => secsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id
); );
const latestSecretVersions = await secretVersionDal.findLatestVersionMany( const latestSecretVersions = await secretVersionDal.findLatestVersionMany(
folderId, folderId,
updatedSecretIds updatedSecretIds
); );
commits.push( commits.push(
...updatedSecrets.map((el) => { ...updatedSecrets.map(({ newSecretName, secretName, ...el }) => {
const secretId = secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].id; const secretId = secsGroupedByBlindIndex[keyName2BlindIndex[secretName]][0].id;
return { return {
...latestSecretVersions[secretId], ...latestSecretVersions[secretId],
...el,
op: CommitType.Update as const, op: CommitType.Update as const,
secret: secretId, secret: secretId,
secretVersion: latestSecretVersions[secretId].id, secretVersion: latestSecretVersions[secretId].id,
...el,
secretBlindIndex: secretBlindIndex:
newSecretBlindIndexes?.[el.secretName] || secretBlindIndexes[el.secretName], newSecretName && newKeyName2BlindIndex[newSecretName]
version: secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].version || 1 ? newKeyName2BlindIndex?.[secretName]
: keyName2BlindIndex[secretName],
version: secsGroupedByBlindIndex[keyName2BlindIndex[secretName]][0].version || 1
}; };
}) })
); );
@@ -578,39 +483,15 @@ export const secretApprovalRequestServiceFactory = ({
// get all blind index // get all blind index
// Find all those secrets // Find all those secrets
// if not throw not found // if not throw not found
const secretBlindIndexToKey: Record<string, string> = {}; // used at audit log point const { keyName2BlindIndex, secrets } = await secretService.fnSecretBlindIndexCheck({
const secretBlindIndexes = await Promise.all( inputSecrets: deletedSecrets,
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(
folderId, folderId,
deletedSecrets.map(({ secretName }) => ({ isNew: false,
blindIndex: secretBlindIndexes[secretName], blindIndexCfg
type: SecretType.Shared });
})) const secretsGroupedByBlindIndex = groupBy(secrets, (i) => i.secretBlindIndex);
);
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;
},
{}
);
const deletedSecretIds = deletedSecrets.map( const deletedSecretIds = deletedSecrets.map(
(el) => secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].id (el) => secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id
); );
const latestSecretVersions = await secretVersionDal.findLatestVersionMany( const latestSecretVersions = await secretVersionDal.findLatestVersionMany(
folderId, folderId,
@@ -618,7 +499,7 @@ export const secretApprovalRequestServiceFactory = ({
); );
commits.push( commits.push(
...deletedSecrets.map((el) => { ...deletedSecrets.map((el) => {
const secretId = secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].id; const secretId = secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id;
return { return {
op: CommitType.Delete as const, op: CommitType.Delete as const,
...latestSecretVersions[secretId], ...latestSecretVersions[secretId],
+11 -10
View File
@@ -173,16 +173,6 @@ export const registerRoutes = async (
userDal, userDal,
samlConfigDal samlConfigDal
}); });
const sarService = secretApprovalRequestServiceFactory({
permissionService,
folderDal,
secretDal,
sarSecretDal,
sarReviewerDal,
secretVersionDal,
secretBlindIndexDal,
secretApprovalRequestDal
});
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal }); const tokenService = tokenServiceFactory({ tokenDal: authTokenDal });
const userService = userServiceFactory({ userDal }); const userService = userServiceFactory({ userDal });
@@ -282,6 +272,17 @@ export const registerRoutes = async (
}); });
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDal }); const projectBotService = projectBotServiceFactory({ permissionService, projectBotDal });
const sarService = secretApprovalRequestServiceFactory({
permissionService,
folderDal,
sarSecretDal,
sarReviewerDal,
secretVersionDal,
secretBlindIndexDal,
secretApprovalRequestDal,
secretService,
snapshotService
});
const secretRotationQueue = secretRotationQueueFactory({ const secretRotationQueue = secretRotationQueueFactory({
secretRotationDal, secretRotationDal,
queue: queueService, queue: queueService,
@@ -52,7 +52,6 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const { user, token } = await server.services.org.verifyUserToOrg({ const { user, token } = await server.services.org.verifyUserToOrg({
orgId: req.body.organizationId, orgId: req.body.organizationId,
@@ -4,7 +4,6 @@ import {
IncidentContactsSchema, IncidentContactsSchema,
OrganizationsSchema, OrganizationsSchema,
OrgMembershipsSchema, OrgMembershipsSchema,
UserEncryptionKeysSchema,
UsersSchema UsersSchema
} from "@app/db/schemas"; } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -67,7 +66,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
firstName: true, firstName: true,
lastName: true, lastName: true,
id: true id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })) }).merge(z.object({ publicKey: z.string().nullable() }))
}) })
) )
.omit({ createdAt: true, updatedAt: true }) .omit({ createdAt: true, updatedAt: true })
@@ -63,7 +63,8 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}), }),
body: z.object({ body: z.object({
slug: z.string().trim().optional(), slug: z.string().trim().optional(),
name: z.string().trim().optional() name: z.string().trim().optional(),
position: z.number().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -91,8 +92,10 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
metadata: { metadata: {
oldName: old.name, oldName: old.name,
oldSlug: old.slug, oldSlug: old.slug,
newName: old.name, oldPos: old.position,
newSlug: old.slug newName: environment.name,
newSlug: environment.slug,
newPos: environment.position
} }
} }
}); });
@@ -1,6 +1,11 @@
import { z } from "zod"; 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 { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types"; import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -22,7 +27,18 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 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()
}) })
} }
}, },
@@ -61,7 +61,7 @@ export const identityServiceFactory = ({
}; };
const updateIdentity = async ({ id, role, name, actor, actorId }: TUpdateIdentityDTO) => { const updateIdentity = async ({ id, role, name, actor, actorId }: TUpdateIdentityDTO) => {
const identityOrgMembership = await identityOrgMembershipDal.findById(id); const identityOrgMembership = await identityOrgMembershipDal.findOne({ identityId: id });
if (!identityOrgMembership) if (!identityOrgMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); 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 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) { if (role) {
await identityOrgMembershipDal.update( await identityOrgMembershipDal.update(
{ identityId: id }, { identityId: id },
@@ -115,7 +117,7 @@ export const identityServiceFactory = ({
}; };
const deleteIdentity = async ({ actorId, actor, id }: TDeleteIdentityDTO) => { const deleteIdentity = async ({ actorId, actor, id }: TDeleteIdentityDTO) => {
const identityOrgMembership = await identityOrgMembershipDal.findById(id); const identityOrgMembership = await identityOrgMembershipDal.findOne({ identityId: id });
if (!identityOrgMembership) if (!identityOrgMembership)
throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
+1 -1
View File
@@ -46,7 +46,7 @@ export const orgDalFactory = (db: TDbClient) => {
const members = await db(TableName.OrgMembership) const members = await db(TableName.OrgMembership)
.where({ orgId }) .where({ orgId })
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.join( .leftJoin(
TableName.UserEncryptionKey, TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`, `${TableName.UserEncryptionKey}.userId`,
`${TableName.Users}.id` `${TableName.Users}.id`
@@ -253,6 +253,7 @@ export const orgServiceFactory = ({
await orgDal.createMembership({ await orgDal.createMembership({
inviteEmail: inviteeEmail, inviteEmail: inviteeEmail,
orgId, orgId,
userId: user.id,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited status: OrgMembershipStatus.Invited
}); });
@@ -26,38 +26,49 @@ export const projectEnvDalFactory = (db: TDbClient) => {
const findLastEnvPosition = async (projectId: string, tx?: Knex) => { const findLastEnvPosition = async (projectId: string, tx?: Knex) => {
const lastPos = await (tx || db)(TableName.Environment) const lastPos = await (tx || db)(TableName.Environment)
.where({ projectId }) .where({ projectId })
.max("position") .max({ position: "position" })
.first(); .first();
return lastPos?.position || 1; return lastPos?.position || 0;
}; };
const incrementLastPosition = async ( const updateAllPosition = async (
projectId: string, projectId: string,
startPos: number, pos: number,
increment = 1, targetPos: number,
tx?: Knex tx?: Knex
) => ) => {
(tx || db)(TableName.Environment) try {
.where("projectId", projectId) if (targetPos === -1) {
.where("postion", ">=", startPos) // this means delete
.increment("position", increment); await (tx || db)(TableName.Environment)
.where({ projectId })
.andWhere("position", ">", pos)
.decrement("position", 1);
return;
}
const decrementLastPosition = async ( if (targetPos > pos) {
projectId: string, await (tx || db)(TableName.Environment)
startPos: number, .where({ projectId })
decrement = 1, .where("position", "<=", targetPos)
tx?: Knex .andWhere("position", ">", pos)
) => .decrement("position", 1);
(tx || db)(TableName.Environment) } else {
.where("projectId", projectId) await (tx || db)(TableName.Environment)
.where("postion", ">", startPos) .where({ projectId })
.decrement("position", decrement); .where("position", ">=", targetPos)
.andWhere("position", "<", pos)
.increment("position", 1);
}
} catch (error) {
throw new DatabaseError({ error, name: "UpdateEnvPos" });
}
};
return { return {
...projectEnvOrm, ...projectEnvOrm,
findBySlugs, findBySlugs,
findLastEnvPosition, findLastEnvPosition,
decrementLastPosition, updateAllPosition
incrementLastPosition
}; };
}; };
@@ -8,7 +8,7 @@ import {
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { TProjectEnvDalFactory } from "./project-env-dal"; 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 = { type TProjectEnvServiceFactoryDep = {
projectEnvDal: TProjectEnvDalFactory; projectEnvDal: TProjectEnvDalFactory;
@@ -38,7 +38,7 @@ export const projectEnvServiceFactory = ({
const env = await projectEnvDal.transaction(async (tx) => { const env = await projectEnvDal.transaction(async (tx) => {
const lastPos = await projectEnvDal.findLastEnvPosition(projectId, 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 doc;
}); });
return env; return env;
@@ -50,7 +50,8 @@ export const projectEnvServiceFactory = ({
actor, actor,
actorId, actorId,
name, name,
id id,
position
}: TUpdateEnvDTO) => { }: TUpdateEnvDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan( 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 }; return { environment: env, old: oldEnv };
}; };
@@ -90,36 +96,15 @@ export const projectEnvServiceFactory = ({
name: "Re-order env" name: "Re-order env"
}); });
await projectEnvDal.decrementLastPosition(projectId, doc.position, 1, tx); await projectEnvDal.updateAllPosition(projectId, doc.position, -1, tx);
return doc; return doc;
}); });
return env; 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 { return {
createEnvironment, createEnvironment,
updateEnvironment, updateEnvironment,
deleteEnvironment, deleteEnvironment
reorderEnvironment
}; };
}; };
@@ -9,6 +9,7 @@ export type TUpdateEnvDTO = {
id: string; id: string;
name?: string; name?: string;
slug?: string; slug?: string;
position?: number;
} & TProjectPermission; } & TProjectPermission;
export type TDeleteEnvDTO = { export type TDeleteEnvDTO = {
+26 -15
View File
@@ -1,9 +1,16 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; 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 { 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>; 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("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")); .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
const formatedSecs = mergeOneToManyRelation( return sqlNestRelationships({
secs, data: secs,
"id", key: "id",
({ tagColor, tagId, tagName, tagSlug, ...data }) => data, parentMapper: (el) => SecretsSchema.parse(el),
({ tagSlug: slug, tagName: name, tagId: id, tagColor: color }) => ({ childrenMapper: [
id, {
slug, key: "tagId",
name, label: "tags" as const,
color mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
}), id,
"tags" color,
); slug,
return formatedSecs; name
})
}
]
});
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "get all secret" }); throw new DatabaseError({ error, name: "get all secret" });
} }
@@ -29,6 +29,7 @@ import {
TDeleteBulkSecretDTO, TDeleteBulkSecretDTO,
TDeleteSecretDTO, TDeleteSecretDTO,
TFnSecretBlindIndexCheck, TFnSecretBlindIndexCheck,
TFnSecretBlindIndexCheckV2,
TFnSecretBulkDelete, TFnSecretBulkDelete,
TFnSecretBulkInsert, TFnSecretBulkInsert,
TFnSecretBulkUpdate, TFnSecretBulkUpdate,
@@ -109,8 +110,8 @@ export const secretServiceFactory = ({
const newSecretGroupByBlindIndex = groupBy(newSecrets, (item) => item.secretBlindIndex); const newSecretGroupByBlindIndex = groupBy(newSecrets, (item) => item.secretBlindIndex);
const newSecretTags = inputSecrets.flatMap(({ tags: secretTags = [], secretBlindIndex }) => const newSecretTags = inputSecrets.flatMap(({ tags: secretTags = [], secretBlindIndex }) =>
secretTags.map((tag) => ({ secretTags.map((tag) => ({
[`${TableName.SecretTag}Id`]: tag, [`${TableName.SecretTag}Id` as const]: tag,
[`${TableName.Secret}Id`]: newSecretGroupByBlindIndex[secretBlindIndex][0].id [`${TableName.Secret}Id` as const]: newSecretGroupByBlindIndex[secretBlindIndex][0].id
})) }))
); );
if (newSecretTags.length) { if (newSecretTags.length) {
@@ -147,8 +148,8 @@ export const secretServiceFactory = ({
); );
const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], id }) => const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], id }) =>
secretTags.map((tag) => ({ secretTags.map((tag) => ({
[`${TableName.SecretTag}Id`]: tag, [`${TableName.SecretTag}Id` as const]: tag,
[`${TableName.Secret}Id`]: id [`${TableName.Secret}Id` as const]: id
})) }))
); );
await secretTagDal.saveTagsToSecret(newSecretTags, tx); await secretTagDal.saveTagsToSecret(newSecretTags, tx);
@@ -229,6 +230,29 @@ export const secretServiceFactory = ({
return { blindIndex2KeyName, keyName2BlindIndex, secrets }; 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 ({ const createSecret = async ({
path, path,
actor, actor,
@@ -540,7 +564,7 @@ export const secretServiceFactory = ({
fnSecretBulkInsert({ fnSecretBulkInsert({
inputSecrets: inputSecrets.map(({ secretName, ...el }) => ({ inputSecrets: inputSecrets.map(({ secretName, ...el }) => ({
...el, ...el,
version:0, version: 0,
secretBlindIndex: keyName2BlindIndex[secretName], secretBlindIndex: keyName2BlindIndex[secretName],
type: SecretType.Shared, type: SecretType.Shared,
algorithm: SecretEncryptionAlgo.AES_256_GCM, algorithm: SecretEncryptionAlgo.AES_256_GCM,
@@ -717,6 +741,7 @@ export const secretServiceFactory = ({
fnSecretBulkDelete, fnSecretBulkDelete,
fnSecretBulkUpdate, fnSecretBulkUpdate,
fnSecretBlindIndexCheck, fnSecretBlindIndexCheck,
fnSecretBulkInsert fnSecretBulkInsert,
fnSecretBlindIndexCheckV2
}; };
}; };
@@ -153,3 +153,10 @@ export type TFnSecretBlindIndexCheck = {
inputSecrets: Array<{ secretName: string; type?: SecretType }>; inputSecrets: Array<{ secretName: string; type?: SecretType }>;
isNew: boolean; isNew: boolean;
}; };
// when blind index is already present
export type TFnSecretBlindIndexCheckV2 = {
folderId: string;
userId?: string;
inputSecrets: Array<{ secretBlindIndex: string; type?: SecretType }>;
};
+1 -1
View File
@@ -35,7 +35,7 @@ export const useAddUserToWs = () => {
workspaceEncryptedNonce: inviteeNonce workspaceEncryptedNonce: inviteeNonce
}; };
}); });
const { data } = await apiRequest.post(`/api/v2/workspace/${workspaceId}/memberships`, { const { data } = await apiRequest.post(`/api/v1/workspace/${workspaceId}/memberships`, {
members: newWsMembers members: newWsMembers
}); });
return data; return data;
+1 -1
View File
@@ -49,7 +49,7 @@ export type OrgUser = {
role: "owner" | "admin" | "member" | "no-access" | "custom"; role: "owner" | "admin" | "member" | "no-access" | "custom";
status: "invited" | "accepted" | "verified" | "completed"; status: "invited" | "accepted" | "verified" | "completed";
deniedPermissions: any[]; deniedPermissions: any[];
customRole: string; roleId: string;
}; };
export type TWorkspaceUser = OrgUser; export type TWorkspaceUser = OrgUser;
+14 -34
View File
@@ -14,7 +14,6 @@ import {
DeleteWorkspaceDTO, DeleteWorkspaceDTO,
NameWorkspaceSecretsDTO, NameWorkspaceSecretsDTO,
RenameWorkspaceDTO, RenameWorkspaceDTO,
ReorderEnvironmentsDTO,
ToggleAutoCapitalizationDTO, ToggleAutoCapitalizationDTO,
UpdateEnvironmentDTO, UpdateEnvironmentDTO,
Workspace Workspace
@@ -29,8 +28,9 @@ export const workspaceKeys = {
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"], getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"], getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
getAllUserWorkspace: ["workspaces"] as const, getAllUserWorkspace: ["workspaces"] as const,
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const, getWorkspaceAuditLogs: (workspaceId: string) =>
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const, [{ workspaceId }, "workspace-audit-logs"] as const,
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
getWorkspaceIdentityMemberships: (workspaceId: string) => getWorkspaceIdentityMemberships: (workspaceId: string) =>
[{ workspaceId }, "workspace-identity-memberships"] as const [{ 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 = () => { export const useUpdateWsEnvironment = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<{}, {}, UpdateEnvironmentDTO>({ return useMutation<{}, {}, UpdateEnvironmentDTO>({
mutationFn: ({ workspaceId, id, name, slug }) => { mutationFn: ({ workspaceId, id, name, slug, position }) => {
return apiRequest.patch(`/api/v1/workspace/${workspaceId}/environments/${id}`, { return apiRequest.patch(`/api/v1/workspace/${workspaceId}/environments/${id}`, {
name, name,
slug slug,
position
}); });
}, },
onSuccess: () => { onSuccess: () => {
@@ -335,7 +312,7 @@ export const useDeleteUserFromWorkspace = () => {
}) => { }) => {
const { const {
data: { deletedMembership } data: { deletedMembership }
} = await apiRequest.delete(`/api/v1/${workspaceId}/membership/${membershipId}`); } = await apiRequest.delete(`/api/v1/workspace/${workspaceId}/memberships/${membershipId}`);
return deletedMembership; return deletedMembership;
}, },
onSuccess: (_, { workspaceId }) => { onSuccess: (_, { workspaceId }) => {
@@ -358,13 +335,16 @@ export const useUpdateUserWorkspaceRole = () => {
}) => { }) => {
const { const {
data: { membership } data: { membership }
} = await apiRequest.post(`/api/v1/${workspaceId}/membership/${membershipId}`, { } = await apiRequest.patch<{ membership: { projectId: string } }>(
role `/api/v1/workspace/${workspaceId}/memberships/${membershipId}`,
}); {
role
}
);
return membership; return membership;
}, },
onSuccess: (res) => { onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.workspace)); queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.projectId));
} }
}); });
}; };
@@ -53,6 +53,7 @@ export type UpdateEnvironmentDTO = {
id: string; id: string;
name?: string; name?: string;
slug?: string; slug?: string;
position?: number;
}; };
export type DeleteEnvironmentDTO = { workspaceId: string; id: string }; export type DeleteEnvironmentDTO = { workspaceId: string; id: string };
+1 -1
View File
@@ -54,7 +54,7 @@ export default function SignupInvite() {
const router = useRouter(); const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]); const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const token = parsedUrl.token as string; 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(); 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. // Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
@@ -57,7 +57,6 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId); const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId);
console.log(roles);
const [searchMemberFilter, setSearchMemberFilter] = useState(""); const [searchMemberFilter, setSearchMemberFilter] = useState("");
@@ -172,7 +171,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
{isLoading && <TableSkeleton columns={5} innerKey="org-members" />} {isLoading && <TableSkeleton columns={5} innerKey="org-members" />}
{!isLoading && {!isLoading &&
filterdUser?.map( 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 name = u ? `${u.firstName} ${u.lastName}` : "-";
const email = u?.email || inviteEmail; const email = u?.email || inviteEmail;
return ( return (
@@ -188,9 +187,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
<> <>
{status === "accepted" && ( {status === "accepted" && (
<Select <Select
value={ value={role === "custom" ? findRoleFromId(roleId)?.slug : role}
role === "custom" ? findRoleFromId(customRole)?.slug : role
}
isDisabled={userId === u?.id || !isAllowed} isDisabled={userId === u?.id || !isAllowed}
className="w-40 bg-mineshaft-600" className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800" dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
@@ -349,8 +349,8 @@ export const OrgMembersTable = ({ roles = [], isRolesLoading }: Props) => {
{isLoading && <TableSkeleton columns={5} innerKey="org-members" />} {isLoading && <TableSkeleton columns={5} innerKey="org-members" />}
{!isLoading && {!isLoading &&
filterdUser?.map( 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 name = u ? `${u.firstName || "-"} ${u.lastName || ""}` : "-";
const email = u?.email || inviteEmail; const email = u?.email || inviteEmail;
const userWs = workspaceMemberships?.[u?.id]; const userWs = workspaceMemberships?.[u?.id];
@@ -368,7 +368,7 @@ export const OrgMembersTable = ({ roles = [], isRolesLoading }: Props) => {
{status === "accepted" && ( {status === "accepted" && (
<Select <Select
defaultValue={ defaultValue={
role === "custom" ? findRoleFromId(customRole)?.slug : role role === "custom" ? findRoleFromId(roleId)?.slug : role
} }
isDisabled={userId === u?.id || !isAllowed} isDisabled={userId === u?.id || !isAllowed}
className="w-40 bg-mineshaft-600" className="w-40 bg-mineshaft-600"
@@ -158,6 +158,7 @@ export const MemberListTab = () => {
() => members?.find(({ user: u }) => userId === u?.id)?.role === "owner", () => members?.find(({ user: u }) => userId === u?.id)?.role === "owner",
[userId, members] [userId, members]
); );
console.log(members);
const findRoleFromId = useCallback( const findRoleFromId = useCallback(
(roleId: string) => { (roleId: string) => {
@@ -290,7 +291,7 @@ export const MemberListTab = () => {
{isLoading && <TableSkeleton columns={4} innerKey="project-members" />} {isLoading && <TableSkeleton columns={4} innerKey="project-members" />}
{!isLoading && {!isLoading &&
filterdUsers?.map( 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 name = u ? `${u.firstName} ${u.lastName}` : "-";
const email = u?.email || inviteEmail; const email = u?.email || inviteEmail;
@@ -306,9 +307,7 @@ export const MemberListTab = () => {
{(isAllowed) => ( {(isAllowed) => (
<> <>
<Select <Select
value={ value={role === "custom" ? findRoleFromId(roleId)?.slug : role}
role === "custom" ? findRoleFromId(customRole)?.slug : role
}
isDisabled={userId === u?.id || !isAllowed} isDisabled={userId === u?.id || !isAllowed}
className="w-40 bg-mineshaft-600" className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800" dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
@@ -30,6 +30,7 @@ export const DeleteProjectSection = () => {
try { try {
if (!currentWorkspace?.id) return; if (!currentWorkspace?.id) return;
const orgId = currentOrg?.id;
await deleteWorkspace.mutateAsync({ await deleteWorkspace.mutateAsync({
workspaceID: currentWorkspace?.id workspaceID: currentWorkspace?.id
}); });
@@ -39,7 +40,7 @@ export const DeleteProjectSection = () => {
type: "success" type: "success"
}); });
router.push(`/org/${currentOrg?.id}/overview`); router.push(`/org/${orgId}/overview`);
handlePopUpClose("deleteWorkspace"); handlePopUpClose("deleteWorkspace");
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -16,7 +16,7 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; 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"; import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = { type Props = {
@@ -37,32 +37,16 @@ type Props = {
export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace, isLoading } = useWorkspace(); const { currentWorkspace, isLoading } = useWorkspace();
const { createNotification } = useNotificationContext(); 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 { try {
if (!currentWorkspace?.id) return; if (!currentWorkspace?.id) return;
const indexOfEnv = currentWorkspace.environments.findIndex( await updateEnvironment.mutateAsync({
(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({
workspaceId: currentWorkspace.id, workspaceId: currentWorkspace.id,
environmentSlug: slug, id,
environmentName: name, position
otherEnvironmentSlug: currentWorkspace.environments[indexToSwap].slug,
otherEnvironmentName: currentWorkspace.environments[indexToSwap].name
}); });
createNotification({ createNotification({
@@ -104,9 +88,12 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
{(isAllowed) => ( {(isAllowed) => (
<IconButton <IconButton
className="mr-3 py-2" className="mr-3 py-2"
onClick={() => { onClick={() =>
handleReorderEnv(false, name, slug); handleReorderEnv(
}} id,
Math.min(currentWorkspace.environments.length, pos + 2)
)
}
colorSchema="primary" colorSchema="primary"
variant="plain" variant="plain"
ariaLabel="update" ariaLabel="update"
@@ -123,9 +110,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
{(isAllowed) => ( {(isAllowed) => (
<IconButton <IconButton
className="mr-3 py-2" className="mr-3 py-2"
onClick={() => { onClick={() => handleReorderEnv(id, Math.max(1, pos))}
handleReorderEnv(true, name, slug);
}}
colorSchema="primary" colorSchema="primary"
variant="plain" variant="plain"
ariaLabel="update" ariaLabel="update"