mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(infisical-pg): completed checklist run for dashboard
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -413,6 +413,8 @@ interface UpdateEnvironmentEvent {
|
||||
newName: string;
|
||||
oldSlug: string;
|
||||
newSlug: string;
|
||||
oldPos: number;
|
||||
newPos: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ export const sarSecretDalFactory = (db: TDbClient) => {
|
||||
secret: el.secretId
|
||||
? {
|
||||
id: el.secretId,
|
||||
version: orgSecVersion,
|
||||
secretBlindIndex: orgSecBlindIndex,
|
||||
secretKeyIV: orgSecKeyIV,
|
||||
secretKeyTag: orgSecKeyTag,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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}` });
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -253,6 +253,7 @@ export const orgServiceFactory = ({
|
||||
await orgDal.createMembership({
|
||||
inviteEmail: inviteeEmail,
|
||||
orgId,
|
||||
userId: user.id,
|
||||
role: OrgMembershipRole.Member,
|
||||
status: OrgMembershipStatus.Invited
|
||||
});
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ export type TUpdateEnvDTO = {
|
||||
id: string;
|
||||
name?: string;
|
||||
slug?: string;
|
||||
position?: number;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TDeleteEnvDTO = {
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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 }>;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -53,6 +53,7 @@ export type UpdateEnvironmentDTO = {
|
||||
id: string;
|
||||
name?: string;
|
||||
slug?: string;
|
||||
position?: number;
|
||||
};
|
||||
|
||||
export type DeleteEnvironmentDTO = { workspaceId: string; id: string };
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user