mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 20:27:43 +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("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,
|
||||||
|
|||||||
+142
-261
@@ -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],
|
||||||
|
|||||||
@@ -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}` });
|
||||||
|
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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 }>;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,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 };
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
+2
-5
@@ -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"
|
||||||
|
|||||||
+2
-1
@@ -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);
|
||||||
|
|||||||
+13
-28
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user