mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 10:26:00 +00:00
Revert "feat(api/secrets): view secret value permission"
This commit is contained in:
@@ -6,7 +6,7 @@ import { decryptAsymmetric, decryptSymmetric128BitHexKeyUTF8, encryptSymmetric12
|
|||||||
|
|
||||||
const createServiceToken = async (
|
const createServiceToken = async (
|
||||||
scopes: { environment: string; secretPath: string }[],
|
scopes: { environment: string; secretPath: string }[],
|
||||||
permissions: ("read" | "write" | "readValue")[]
|
permissions: ("read" | "write")[]
|
||||||
) => {
|
) => {
|
||||||
const projectKeyRes = await testServer.inject({
|
const projectKeyRes = await testServer.inject({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -139,7 +139,7 @@ describe("Service token secret ops", async () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
serviceToken = await createServiceToken(
|
serviceToken = await createServiceToken(
|
||||||
[{ secretPath: "/**", environment: seedData1.environment.slug }],
|
[{ secretPath: "/**", environment: seedData1.environment.slug }],
|
||||||
["read", "write", "readValue"]
|
["read", "write"]
|
||||||
);
|
);
|
||||||
|
|
||||||
// this is ensure cli service token decryptiong working fine
|
// this is ensure cli service token decryptiong working fine
|
||||||
@@ -496,7 +496,7 @@ describe("Service token fail cases", async () => {
|
|||||||
test("Unauthorized secret path access", async () => {
|
test("Unauthorized secret path access", async () => {
|
||||||
const serviceToken = await createServiceToken(
|
const serviceToken = await createServiceToken(
|
||||||
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
||||||
["read", "readValue", "write"]
|
["read", "write"]
|
||||||
);
|
);
|
||||||
const fetchSecrets = await testServer.inject({
|
const fetchSecrets = await testServer.inject({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -518,7 +518,7 @@ describe("Service token fail cases", async () => {
|
|||||||
test("Unauthorized secret environment access", async () => {
|
test("Unauthorized secret environment access", async () => {
|
||||||
const serviceToken = await createServiceToken(
|
const serviceToken = await createServiceToken(
|
||||||
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
||||||
["read", "readValue", "write"]
|
["read", "write"]
|
||||||
);
|
);
|
||||||
const fetchSecrets = await testServer.inject({
|
const fetchSecrets = await testServer.inject({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -540,7 +540,7 @@ describe("Service token fail cases", async () => {
|
|||||||
test("Unauthorized write operation", async () => {
|
test("Unauthorized write operation", async () => {
|
||||||
const serviceToken = await createServiceToken(
|
const serviceToken = await createServiceToken(
|
||||||
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
||||||
["read", "readValue"]
|
["read"]
|
||||||
);
|
);
|
||||||
const writeSecrets = await testServer.inject({
|
const writeSecrets = await testServer.inject({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -120,3 +120,4 @@ export default {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
-313
@@ -1,313 +0,0 @@
|
|||||||
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
|
||||||
import { Knex } from "knex";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { selectAllTableCols } from "@app/lib/knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
|
|
||||||
enum ProjectPermissionSub {
|
|
||||||
Secrets = "secrets"
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SecretActions {
|
|
||||||
Read = "read",
|
|
||||||
ReadValue = "readValue"
|
|
||||||
}
|
|
||||||
|
|
||||||
const UnpackedPermissionSchema = z.object({
|
|
||||||
subject: z
|
|
||||||
.union([z.string().min(1), z.string().array()])
|
|
||||||
.transform((el) => (typeof el !== "string" ? el[0] : el))
|
|
||||||
.optional(),
|
|
||||||
action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
|
|
||||||
conditions: z.unknown().optional(),
|
|
||||||
inverted: z.boolean().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
const $unpackPermissions = (permissions: unknown) =>
|
|
||||||
UnpackedPermissionSchema.array().parse(unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility>>[]));
|
|
||||||
|
|
||||||
const $updatePermissionsUp = (permissions: unknown) => {
|
|
||||||
const parsedPermissions = $unpackPermissions(permissions);
|
|
||||||
let shouldUpdate = false;
|
|
||||||
|
|
||||||
for (let i = 0; i < parsedPermissions.length; i += 1) {
|
|
||||||
const parsedPermission = parsedPermissions[i];
|
|
||||||
const { subject, action } = parsedPermission;
|
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.Secrets) {
|
|
||||||
if (action.includes(SecretActions.Read) && !action.includes(SecretActions.ReadValue)) {
|
|
||||||
action.push(SecretActions.ReadValue);
|
|
||||||
parsedPermissions[i] = { ...parsedPermission, action };
|
|
||||||
shouldUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
parsedPermissions,
|
|
||||||
shouldUpdate
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const $updatePermissionsDown = (permissions: unknown) => {
|
|
||||||
const parsedPermissions = $unpackPermissions(permissions);
|
|
||||||
|
|
||||||
let shouldUpdate = false;
|
|
||||||
for (let i = 0; i < parsedPermissions.length; i += 1) {
|
|
||||||
const parsedPermission = parsedPermissions[i];
|
|
||||||
|
|
||||||
const { subject, action } = parsedPermission;
|
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.Secrets) {
|
|
||||||
const readValueIndex = action.indexOf(SecretActions.ReadValue);
|
|
||||||
|
|
||||||
if (action.includes(SecretActions.ReadValue) && readValueIndex !== -1) {
|
|
||||||
action.splice(readValueIndex, 1);
|
|
||||||
parsedPermissions[i] = { ...parsedPermission, action };
|
|
||||||
|
|
||||||
shouldUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const repackedPermissions = packRules(parsedPermissions);
|
|
||||||
|
|
||||||
return {
|
|
||||||
repackedPermissions,
|
|
||||||
shouldUpdate
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const CHUNK_SIZE = 1000;
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
const projectRoles = await knex(TableName.ProjectRoles).select(selectAllTableCols(TableName.ProjectRoles));
|
|
||||||
const projectIdentityAdditionalPrivileges = await knex(TableName.IdentityProjectAdditionalPrivilege).select(
|
|
||||||
selectAllTableCols(TableName.IdentityProjectAdditionalPrivilege)
|
|
||||||
);
|
|
||||||
const projectUserAdditionalPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select(
|
|
||||||
selectAllTableCols(TableName.ProjectUserAdditionalPrivilege)
|
|
||||||
);
|
|
||||||
|
|
||||||
const serviceTokens = await knex(TableName.ServiceToken).select(selectAllTableCols(TableName.ServiceToken));
|
|
||||||
|
|
||||||
const updatedServiceTokens = serviceTokens.reduce<typeof serviceTokens>((acc, serviceToken) => {
|
|
||||||
const { permissions } = serviceToken; // Service tokens are special, and include an array of actions only.
|
|
||||||
|
|
||||||
if (permissions.includes(SecretActions.Read) && !permissions.includes(SecretActions.ReadValue)) {
|
|
||||||
permissions.push(SecretActions.ReadValue);
|
|
||||||
acc.push({
|
|
||||||
...serviceToken,
|
|
||||||
permissions
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (updatedServiceTokens.length > 0) {
|
|
||||||
for (let i = 0; i < updatedServiceTokens.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedServiceTokens.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ServiceToken)
|
|
||||||
.whereIn(
|
|
||||||
"id",
|
|
||||||
chunk.map((t) => t.id)
|
|
||||||
)
|
|
||||||
.update({
|
|
||||||
// @ts-expect-error -- raw query
|
|
||||||
permissions: knex.raw(
|
|
||||||
`CASE id
|
|
||||||
${chunk.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")}
|
|
||||||
END`,
|
|
||||||
chunk.map((t) => t.permissions)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedRoles = projectRoles.reduce<typeof projectRoles>((acc, projectRole) => {
|
|
||||||
const { shouldUpdate, parsedPermissions } = $updatePermissionsUp(projectRole.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...projectRole,
|
|
||||||
permissions: JSON.stringify(packRules(parsedPermissions))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updatedIdentityAdditionalPrivileges = projectIdentityAdditionalPrivileges.reduce<
|
|
||||||
typeof projectIdentityAdditionalPrivileges
|
|
||||||
>((acc, identityAdditionalPrivilege) => {
|
|
||||||
const { shouldUpdate, parsedPermissions } = $updatePermissionsUp(identityAdditionalPrivilege.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...identityAdditionalPrivilege,
|
|
||||||
permissions: JSON.stringify(packRules(parsedPermissions))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updatedUserAdditionalPrivileges = projectUserAdditionalPrivileges.reduce<
|
|
||||||
typeof projectUserAdditionalPrivileges
|
|
||||||
>((acc, userAdditionalPrivilege) => {
|
|
||||||
const { shouldUpdate, parsedPermissions } = $updatePermissionsUp(userAdditionalPrivilege.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...userAdditionalPrivilege,
|
|
||||||
permissions: JSON.stringify(packRules(parsedPermissions))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (updatedRoles.length > 0) {
|
|
||||||
for (let i = 0; i < updatedRoles.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedRoles.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ProjectRoles).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedIdentityAdditionalPrivileges.length > 0) {
|
|
||||||
for (let i = 0; i < updatedIdentityAdditionalPrivileges.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedIdentityAdditionalPrivileges.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.IdentityProjectAdditionalPrivilege).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedUserAdditionalPrivileges.length > 0) {
|
|
||||||
for (let i = 0; i < updatedUserAdditionalPrivileges.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedUserAdditionalPrivileges.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ProjectUserAdditionalPrivilege).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
const projectRoles = await knex(TableName.ProjectRoles).select(selectAllTableCols(TableName.ProjectRoles));
|
|
||||||
const identityAdditionalPrivileges = await knex(TableName.IdentityProjectAdditionalPrivilege).select(
|
|
||||||
selectAllTableCols(TableName.IdentityProjectAdditionalPrivilege)
|
|
||||||
);
|
|
||||||
const userAdditionalPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select(
|
|
||||||
selectAllTableCols(TableName.ProjectUserAdditionalPrivilege)
|
|
||||||
);
|
|
||||||
const serviceTokens = await knex(TableName.ServiceToken).select(selectAllTableCols(TableName.ServiceToken));
|
|
||||||
|
|
||||||
const updatedServiceTokens = serviceTokens.reduce<typeof serviceTokens>((acc, serviceToken) => {
|
|
||||||
const { permissions } = serviceToken;
|
|
||||||
|
|
||||||
if (permissions.includes(SecretActions.ReadValue)) {
|
|
||||||
permissions.splice(permissions.indexOf(SecretActions.ReadValue), 1);
|
|
||||||
acc.push({
|
|
||||||
...serviceToken,
|
|
||||||
permissions
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (updatedServiceTokens.length > 0) {
|
|
||||||
for (let i = 0; i < updatedServiceTokens.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedServiceTokens.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ServiceToken)
|
|
||||||
.whereIn(
|
|
||||||
"id",
|
|
||||||
chunk.map((t) => t.id)
|
|
||||||
)
|
|
||||||
.update({
|
|
||||||
// @ts-expect-error -- raw query
|
|
||||||
permissions: knex.raw(
|
|
||||||
`CASE id
|
|
||||||
${chunk.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")}
|
|
||||||
END`,
|
|
||||||
chunk.map((t) => t.permissions)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedRoles = projectRoles.reduce<typeof projectRoles>((acc, projectRole) => {
|
|
||||||
const { shouldUpdate, repackedPermissions } = $updatePermissionsDown(projectRole.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...projectRole,
|
|
||||||
permissions: JSON.stringify(repackedPermissions)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updatedIdentityAdditionalPrivileges = identityAdditionalPrivileges.reduce<typeof identityAdditionalPrivileges>(
|
|
||||||
(acc, identityAdditionalPrivilege) => {
|
|
||||||
const { shouldUpdate, repackedPermissions } = $updatePermissionsDown(identityAdditionalPrivilege.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...identityAdditionalPrivilege,
|
|
||||||
permissions: JSON.stringify(repackedPermissions)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedUserAdditionalPrivileges = userAdditionalPrivileges.reduce<typeof userAdditionalPrivileges>(
|
|
||||||
(acc, userAdditionalPrivilege) => {
|
|
||||||
const { shouldUpdate, repackedPermissions } = $updatePermissionsDown(userAdditionalPrivilege.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...userAdditionalPrivilege,
|
|
||||||
permissions: JSON.stringify(repackedPermissions)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updatedRoles.length > 0) {
|
|
||||||
for (let i = 0; i < updatedRoles.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedRoles.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ProjectRoles).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedIdentityAdditionalPrivileges.length > 0) {
|
|
||||||
for (let i = 0; i < updatedIdentityAdditionalPrivileges.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedIdentityAdditionalPrivileges.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.IdentityProjectAdditionalPrivilege).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedUserAdditionalPrivileges.length > 0) {
|
|
||||||
for (let i = 0; i < updatedUserAdditionalPrivileges.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedUserAdditionalPrivileges.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ProjectUserAdditionalPrivilege).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretApprovalRequestsReviewersSchema, SecretApprovalRequestsSchema, UsersSchema } from "@app/db/schemas";
|
import {
|
||||||
|
SecretApprovalRequestsReviewersSchema,
|
||||||
|
SecretApprovalRequestsSchema,
|
||||||
|
SecretTagsSchema,
|
||||||
|
UsersSchema
|
||||||
|
} 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 { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
|
|
||||||
@@ -245,6 +250,14 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tagSchema = SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional();
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:id",
|
url: "/:id",
|
||||||
@@ -278,7 +291,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
.omit({ _id: true, environment: true, workspace: true, type: true, version: true })
|
.omit({ _id: true, environment: true, workspace: true, type: true, version: true })
|
||||||
.extend({
|
.extend({
|
||||||
op: z.string(),
|
op: z.string(),
|
||||||
tags: SanitizedTagSchema.array().optional(),
|
tags: tagSchema,
|
||||||
secretMetadata: ResourceMetadataSchema.nullish(),
|
secretMetadata: ResourceMetadataSchema.nullish(),
|
||||||
secret: z
|
secret: z
|
||||||
.object({
|
.object({
|
||||||
@@ -297,7 +310,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
secretKey: z.string(),
|
secretKey: z.string(),
|
||||||
secretValue: z.string().optional(),
|
secretValue: z.string().optional(),
|
||||||
secretComment: z.string().optional(),
|
secretComment: z.string().optional(),
|
||||||
tags: SanitizedTagSchema.array().optional(),
|
tags: tagSchema,
|
||||||
secretMetadata: ResourceMetadataSchema.nullish()
|
secretMetadata: ResourceMetadataSchema.nullish()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions } from "@app/ee/services/permission/project-permission";
|
||||||
import { RAW_SECRETS } from "@app/lib/api-docs";
|
import { RAW_SECRETS } from "@app/lib/api-docs";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -9,7 +9,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
|
|
||||||
const AccessListEntrySchema = z
|
const AccessListEntrySchema = z
|
||||||
.object({
|
.object({
|
||||||
allowedActions: z.nativeEnum(ProjectPermissionSecretActions).array(),
|
allowedActions: z.nativeEnum(ProjectPermissionActions).array(),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
membershipId: z.string(),
|
membershipId: z.string(),
|
||||||
name: z.string()
|
name: z.string()
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secretVersions: secretRawSchema
|
secretVersions: secretRawSchema.array()
|
||||||
.extend({
|
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -41,7 +37,6 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
|||||||
offset: req.query.offset,
|
offset: req.query.offset,
|
||||||
secretId: req.params.secretId
|
secretId: req.params.secretId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secretVersions };
|
return { secretVersions };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretSnapshotsSchema } from "@app/db/schemas";
|
import { SecretSnapshotsSchema, SecretTagsSchema } from "@app/db/schemas";
|
||||||
import { PROJECTS } from "@app/lib/api-docs";
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -31,9 +31,12 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretVersions: secretRawSchema
|
secretVersions: secretRawSchema
|
||||||
.omit({ _id: true, environment: true, workspace: true, type: true })
|
.omit({ _id: true, environment: true, workspace: true, type: true })
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretId: z.string(),
|
secretId: z.string(),
|
||||||
tags: SanitizedTagSchema.array()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
}).array()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
|
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
|
||||||
@@ -52,7 +55,6 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.secretSnapshotId
|
id: req.params.secretSnapshotId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secretSnapshot };
|
return { secretSnapshot };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,14 +17,6 @@ export enum ProjectPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretActions {
|
|
||||||
DescribeSecret = "read",
|
|
||||||
ReadValue = "readValue",
|
|
||||||
Create = "create",
|
|
||||||
Edit = "edit",
|
|
||||||
Delete = "delete"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProjectPermissionCmekActions {
|
export enum ProjectPermissionCmekActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@@ -123,7 +115,7 @@ export type IdentityManagementSubjectFields = {
|
|||||||
|
|
||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
||||||
]
|
]
|
||||||
| [
|
| [
|
||||||
@@ -437,7 +429,6 @@ const GeneralPermissionSchema = [
|
|||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
// Do not update this schema anymore, as it's kept purely for backwards compatability. Update V2 schema only.
|
|
||||||
export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
@@ -469,7 +460,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
|||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
),
|
),
|
||||||
conditions: SecretConditionV2Schema.describe(
|
conditions: SecretConditionV2Schema.describe(
|
||||||
@@ -526,6 +517,7 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
// Admins get full access to everything
|
// Admins get full access to everything
|
||||||
[
|
[
|
||||||
|
ProjectPermissionSub.Secrets,
|
||||||
ProjectPermissionSub.SecretFolders,
|
ProjectPermissionSub.SecretFolders,
|
||||||
ProjectPermissionSub.SecretImports,
|
ProjectPermissionSub.SecretImports,
|
||||||
ProjectPermissionSub.SecretApproval,
|
ProjectPermissionSub.SecretApproval,
|
||||||
@@ -558,21 +550,10 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionActions.Delete
|
ProjectPermissionActions.Delete
|
||||||
],
|
],
|
||||||
el
|
el as ProjectPermissionSub
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
ProjectPermissionSecretActions.Create,
|
|
||||||
ProjectPermissionSecretActions.Edit,
|
|
||||||
ProjectPermissionSecretActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
@@ -632,11 +613,10 @@ const buildMemberPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Edit,
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionActions.Delete
|
||||||
ProjectPermissionSecretActions.Delete
|
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Secrets
|
ProjectPermissionSub.Secrets
|
||||||
);
|
);
|
||||||
@@ -808,8 +788,7 @@ export const projectMemberPermissions = buildMemberPermissionRules();
|
|||||||
const buildViewerPermissionRules = () => {
|
const buildViewerPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||||
@@ -852,8 +831,6 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
) => {
|
) => {
|
||||||
const canWrite = permission.includes("write");
|
const canWrite = permission.includes("write");
|
||||||
const canRead = permission.includes("read");
|
const canRead = permission.includes("read");
|
||||||
const canReadValue = permission.includes("readValue");
|
|
||||||
|
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
scopes.forEach(({ secretPath, environment }) => {
|
scopes.forEach(({ secretPath, environment }) => {
|
||||||
[ProjectPermissionSub.Secrets, ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretFolders].forEach(
|
[ProjectPermissionSub.Secrets, ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretFolders].forEach(
|
||||||
@@ -883,14 +860,6 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.Secrets && canReadValue) {
|
|
||||||
// @ts-expect-error type
|
|
||||||
can(ProjectPermissionSecretActions.ReadValue, subject as ProjectPermissionSub.Secrets, {
|
|
||||||
secretPath: { $glob: secretPath },
|
|
||||||
environment
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -947,17 +916,7 @@ export const backfillPermissionV1SchemaToV2Schema = (
|
|||||||
subject: ProjectPermissionSub.SecretImports as const
|
subject: ProjectPermissionSub.SecretImports as const
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const secretPolicies = secretSubjects.map(({ subject, ...el }) => ({
|
|
||||||
subject: ProjectPermissionSub.Secrets as const,
|
|
||||||
...el,
|
|
||||||
action:
|
|
||||||
el.action.includes(ProjectPermissionActions.Read) && !el.action.includes(ProjectPermissionSecretActions.ReadValue)
|
|
||||||
? el.action.concat(ProjectPermissionSecretActions.ReadValue)
|
|
||||||
: el.action
|
|
||||||
}));
|
|
||||||
|
|
||||||
const secretFolderPolicies = secretSubjects
|
const secretFolderPolicies = secretSubjects
|
||||||
|
|
||||||
.map(({ subject, ...el }) => ({
|
.map(({ subject, ...el }) => ({
|
||||||
...el,
|
...el,
|
||||||
// read permission is not needed anymore
|
// read permission is not needed anymore
|
||||||
@@ -999,7 +958,6 @@ export const backfillPermissionV1SchemaToV2Schema = (
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore-error this is valid ts
|
// @ts-ignore-error this is valid ts
|
||||||
secretImportPolicies,
|
secretImportPolicies,
|
||||||
secretPolicies,
|
|
||||||
dynamicSecretPolicies,
|
dynamicSecretPolicies,
|
||||||
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
||||||
@@ -88,12 +88,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretDAL: TSecretDALFactory;
|
secretDAL: TSecretDALFactory;
|
||||||
secretTagDAL: Pick<
|
secretTagDAL: Pick<
|
||||||
TSecretTagDALFactory,
|
TSecretTagDALFactory,
|
||||||
| "findManyTagsById"
|
"findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret" | "saveTagsToSecretV2" | "deleteTagsToSecretV2"
|
||||||
| "saveTagsToSecret"
|
|
||||||
| "deleteTagsManySecret"
|
|
||||||
| "saveTagsToSecretV2"
|
|
||||||
| "deleteTagsToSecretV2"
|
|
||||||
| "find"
|
|
||||||
>;
|
>;
|
||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
@@ -111,7 +106,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
|
||||||
secretV2BridgeDAL: Pick<
|
secretV2BridgeDAL: Pick<
|
||||||
TSecretV2BridgeDALFactory,
|
TSecretV2BridgeDALFactory,
|
||||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany" | "find"
|
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany"
|
||||||
>;
|
>;
|
||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
@@ -919,7 +914,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1006,7 +1001,6 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
: keyName2BlindIndex[secretName];
|
: keyName2BlindIndex[secretName];
|
||||||
// add tags
|
// add tags
|
||||||
if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds;
|
if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...latestSecretVersions[secretId],
|
...latestSecretVersions[secretId],
|
||||||
...el,
|
...el,
|
||||||
@@ -1369,9 +1363,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const tagsGroupById = groupBy(tags, (i) => i.id);
|
const tagsGroupById = groupBy(tags, (i) => i.id);
|
||||||
|
|
||||||
commits.forEach((commit) => {
|
commits.forEach((commit) => {
|
||||||
let action = ProjectPermissionSecretActions.Create;
|
let action = ProjectPermissionActions.Create;
|
||||||
if (commit.op === SecretOperations.Update) action = ProjectPermissionSecretActions.Edit;
|
if (commit.op === SecretOperations.Update) action = ProjectPermissionActions.Edit;
|
||||||
if (commit.op === SecretOperations.Delete) action = ProjectPermissionSecretActions.Delete;
|
if (commit.op === SecretOperations.Delete) action = ProjectPermissionActions.Delete;
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
action,
|
action,
|
||||||
|
|||||||
@@ -265,7 +265,6 @@ export const secretReplicationServiceFactory = ({
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
viewSecretValue: true,
|
|
||||||
hasSecretAccess: () => true
|
hasSecretAccess: () => true
|
||||||
});
|
});
|
||||||
// secrets that gets replicated across imports
|
// secrets that gets replicated across imports
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "../permission/project-permission";
|
|
||||||
import { TSecretRotationDALFactory } from "./secret-rotation-dal";
|
import { TSecretRotationDALFactory } from "./secret-rotation-dal";
|
||||||
import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
|
import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
|
||||||
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
|
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
|
||||||
@@ -110,7 +106,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/se
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "../permission/project-permission";
|
|
||||||
import {
|
import {
|
||||||
TGetSnapshotDataDTO,
|
TGetSnapshotDataDTO,
|
||||||
TProjectSnapshotCountDTO,
|
TProjectSnapshotCountDTO,
|
||||||
@@ -38,7 +34,6 @@ import { TSnapshotFolderDALFactory } from "./snapshot-folder-dal";
|
|||||||
import { TSnapshotSecretDALFactory } from "./snapshot-secret-dal";
|
import { TSnapshotSecretDALFactory } from "./snapshot-secret-dal";
|
||||||
import { TSnapshotSecretV2DALFactory } from "./snapshot-secret-v2-dal";
|
import { TSnapshotSecretV2DALFactory } from "./snapshot-secret-v2-dal";
|
||||||
import { getFullFolderPath } from "./snapshot-service-fns";
|
import { getFullFolderPath } from "./snapshot-service-fns";
|
||||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "@app/services/secret/secret-fns";
|
|
||||||
|
|
||||||
type TSecretSnapshotServiceFactoryDep = {
|
type TSecretSnapshotServiceFactoryDep = {
|
||||||
snapshotDAL: TSnapshotDALFactory;
|
snapshotDAL: TSnapshotDALFactory;
|
||||||
@@ -102,7 +97,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -139,7 +134,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -166,7 +161,6 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
const shouldUseBridge = snapshot.projectVersion === 3;
|
const shouldUseBridge = snapshot.projectVersion === 3;
|
||||||
let snapshotDetails;
|
let snapshotDetails;
|
||||||
if (shouldUseBridge) {
|
if (shouldUseBridge) {
|
||||||
@@ -175,110 +169,68 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
projectId: snapshot.projectId
|
projectId: snapshot.projectId
|
||||||
});
|
});
|
||||||
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotV2DataById(id);
|
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotV2DataById(id);
|
||||||
|
|
||||||
const fullFolderPath = await getFullFolderPath({
|
|
||||||
folderDAL,
|
|
||||||
folderId: encryptedSnapshotDetails.folderId,
|
|
||||||
envId: encryptedSnapshotDetails.environment.id
|
|
||||||
});
|
|
||||||
|
|
||||||
snapshotDetails = {
|
snapshotDetails = {
|
||||||
...encryptedSnapshotDetails,
|
...encryptedSnapshotDetails,
|
||||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => {
|
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
|
||||||
const canReadValue = permission.can(
|
...el,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
secretKey: el.key,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretValue: el.encryptedValue
|
||||||
environment: encryptedSnapshotDetails.environment.slug,
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||||
secretPath: fullFolderPath,
|
: "",
|
||||||
secretName: el.key,
|
secretComment: el.encryptedComment
|
||||||
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||||
})
|
: ""
|
||||||
);
|
}))
|
||||||
|
|
||||||
let secretValue = "";
|
|
||||||
if (canReadValue) {
|
|
||||||
secretValue = el.encryptedValue
|
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
|
||||||
: "";
|
|
||||||
} else {
|
|
||||||
secretValue = INFISICAL_SECRET_VALUE_HIDDEN_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...el,
|
|
||||||
secretKey: el.key,
|
|
||||||
secretValueHidden: !canReadValue,
|
|
||||||
secretValue,
|
|
||||||
secretComment: el.encryptedComment
|
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
|
||||||
: ""
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotDataById(id);
|
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotDataById(id);
|
||||||
|
|
||||||
const fullFolderPath = await getFullFolderPath({
|
|
||||||
folderDAL,
|
|
||||||
folderId: encryptedSnapshotDetails.folderId,
|
|
||||||
envId: encryptedSnapshotDetails.environment.id
|
|
||||||
});
|
|
||||||
|
|
||||||
const { botKey } = await projectBotService.getBotKey(snapshot.projectId);
|
const { botKey } = await projectBotService.getBotKey(snapshot.projectId);
|
||||||
if (!botKey)
|
if (!botKey)
|
||||||
throw new NotFoundError({ message: `Project bot key not found for project with ID '${snapshot.projectId}'` });
|
throw new NotFoundError({ message: `Project bot key not found for project with ID '${snapshot.projectId}'` });
|
||||||
snapshotDetails = {
|
snapshotDetails = {
|
||||||
...encryptedSnapshotDetails,
|
...encryptedSnapshotDetails,
|
||||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => {
|
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
|
||||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
...el,
|
||||||
|
secretKey: decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: el.secretKeyCiphertext,
|
ciphertext: el.secretKeyCiphertext,
|
||||||
iv: el.secretKeyIV,
|
iv: el.secretKeyIV,
|
||||||
tag: el.secretKeyTag,
|
tag: el.secretKeyTag,
|
||||||
key: botKey
|
key: botKey
|
||||||
});
|
}),
|
||||||
|
secretValue: decryptSymmetric128BitHexKeyUTF8({
|
||||||
const canReadValue = permission.can(
|
ciphertext: el.secretValueCiphertext,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
iv: el.secretValueIV,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
tag: el.secretValueTag,
|
||||||
environment: encryptedSnapshotDetails.environment.slug,
|
key: botKey
|
||||||
secretPath: fullFolderPath,
|
}),
|
||||||
secretName: secretKey,
|
secretComment:
|
||||||
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
el.secretCommentTag && el.secretCommentIV && el.secretCommentCiphertext
|
||||||
})
|
? decryptSymmetric128BitHexKeyUTF8({
|
||||||
);
|
ciphertext: el.secretCommentCiphertext,
|
||||||
|
iv: el.secretCommentIV,
|
||||||
let secretValue = "";
|
tag: el.secretCommentTag,
|
||||||
|
key: botKey
|
||||||
if (canReadValue) {
|
})
|
||||||
secretValue = decryptSymmetric128BitHexKeyUTF8({
|
: ""
|
||||||
ciphertext: el.secretValueCiphertext,
|
}))
|
||||||
iv: el.secretValueIV,
|
|
||||||
tag: el.secretValueTag,
|
|
||||||
key: botKey
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
secretValue = INFISICAL_SECRET_VALUE_HIDDEN_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...el,
|
|
||||||
secretKey,
|
|
||||||
secretValueHidden: !canReadValue,
|
|
||||||
secretValue,
|
|
||||||
secretComment:
|
|
||||||
el.secretCommentTag && el.secretCommentIV && el.secretCommentCiphertext
|
|
||||||
? decryptSymmetric128BitHexKeyUTF8({
|
|
||||||
ciphertext: el.secretCommentCiphertext,
|
|
||||||
iv: el.secretCommentIV,
|
|
||||||
tag: el.secretCommentTag,
|
|
||||||
key: botKey
|
|
||||||
})
|
|
||||||
: ""
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fullFolderPath = await getFullFolderPath({
|
||||||
|
folderDAL,
|
||||||
|
folderId: snapshotDetails.folderId,
|
||||||
|
envId: snapshotDetails.environment.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: snapshotDetails.environment.slug,
|
||||||
|
secretPath: fullFolderPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return snapshotDetails;
|
return snapshotDetails;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -666,7 +666,6 @@ export const SECRETS = {
|
|||||||
secretPath: "The path of the secret to attach tags to.",
|
secretPath: "The path of the secret to attach tags to.",
|
||||||
type: "The type of the secret to attach tags to. (shared/personal)",
|
type: "The type of the secret to attach tags to. (shared/personal)",
|
||||||
environment: "The slug of the environment where the secret is located",
|
environment: "The slug of the environment where the secret is located",
|
||||||
viewSecretValue: "Whether or not to retrieve the secret value.",
|
|
||||||
projectSlug: "The slug of the project where the secret is located.",
|
projectSlug: "The slug of the project where the secret is located.",
|
||||||
tagSlugs: "An array of existing tag slugs to attach to the secret."
|
tagSlugs: "An array of existing tag slugs to attach to the secret."
|
||||||
},
|
},
|
||||||
@@ -690,7 +689,6 @@ export const RAW_SECRETS = {
|
|||||||
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
||||||
environment: "The slug of the environment to list secrets from.",
|
environment: "The slug of the environment to list secrets from.",
|
||||||
secretPath: "The secret path to list secrets from.",
|
secretPath: "The secret path to list secrets from.",
|
||||||
viewSecretValue: "Whether or not to retrieve the secret value.",
|
|
||||||
includeImports: "Weather to include imported secrets or not.",
|
includeImports: "Weather to include imported secrets or not.",
|
||||||
tagSlugs: "The comma separated tag slugs to filter secrets.",
|
tagSlugs: "The comma separated tag slugs to filter secrets.",
|
||||||
metadataFilter:
|
metadataFilter:
|
||||||
@@ -719,7 +717,6 @@ export const RAW_SECRETS = {
|
|||||||
secretPath: "The path of the secret to get.",
|
secretPath: "The path of the secret to get.",
|
||||||
version: "The version of the secret to get.",
|
version: "The version of the secret to get.",
|
||||||
type: "The type of the secret to get.",
|
type: "The type of the secret to get.",
|
||||||
viewSecretValue: "Whether or not to retrieve the secret value.",
|
|
||||||
includeImports: "Weather to include imported secrets or not."
|
includeImports: "Weather to include imported secrets or not."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
export class DatabaseError extends Error {
|
export class DatabaseError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
ProjectRolesSchema,
|
ProjectRolesSchema,
|
||||||
ProjectsSchema,
|
ProjectsSchema,
|
||||||
SecretApprovalPoliciesSchema,
|
SecretApprovalPoliciesSchema,
|
||||||
SecretTagsSchema,
|
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
@@ -233,11 +232,3 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
|
|||||||
kmsCertificateKeyId: true,
|
kmsCertificateKeyId: true,
|
||||||
auditLogsRetentionDays: true
|
auditLogsRetentionDays: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedTagSchema = SecretTagsSchema.pick({
|
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
color: true
|
|
||||||
}).extend({
|
|
||||||
name: z.string()
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ActionProjectType, SecretFoldersSchema, SecretImportsSchema } from "@app/db/schemas";
|
import { ActionProjectType, SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { DASHBOARD } from "@app/lib/api-docs";
|
import { DASHBOARD } from "@app/lib/api-docs";
|
||||||
@@ -16,7 +15,7 @@ import { secretsLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedDynamicSecretSchema, SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedDynamicSecretSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
@@ -117,10 +116,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
dynamicSecrets: SanitizedDynamicSecretSchema.extend({ environment: z.string() }).array().optional(),
|
dynamicSecrets: SanitizedDynamicSecretSchema.extend({ environment: z.string() }).array().optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -289,7 +294,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||||
secrets = await server.services.secret.getSecretsRawMultiEnv({
|
secrets = await server.services.secret.getSecretsRawMultiEnv({
|
||||||
viewSecretValue: true,
|
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
@@ -389,7 +393,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
|
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
|
||||||
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
|
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
|
||||||
viewSecretValue: booleanSchema.default(true),
|
|
||||||
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
||||||
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
||||||
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
||||||
@@ -407,10 +410,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
dynamicSecrets: SanitizedDynamicSecretSchema.array().optional(),
|
dynamicSecrets: SanitizedDynamicSecretSchema.array().optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -592,25 +601,23 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||||
secrets = (
|
const secretsRaw = await server.services.secret.getSecretsRaw({
|
||||||
await server.services.secret.getSecretsRaw({
|
actorId: req.permission.id,
|
||||||
actorId: req.permission.id,
|
actor: req.permission.type,
|
||||||
actor: req.permission.type,
|
actorOrgId: req.permission.orgId,
|
||||||
viewSecretValue: req.query.viewSecretValue,
|
environment,
|
||||||
throwOnMissingReadValuePermission: false,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
projectId,
|
||||||
environment,
|
path: secretPath,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
orderBy,
|
||||||
projectId,
|
orderDirection,
|
||||||
path: secretPath,
|
search,
|
||||||
orderBy,
|
limit: remainingLimit,
|
||||||
orderDirection,
|
offset: adjustedOffset,
|
||||||
search,
|
tagSlugs: tags
|
||||||
limit: remainingLimit,
|
});
|
||||||
offset: adjustedOffset,
|
|
||||||
tagSlugs: tags
|
secrets = secretsRaw.secrets;
|
||||||
})
|
|
||||||
).secrets;
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -689,10 +696,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -736,7 +749,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
const secrets = await server.services.secret.getSecretsRawByFolderMappings(
|
const secrets = await server.services.secret.getSecretsRawByFolderMappings(
|
||||||
{
|
{
|
||||||
filterByAction: ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
projectId,
|
projectId,
|
||||||
folderMappings,
|
folderMappings,
|
||||||
filters: {
|
filters: {
|
||||||
@@ -850,17 +862,22 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: z.string().trim(),
|
projectId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||||
keys: z.string().trim().transform(decodeURIComponent),
|
keys: z.string().trim().transform(decodeURIComponent)
|
||||||
viewSecretValue: booleanSchema.default(false)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -869,7 +886,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { secretPath, projectId, environment, viewSecretValue } = req.query;
|
const { secretPath, projectId, environment } = req.query;
|
||||||
|
|
||||||
const keys = req.query.keys?.split(",").filter((key) => Boolean(key.trim())) ?? [];
|
const keys = req.query.keys?.split(",").filter((key) => Boolean(key.trim())) ?? [];
|
||||||
if (!keys.length) throw new BadRequestError({ message: "One or more keys required" });
|
if (!keys.length) throw new BadRequestError({ message: "One or more keys required" });
|
||||||
@@ -878,7 +895,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
viewSecretValue,
|
|
||||||
environment,
|
environment,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
|
|||||||
iv: z.string().trim(),
|
iv: z.string().trim(),
|
||||||
tag: z.string().trim(),
|
tag: z.string().trim(),
|
||||||
expiresIn: z.number().nullable(),
|
expiresIn: z.number().nullable(),
|
||||||
permissions: z.enum(["read", "write", "readValue"]).array()
|
permissions: z.enum(["read", "write"]).array()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretApprovalRequestsSchema, SecretsSchema, SecretType, ServiceTokenScopes } from "@app/db/schemas";
|
import {
|
||||||
|
SecretApprovalRequestsSchema,
|
||||||
|
SecretsSchema,
|
||||||
|
SecretTagsSchema,
|
||||||
|
SecretType,
|
||||||
|
ServiceTokenScopes
|
||||||
|
} from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
|
import { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -17,7 +23,7 @@ import { SecretOperations, SecretProtectionType } from "@app/services/secret/sec
|
|||||||
import { SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
import { SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
import { SanitizedTagSchema, secretRawSchema } from "../sanitizedSchemas";
|
import { secretRawSchema } from "../sanitizedSchemas";
|
||||||
|
|
||||||
const SecretReferenceNode = z.object({
|
const SecretReferenceNode = z.object({
|
||||||
key: z.string(),
|
key: z.string(),
|
||||||
@@ -25,14 +31,6 @@ const SecretReferenceNode = z.object({
|
|||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
secretPath: z.string()
|
secretPath: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
const convertStringBoolean = (defaultValue: boolean = false) => {
|
|
||||||
return z
|
|
||||||
.enum(["true", "false"])
|
|
||||||
.default(defaultValue ? "true" : "false")
|
|
||||||
.transform((value) => value === "true");
|
|
||||||
};
|
|
||||||
|
|
||||||
type TSecretReferenceNode = z.infer<typeof SecretReferenceNode> & { children: TSecretReferenceNode[] };
|
type TSecretReferenceNode = z.infer<typeof SecretReferenceNode> & { children: TSecretReferenceNode[] };
|
||||||
|
|
||||||
const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReferenceNode.extend({
|
const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReferenceNode.extend({
|
||||||
@@ -77,9 +75,17 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||||
tags: SanitizedTagSchema.array()
|
z.object({
|
||||||
})
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -133,7 +139,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
||||||
tags: SanitizedTagSchema.array()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -235,10 +247,21 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
|
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
|
||||||
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
||||||
viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.LIST.viewSecretValue),
|
expandSecretReferences: z
|
||||||
expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.LIST.expand),
|
.enum(["true", "false"])
|
||||||
recursive: convertStringBoolean().describe(RAW_SECRETS.LIST.recursive),
|
.default("false")
|
||||||
include_imports: convertStringBoolean().describe(RAW_SECRETS.LIST.includeImports),
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.LIST.expand),
|
||||||
|
recursive: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.LIST.recursive),
|
||||||
|
include_imports: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.LIST.includeImports),
|
||||||
tagSlugs: z
|
tagSlugs: z
|
||||||
.string()
|
.string()
|
||||||
.describe(RAW_SECRETS.LIST.tagSlugs)
|
.describe(RAW_SECRETS.LIST.tagSlugs)
|
||||||
@@ -251,9 +274,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
imports: z
|
imports: z
|
||||||
@@ -264,7 +293,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.omit({ createdAt: true, updatedAt: true })
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretMetadata: ResourceMetadataSchema.optional()
|
secretMetadata: ResourceMetadataSchema.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
@@ -314,7 +342,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
expandSecretReferences: req.query.expandSecretReferences,
|
expandSecretReferences: req.query.expandSecretReferences,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
viewSecretValue: req.query.viewSecretValue,
|
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
metadataFilter: req.query.metadataFilter,
|
metadataFilter: req.query.metadataFilter,
|
||||||
includeImports: req.query.include_imports,
|
includeImports: req.query.include_imports,
|
||||||
@@ -349,7 +376,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { secrets, imports };
|
return { secrets, imports };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -377,15 +403,28 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
|
||||||
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
||||||
viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.GET.viewSecretValue),
|
expandSecretReferences: z
|
||||||
expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.GET.expand),
|
.enum(["true", "false"])
|
||||||
include_imports: convertStringBoolean().describe(RAW_SECRETS.GET.includeImports)
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.GET.expand),
|
||||||
|
include_imports: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.GET.includeImports)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: secretRawSchema.extend({
|
secret: secretRawSchema.extend({
|
||||||
secretValueHidden: z.boolean(),
|
tags: SecretTagsSchema.pick({
|
||||||
tags: SanitizedTagSchema.array().optional(),
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional()
|
secretMetadata: ResourceMetadataSchema.optional()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -417,7 +456,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
expandSecretReferences: req.query.expandSecretReferences,
|
expandSecretReferences: req.query.expandSecretReferences,
|
||||||
environment,
|
environment,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
viewSecretValue: req.query.viewSecretValue,
|
|
||||||
projectSlug: workspaceSlug,
|
projectSlug: workspaceSlug,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
secretName: req.params.secretName,
|
secretName: req.params.secretName,
|
||||||
@@ -624,9 +662,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secret: secretRawSchema.extend({
|
secret: secretRawSchema
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -722,9 +758,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secret: secretRawSchema.extend({
|
secret: secretRawSchema
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -746,7 +780,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
if (secretOperation.type === SecretProtectionType.Approval) {
|
if (secretOperation.type === SecretProtectionType.Approval) {
|
||||||
return { approval: secretOperation.approval };
|
return { approval: secretOperation.approval };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { secret } = secretOperation;
|
const { secret } = secretOperation;
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -809,7 +842,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
workspace: z.string(),
|
workspace: z.string(),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
tags: SanitizedTagSchema.array()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
imports: z
|
imports: z
|
||||||
@@ -905,7 +944,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
||||||
version: z.coerce.number().optional(),
|
version: z.coerce.number().optional(),
|
||||||
include_imports: convertStringBoolean()
|
include_imports: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -1176,7 +1218,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
z.object({
|
z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||||
z.object({
|
z.object({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
_id: z.string(),
|
_id: z.string(),
|
||||||
workspace: z.string(),
|
workspace: z.string(),
|
||||||
environment: z.string()
|
environment: z.string()
|
||||||
@@ -1346,12 +1387,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||||
_id: z.string(),
|
z.object({
|
||||||
secretValueHidden: z.boolean(),
|
_id: z.string(),
|
||||||
workspace: z.string(),
|
workspace: z.string(),
|
||||||
environment: z.string()
|
environment: z.string()
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -1663,7 +1705,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).extend({ secretValueHidden: z.boolean() }).array()
|
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -1778,11 +1820,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true })
|
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||||
.extend({
|
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -2044,7 +2082,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: secretRawSchema.extend({ secretValueHidden: z.boolean() }).array()
|
secrets: secretRawSchema.array()
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -2166,11 +2204,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema.array()
|
||||||
.extend({
|
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ export type TImportDataIntoInfisicalDTO = {
|
|||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "find">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create" | "find">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ export type TExternalMigrationQueueFactoryDep = {
|
|||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "find">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create" | "find">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findOne" | "findById">;
|
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findOne" | "findById">;
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ const getIntegrationSecretsV2 = async (
|
|||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
secretImports,
|
secretImports,
|
||||||
hasSecretAccess: () => true,
|
hasSecretAccess: () => true
|
||||||
viewSecretValue: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
@@ -96,7 +92,7 @@ export const integrationServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment,
|
||||||
secretPath
|
secretPath
|
||||||
@@ -179,7 +175,7 @@ export const integrationServiceFactory = ({
|
|||||||
|
|
||||||
if (environment || secretPath) {
|
if (environment || secretPath) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: newEnvironment,
|
environment: newEnvironment,
|
||||||
secretPath: newSecretPath
|
secretPath: newSecretPath
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ import {
|
|||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
|
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
|
||||||
import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types";
|
import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types";
|
||||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||||
@@ -751,10 +747,7 @@ export const projectServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.Any
|
actionProjectType: ActionProjectType.Any
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
|
|
||||||
const project = await projectDAL.findProjectById(projectId);
|
const project = await projectDAL.findProjectById(projectId);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { groupBy, unique } from "@app/lib/fn";
|
|||||||
|
|
||||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||||
import { TSecretDALFactory } from "../secret/secret-dal";
|
import { TSecretDALFactory } from "../secret/secret-dal";
|
||||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
import { TSecretImportDALFactory } from "./secret-import-dal";
|
import { TSecretImportDALFactory } from "./secret-import-dal";
|
||||||
@@ -33,12 +32,6 @@ type TSecretImportSecretsV2 = {
|
|||||||
folderId: string | undefined;
|
folderId: string | undefined;
|
||||||
importFolderId: string;
|
importFolderId: string;
|
||||||
secrets: (TSecretsV2 & {
|
secrets: (TSecretsV2 & {
|
||||||
secretTags: {
|
|
||||||
slug: string;
|
|
||||||
name: string;
|
|
||||||
color?: string | null;
|
|
||||||
id: string;
|
|
||||||
}[];
|
|
||||||
workspace: string;
|
workspace: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -46,7 +39,6 @@ type TSecretImportSecretsV2 = {
|
|||||||
// akhilmhdh: yes i know you can put ?.
|
// akhilmhdh: yes i know you can put ?.
|
||||||
// But for somereason ts consider ? and undefined explicit as different just ts things
|
// But for somereason ts consider ? and undefined explicit as different just ts things
|
||||||
secretValue: string;
|
secretValue: string;
|
||||||
secretValueHidden: boolean;
|
|
||||||
secretComment: string;
|
secretComment: string;
|
||||||
secretMetadata?: ResourceMetadataDTO;
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
})[];
|
})[];
|
||||||
@@ -158,14 +150,12 @@ export const fnSecretsV2FromImports = async ({
|
|||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor,
|
decryptor,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
hasSecretAccess,
|
hasSecretAccess
|
||||||
viewSecretValue
|
|
||||||
}: {
|
}: {
|
||||||
secretImports: (Omit<TSecretImports, "importEnv"> & {
|
secretImports: (Omit<TSecretImports, "importEnv"> & {
|
||||||
importEnv: { id: string; slug: string; name: string };
|
importEnv: { id: string; slug: string; name: string };
|
||||||
})[];
|
})[];
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findByManySecretPath">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findByManySecretPath">;
|
||||||
viewSecretValue: boolean;
|
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "find">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "find">;
|
||||||
secretImportDAL: Pick<TSecretImportDALFactory, "findByFolderIds">;
|
secretImportDAL: Pick<TSecretImportDALFactory, "findByFolderIds">;
|
||||||
decryptor: (value?: Buffer | null) => string;
|
decryptor: (value?: Buffer | null) => string;
|
||||||
@@ -178,14 +168,9 @@ export const fnSecretsV2FromImports = async ({
|
|||||||
hasSecretAccess: (environment: string, secretPath: string, secretName: string, secretTagSlugs: string[]) => boolean;
|
hasSecretAccess: (environment: string, secretPath: string, secretName: string, secretTagSlugs: string[]) => boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const cyclicDetector = new Set();
|
const cyclicDetector = new Set();
|
||||||
const stack: {
|
const stack: { secretImports: typeof rootSecretImports; depth: number; parentImportedSecrets: TSecretsV2[] }[] = [
|
||||||
secretImports: typeof rootSecretImports;
|
{ secretImports: rootSecretImports, depth: 0, parentImportedSecrets: [] }
|
||||||
depth: number;
|
];
|
||||||
parentImportedSecrets: (TSecretsV2 & {
|
|
||||||
secretValueHidden: boolean;
|
|
||||||
secretTags: { slug: string; name: string; id: string; color?: string | null }[];
|
|
||||||
})[];
|
|
||||||
}[] = [{ secretImports: rootSecretImports, depth: 0, parentImportedSecrets: [] }];
|
|
||||||
|
|
||||||
const processedImports: TSecretImportSecretsV2[] = [];
|
const processedImports: TSecretImportSecretsV2[] = [];
|
||||||
|
|
||||||
@@ -244,9 +229,7 @@ export const fnSecretsV2FromImports = async ({
|
|||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
secretKey: item.key,
|
secretKey: item.key,
|
||||||
secretValue: viewSecretValue ? decryptor(item.encryptedValue) : INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
secretValue: decryptor(item.encryptedValue),
|
||||||
secretValueHidden: !viewSecretValue,
|
|
||||||
secretTags: item.tags,
|
|
||||||
secretComment: decryptor(item.encryptedComment),
|
secretComment: decryptor(item.encryptedComment),
|
||||||
environment: importEnv.slug,
|
environment: importEnv.slug,
|
||||||
workspace: "", // This field should not be used, it's only here to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
workspace: "", // This field should not be used, it's only here to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||||
@@ -284,8 +267,6 @@ export const fnSecretsV2FromImports = async ({
|
|||||||
processedImport.secrets = unique(processedImport.secrets, (i) => i.key);
|
processedImport.secrets = unique(processedImport.secrets, (i) => i.key);
|
||||||
return Promise.allSettled(
|
return Promise.allSettled(
|
||||||
processedImport.secrets.map(async (decryptedSecret, index) => {
|
processedImport.secrets.map(async (decryptedSecret, index) => {
|
||||||
if (decryptedSecret.secretValueHidden) return;
|
|
||||||
|
|
||||||
const expandedSecretValue = await expandSecretReferences({
|
const expandedSecretValue = await expandSecretReferences({
|
||||||
value: decryptedSecret.secretValue,
|
value: decryptedSecret.secretValue,
|
||||||
secretPath: processedImport.secretPath,
|
secretPath: processedImport.secretPath,
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { getReplicationFolderName } from "@app/ee/services/secret-replication/secret-replication-service";
|
import { getReplicationFolderName } from "@app/ee/services/secret-replication/secret-replication-service";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -94,7 +90,7 @@ export const secretImportServiceFactory = ({
|
|||||||
|
|
||||||
// check if user has permission to import from target path
|
// check if user has permission to import from target path
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: data.environment,
|
environment: data.environment,
|
||||||
secretPath: data.path
|
secretPath: data.path
|
||||||
@@ -406,7 +402,7 @@ export const secretImportServiceFactory = ({
|
|||||||
|
|
||||||
// check if user has permission to import from target path
|
// check if user has permission to import from target path
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: secretImportDoc.importEnv.slug,
|
environment: secretImportDoc.importEnv.slug,
|
||||||
secretPath: secretImportDoc.importPath
|
secretPath: secretImportDoc.importPath
|
||||||
@@ -600,7 +596,7 @@ export const secretImportServiceFactory = ({
|
|||||||
const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false });
|
const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false });
|
||||||
const allowedImports = secretImports.filter((el) =>
|
const allowedImports = secretImports.filter((el) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: el.importEnv.slug,
|
environment: el.importEnv.slug,
|
||||||
secretPath: el.importPath
|
secretPath: el.importPath
|
||||||
@@ -646,13 +642,12 @@ export const secretImportServiceFactory = ({
|
|||||||
const importedSecrets = await fnSecretsV2FromImports({
|
const importedSecrets = await fnSecretsV2FromImports({
|
||||||
secretImports,
|
secretImports,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
viewSecretValue: true,
|
|
||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: expandEnvironment,
|
environment: expandEnvironment,
|
||||||
secretPath: expandSecretPath,
|
secretPath: expandSecretPath,
|
||||||
@@ -672,7 +667,7 @@ export const secretImportServiceFactory = ({
|
|||||||
|
|
||||||
const allowedImports = secretImports.filter((el) =>
|
const allowedImports = secretImports.filter((el) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: el.importEnv.slug,
|
environment: el.importEnv.slug,
|
||||||
secretPath: el.importPath
|
secretPath: el.importPath
|
||||||
@@ -688,10 +683,7 @@ export const secretImportServiceFactory = ({
|
|||||||
return importedSecrets.map((el) => ({
|
return importedSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
secrets: el.secrets.map((encryptedSecret) =>
|
secrets: el.secrets.map((encryptedSecret) =>
|
||||||
decryptSecretRaw(
|
decryptSecretRaw({ ...encryptedSecret, workspace: projectId, environment, secretPath }, botKey)
|
||||||
{ ...encryptedSecret, workspace: projectId, environment, secretPath, secretValueHidden: false },
|
|
||||||
botKey
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -249,8 +249,7 @@ export const secretSyncQueueFactory = ({
|
|||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
secretImports,
|
secretImports,
|
||||||
hasSecretAccess: () => true,
|
hasSecretAccess: () => true
|
||||||
viewSecretValue: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSecretSyncActions,
|
ProjectPermissionSecretSyncActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
@@ -179,7 +179,7 @@ export const secretSyncServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
ForbiddenError.from(projectPermission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
@@ -270,7 +270,7 @@ export const secretSyncServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Must specify both source environment and secret path" });
|
throw new BadRequestError({ message: "Must specify both source environment and secret path" });
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: updatedEnvironment,
|
environment: updatedEnvironment,
|
||||||
secretPath: updatedSecretPath
|
secretPath: updatedSecretPath
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export const secretTagDALFactory = (db: TDbClient) => {
|
|||||||
throw new DatabaseError({ error, name: "Find all by ids" });
|
throw new DatabaseError({ error, name: "Find all by ids" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...secretTagOrm,
|
...secretTagOrm,
|
||||||
saveTagsToSecret: secretJnTagOrm.insertMany,
|
saveTagsToSecret: secretJnTagOrm.insertMany,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { logger } from "@app/lib/logger";
|
|||||||
|
|
||||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
|
||||||
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
|
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
|
||||||
@@ -103,7 +102,6 @@ export const fnSecretBulkInsert = async ({
|
|||||||
[`${TableName.SecretV2}Id` as const]: newSecretGroupedByKeyName[key][0].id
|
[`${TableName.SecretV2}Id` as const]: newSecretGroupedByKeyName[key][0].id
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
const secretVersions = await secretVersionDAL.insertMany(
|
const secretVersions = await secretVersionDAL.insertMany(
|
||||||
sanitizedInputSecrets.map((el) => ({
|
sanitizedInputSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
@@ -139,7 +137,6 @@ export const fnSecretBulkInsert = async ({
|
|||||||
if (newSecretTags.length) {
|
if (newSecretTags.length) {
|
||||||
const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx);
|
const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx);
|
||||||
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
|
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
|
||||||
|
|
||||||
const newSecretVersionTags = secTags.flatMap(({ secrets_v2Id, secret_tagsId }) => ({
|
const newSecretVersionTags = secTags.flatMap(({ secrets_v2Id, secret_tagsId }) => ({
|
||||||
[`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id,
|
[`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id,
|
||||||
[`${TableName.SecretTag}Id` as const]: secret_tagsId
|
[`${TableName.SecretTag}Id` as const]: secret_tagsId
|
||||||
@@ -148,16 +145,7 @@ export const fnSecretBulkInsert = async ({
|
|||||||
await secretVersionTagDAL.insertMany(newSecretVersionTags, tx);
|
await secretVersionTagDAL.insertMany(newSecretVersionTags, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretsWithTags = await secretDAL.find(
|
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
||||||
{
|
|
||||||
$in: {
|
|
||||||
[`${TableName.SecretV2}.id` as "id"]: newSecrets.map((s) => s.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ tx }
|
|
||||||
);
|
|
||||||
|
|
||||||
return secretsWithTags.map((secret) => ({ ...secret, _id: secret.id }));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fnSecretBulkUpdate = async ({
|
export const fnSecretBulkUpdate = async ({
|
||||||
@@ -295,15 +283,7 @@ export const fnSecretBulkUpdate = async ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
const secretsWithTags = await secretDAL.find(
|
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
||||||
{
|
|
||||||
$in: {
|
|
||||||
[`${TableName.SecretV2}.id` as "id"]: newSecrets.map((s) => s.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ tx }
|
|
||||||
);
|
|
||||||
return secretsWithTags.map((secret) => ({ ...secret, _id: secret.id }));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fnSecretBulkDelete = async ({
|
export const fnSecretBulkDelete = async ({
|
||||||
@@ -536,7 +516,7 @@ export const expandSecretReferencesFactory = ({
|
|||||||
const referredValue = await fetchSecret(environment, secretPath, secretKey);
|
const referredValue = await fetchSecret(environment, secretPath, secretKey);
|
||||||
if (!canExpandValue(environment, secretPath, secretKey, referredValue.tags))
|
if (!canExpandValue(environment, secretPath, secretKey, referredValue.tags))
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: `You are attempting to reference secret named ${secretKey} from environment ${environment} in path ${secretPath} which you do not have access to read value on.`
|
message: `You are attempting to reference secret named ${secretKey} from environment ${environment} in path ${secretPath} which you do not have access to.`
|
||||||
});
|
});
|
||||||
|
|
||||||
const cacheKey = getCacheUniqueKey(environment, secretPath);
|
const cacheKey = getCacheUniqueKey(environment, secretPath);
|
||||||
@@ -555,7 +535,7 @@ export const expandSecretReferencesFactory = ({
|
|||||||
const referedValue = await fetchSecret(secretReferenceEnvironment, secretReferencePath, secretReferenceKey);
|
const referedValue = await fetchSecret(secretReferenceEnvironment, secretReferencePath, secretReferenceKey);
|
||||||
if (!canExpandValue(secretReferenceEnvironment, secretReferencePath, secretReferenceKey, referedValue.tags))
|
if (!canExpandValue(secretReferenceEnvironment, secretReferencePath, secretReferenceKey, referedValue.tags))
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: `You are attempting to reference secret named ${secretReferenceKey} from environment ${secretReferenceEnvironment} in path ${secretReferencePath} which you do not have access to read value on.`
|
message: `You are attempting to reference secret named ${secretReferenceKey} from environment ${secretReferenceEnvironment} in path ${secretReferencePath} which you do not have access to.`
|
||||||
});
|
});
|
||||||
|
|
||||||
const cacheKey = getCacheUniqueKey(secretReferenceEnvironment, secretReferencePath);
|
const cacheKey = getCacheUniqueKey(secretReferenceEnvironment, secretReferencePath);
|
||||||
@@ -643,13 +623,13 @@ export const reshapeBridgeSecret = (
|
|||||||
name: string;
|
name: string;
|
||||||
}[];
|
}[];
|
||||||
secretMetadata?: ResourceMetadataDTO;
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
},
|
}
|
||||||
secretValueHidden: boolean
|
|
||||||
) => ({
|
) => ({
|
||||||
secretKey: secret.key,
|
secretKey: secret.key,
|
||||||
secretPath,
|
secretPath,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
|
secretValue: secret.value || "",
|
||||||
secretComment: secret.comment || "",
|
secretComment: secret.comment || "",
|
||||||
version: secret.version,
|
version: secret.version,
|
||||||
type: secret.type,
|
type: secret.type,
|
||||||
@@ -663,15 +643,5 @@ export const reshapeBridgeSecret = (
|
|||||||
metadata: secret.metadata,
|
metadata: secret.metadata,
|
||||||
secretMetadata: secret.secretMetadata,
|
secretMetadata: secret.secretMetadata,
|
||||||
createdAt: secret.createdAt,
|
createdAt: secret.createdAt,
|
||||||
updatedAt: secret.updatedAt,
|
updatedAt: secret.updatedAt
|
||||||
|
|
||||||
...(secretValueHidden
|
|
||||||
? {
|
|
||||||
secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
|
||||||
secretValueHidden: true
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
secretValue: secret.value || "",
|
|
||||||
secretValueHidden: false
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { SecretType, TSecretsV2, TSecretsV2Insert, TSecretsV2Update } from "@app/db/schemas";
|
import { SecretType, TSecretsV2, TSecretsV2Insert, TSecretsV2Update } from "@app/db/schemas";
|
||||||
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
|
||||||
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
|
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
@@ -37,8 +36,6 @@ export type TGetSecretsDTO = {
|
|||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
tagSlugs?: string[];
|
tagSlugs?: string[];
|
||||||
viewSecretValue: boolean;
|
|
||||||
throwOnMissingReadValuePermission?: boolean;
|
|
||||||
metadataFilter?: {
|
metadataFilter?: {
|
||||||
key?: string;
|
key?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
@@ -51,11 +48,6 @@ export type TGetSecretsDTO = {
|
|||||||
keys?: string[];
|
keys?: string[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetSecretsMissingReadValuePermissionDTO = Omit<
|
|
||||||
TGetSecretsDTO,
|
|
||||||
"viewSecretValue" | "recursive" | "expandSecretReferences"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type TGetASecretDTO = {
|
export type TGetASecretDTO = {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
path: string;
|
path: string;
|
||||||
@@ -65,7 +57,6 @@ export type TGetASecretDTO = {
|
|||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
version?: number;
|
version?: number;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
viewSecretValue: boolean;
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateSecretDTO = TProjectPermission & {
|
export type TCreateSecretDTO = TProjectPermission & {
|
||||||
@@ -173,9 +164,9 @@ export type TFnSecretBulkInsert = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "find">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "find">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,9 +188,9 @@ export type TFnSecretBulkUpdate = {
|
|||||||
data: TRequireReferenceIfValue & { tags?: string[]; secretMetadata?: ResourceMetadataDTO };
|
data: TRequireReferenceIfValue & { tags?: string[]; secretMetadata?: ResourceMetadataDTO };
|
||||||
}[];
|
}[];
|
||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences" | "find">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2" | "find">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
};
|
};
|
||||||
@@ -341,5 +332,4 @@ export type TGetSecretsRawByFolderMappingsDTO = {
|
|||||||
folderMappings: { folderId: string; path: string; environment: string }[];
|
folderMappings: { folderId: string; path: string; environment: string }[];
|
||||||
userId: string;
|
userId: string;
|
||||||
filters: TFindSecretsByFolderIdsFilter;
|
filters: TFindSecretsByFolderIdsFilter;
|
||||||
filterByAction?: ProjectPermissionSecretActions;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { SecretVersionsV2Schema, TableName, TSecretVersionsV2, TSecretVersionsV2Update } from "@app/db/schemas";
|
import { TableName, TSecretVersionsV2, TSecretVersionsV2Update } from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
|
|
||||||
@@ -12,58 +12,6 @@ export type TSecretVersionV2DALFactory = ReturnType<typeof secretVersionV2Bridge
|
|||||||
export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||||
const secretVersionV2Orm = ormify(db, TableName.SecretVersionV2);
|
const secretVersionV2Orm = ormify(db, TableName.SecretVersionV2);
|
||||||
|
|
||||||
const findBySecretId = async (secretId: string, { offset, limit, sort, tx }: TFindOpt<TSecretVersionsV2> = {}) => {
|
|
||||||
try {
|
|
||||||
const query = (tx || db.replicaNode())(TableName.SecretVersionV2)
|
|
||||||
.where(`${TableName.SecretVersionV2}.secretId`, secretId)
|
|
||||||
.leftJoin(TableName.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`)
|
|
||||||
.leftJoin(
|
|
||||||
TableName.SecretV2JnTag,
|
|
||||||
`${TableName.SecretV2}.id`,
|
|
||||||
`${TableName.SecretV2JnTag}.${TableName.SecretV2}Id`
|
|
||||||
)
|
|
||||||
.leftJoin(
|
|
||||||
TableName.SecretTag,
|
|
||||||
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
|
||||||
`${TableName.SecretTag}.id`
|
|
||||||
)
|
|
||||||
.select(selectAllTableCols(TableName.SecretVersionV2))
|
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
|
||||||
|
|
||||||
if (limit) void query.limit(limit);
|
|
||||||
if (offset) void query.offset(offset);
|
|
||||||
if (sort) {
|
|
||||||
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
|
||||||
}
|
|
||||||
|
|
||||||
const docs = await query;
|
|
||||||
|
|
||||||
const data = sqlNestRelationships({
|
|
||||||
data: docs,
|
|
||||||
key: "id",
|
|
||||||
parentMapper: (el) => ({ _id: el.id, ...SecretVersionsV2Schema.parse(el) }),
|
|
||||||
childrenMapper: [
|
|
||||||
{
|
|
||||||
key: "tagId",
|
|
||||||
label: "tags" as const,
|
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
|
||||||
id,
|
|
||||||
color,
|
|
||||||
slug,
|
|
||||||
name: slug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: `${TableName.SecretVersionV2}: FindBySecretId` });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This will fetch all latest secret versions from a folder
|
// This will fetch all latest secret versions from a folder
|
||||||
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
@@ -176,7 +124,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
pruneExcessVersions,
|
pruneExcessVersions,
|
||||||
findLatestVersionMany,
|
findLatestVersionMany,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
findLatestVersionByFolderId,
|
findLatestVersionByFolderId
|
||||||
findBySecretId
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -169,48 +169,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findManySecretsWithTags = async (
|
|
||||||
filter: {
|
|
||||||
secretIds: string[];
|
|
||||||
type: SecretType;
|
|
||||||
},
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const secrets = await (tx || db.replicaNode())(TableName.Secret)
|
|
||||||
.whereIn(`${TableName.Secret}.id` as "id", filter.secretIds)
|
|
||||||
.where("type", filter.type)
|
|
||||||
.leftJoin(TableName.JnSecretTag, `${TableName.Secret}.id`, `${TableName.JnSecretTag}.${TableName.Secret}Id`)
|
|
||||||
.leftJoin(TableName.SecretTag, `${TableName.JnSecretTag}.${TableName.SecretTag}Id`, `${TableName.SecretTag}.id`)
|
|
||||||
.select(selectAllTableCols(TableName.Secret))
|
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
|
||||||
|
|
||||||
const data = sqlNestRelationships({
|
|
||||||
data: secrets,
|
|
||||||
key: "id",
|
|
||||||
parentMapper: (el) => ({ _id: el.id, ...SecretsSchema.parse(el) }),
|
|
||||||
childrenMapper: [
|
|
||||||
{
|
|
||||||
key: "tagId",
|
|
||||||
label: "tags" as const,
|
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
|
||||||
id,
|
|
||||||
color,
|
|
||||||
slug,
|
|
||||||
name: slug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "get many secrets with tags" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const findByFolderIds = async (folderIds: string[], userId?: string, tx?: Knex) => {
|
const findByFolderIds = async (folderIds: string[], userId?: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
// check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo)
|
// check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo)
|
||||||
@@ -485,7 +443,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
upsertSecretReferences,
|
upsertSecretReferences,
|
||||||
findReferencedSecretReferences,
|
findReferencedSecretReferences,
|
||||||
findAllProjectSecretValues,
|
findAllProjectSecretValues,
|
||||||
pruneSecretReminders,
|
pruneSecretReminders
|
||||||
findManySecretsWithTags
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
TSecrets
|
TSecrets
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import {
|
import {
|
||||||
buildSecretBlindIndexFromName,
|
buildSecretBlindIndexFromName,
|
||||||
@@ -51,8 +51,6 @@ import {
|
|||||||
TUpdateManySecretsRawFnFactory
|
TUpdateManySecretsRawFnFactory
|
||||||
} from "./secret-types";
|
} from "./secret-types";
|
||||||
|
|
||||||
export const INFISICAL_SECRET_VALUE_HIDDEN_MASK = "<hidden-by-infisical>";
|
|
||||||
|
|
||||||
export const generateSecretBlindIndexBySalt = async (secretName: string, secretBlindIndexDoc: TSecretBlindIndexes) => {
|
export const generateSecretBlindIndexBySalt = async (secretName: string, secretBlindIndexDoc: TSecretBlindIndexes) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const secretBlindIndex = await buildSecretBlindIndexFromName({
|
const secretBlindIndex = await buildSecretBlindIndexFromName({
|
||||||
@@ -192,7 +190,7 @@ export const recursivelyGetSecretPaths = ({
|
|||||||
const allowedPaths = paths.filter(
|
const allowedPaths = paths.filter(
|
||||||
(folder) =>
|
(folder) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath: folder.path
|
secretPath: folder.path
|
||||||
@@ -346,7 +344,6 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
|
|||||||
|
|
||||||
export const decryptSecretRaw = (
|
export const decryptSecretRaw = (
|
||||||
secret: TSecrets & {
|
secret: TSecrets & {
|
||||||
secretValueHidden: boolean;
|
|
||||||
workspace: string;
|
workspace: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
@@ -365,14 +362,12 @@ export const decryptSecretRaw = (
|
|||||||
key
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValue = !secret.secretValueHidden
|
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
? decryptSymmetric128BitHexKeyUTF8({
|
ciphertext: secret.secretValueCiphertext,
|
||||||
ciphertext: secret.secretValueCiphertext,
|
iv: secret.secretValueIV,
|
||||||
iv: secret.secretValueIV,
|
tag: secret.secretValueTag,
|
||||||
tag: secret.secretValueTag,
|
key
|
||||||
key
|
});
|
||||||
})
|
|
||||||
: INFISICAL_SECRET_VALUE_HIDDEN_MASK;
|
|
||||||
|
|
||||||
let secretComment = "";
|
let secretComment = "";
|
||||||
|
|
||||||
@@ -390,7 +385,6 @@ export const decryptSecretRaw = (
|
|||||||
secretPath: secret.secretPath,
|
secretPath: secret.secretPath,
|
||||||
workspace: secret.workspace,
|
workspace: secret.workspace,
|
||||||
environment: secret.environment,
|
environment: secret.environment,
|
||||||
secretValueHidden: secret.secretValueHidden,
|
|
||||||
secretValue,
|
secretValue,
|
||||||
secretComment,
|
secretComment,
|
||||||
version: secret.version,
|
version: secret.version,
|
||||||
@@ -1203,23 +1197,3 @@ export const fnDeleteProjectSecretReminders = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const conditionallyHideSecretValue = (
|
|
||||||
shouldHideValue: boolean,
|
|
||||||
{
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag
|
|
||||||
}: {
|
|
||||||
secretValueCiphertext: string;
|
|
||||||
secretValueIV: string;
|
|
||||||
secretValueTag: string;
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
secretValueCiphertext: shouldHideValue ? INFISICAL_SECRET_VALUE_HIDDEN_MASK : secretValueCiphertext,
|
|
||||||
secretValueIV: shouldHideValue ? INFISICAL_SECRET_VALUE_HIDDEN_MASK : secretValueIV,
|
|
||||||
secretValueTag: shouldHideValue ? INFISICAL_SECRET_VALUE_HIDDEN_MASK : secretValueTag,
|
|
||||||
secretValueHidden: shouldHideValue
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -402,8 +402,7 @@ export const secretQueueFactory = ({
|
|||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
secretImports,
|
secretImports,
|
||||||
hasSecretAccess: () => true,
|
hasSecretAccess: () => true
|
||||||
viewSecretValue: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
ActionProjectType,
|
ActionProjectType,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUpgradeStatus,
|
ProjectUpgradeStatus,
|
||||||
ProjectVersion,
|
|
||||||
SecretEncryptionAlgo,
|
SecretEncryptionAlgo,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
SecretsSchema,
|
SecretsSchema,
|
||||||
@@ -14,11 +13,7 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
||||||
@@ -53,7 +48,6 @@ import { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bri
|
|||||||
import { TGetSecretReferencesTreeDTO } from "../secret-v2-bridge/secret-v2-bridge-types";
|
import { TGetSecretReferencesTreeDTO } from "../secret-v2-bridge/secret-v2-bridge-types";
|
||||||
import { TSecretDALFactory } from "./secret-dal";
|
import { TSecretDALFactory } from "./secret-dal";
|
||||||
import {
|
import {
|
||||||
conditionallyHideSecretValue,
|
|
||||||
decryptSecretRaw,
|
decryptSecretRaw,
|
||||||
fnSecretBlindIndexCheck,
|
fnSecretBlindIndexCheck,
|
||||||
fnSecretBulkDelete,
|
fnSecretBulkDelete,
|
||||||
@@ -101,7 +95,7 @@ type TSecretServiceFactoryDep = {
|
|||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
folderDAL: Pick<
|
folderDAL: Pick<
|
||||||
TSecretFolderDALFactory,
|
TSecretFolderDALFactory,
|
||||||
"findBySecretPath" | "updateById" | "findById" | "findByManySecretPath" | "find" | "findSecretPathByFolderIds"
|
"findBySecretPath" | "updateById" | "findById" | "findByManySecretPath" | "find"
|
||||||
>;
|
>;
|
||||||
secretV2BridgeService: TSecretV2BridgeServiceFactory;
|
secretV2BridgeService: TSecretV2BridgeServiceFactory;
|
||||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||||
@@ -210,7 +204,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -328,7 +322,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -450,22 +444,7 @@ export const secretServiceFactory = ({
|
|||||||
environmentSlug: folder.environment.slug
|
environmentSlug: folder.environment.slug
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return { ...updatedSecret[0], workspace: projectId, environment, secretPath: path };
|
||||||
const secretValueHidden = !permission.can(
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment,
|
|
||||||
secretPath: path
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...updatedSecret[0],
|
|
||||||
...conditionallyHideSecretValue(secretValueHidden, updatedSecret[0]),
|
|
||||||
workspace: projectId,
|
|
||||||
environment,
|
|
||||||
secretPath: path
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteSecret = async ({
|
const deleteSecret = async ({
|
||||||
@@ -488,7 +467,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -561,19 +540,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretValueHidden = !permission.can(
|
return { ...deletedSecret[0], _id: deletedSecret[0].id, workspace: projectId, environment, secretPath: path };
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...deletedSecret[0],
|
|
||||||
...conditionallyHideSecretValue(secretValueHidden, deletedSecret[0]),
|
|
||||||
_id: deletedSecret[0].id,
|
|
||||||
workspace: projectId,
|
|
||||||
environment,
|
|
||||||
secretPath: path
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecrets = async ({
|
const getSecrets = async ({
|
||||||
@@ -622,7 +589,7 @@ export const secretServiceFactory = ({
|
|||||||
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
||||||
} else {
|
} else {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -647,7 +614,7 @@ export const secretServiceFactory = ({
|
|||||||
actor === ActorType.SERVICE
|
actor === ActorType.SERVICE
|
||||||
? true
|
? true
|
||||||
: permission.can(
|
: permission.can(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: importEnv.slug,
|
environment: importEnv.slug,
|
||||||
secretPath: importPath
|
secretPath: importPath
|
||||||
@@ -704,7 +671,7 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
@@ -754,7 +721,7 @@ export const secretServiceFactory = ({
|
|||||||
actor === ActorType.SERVICE
|
actor === ActorType.SERVICE
|
||||||
? true
|
? true
|
||||||
: permission.can(
|
: permission.can(
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: importEnv.slug,
|
environment: importEnv.slug,
|
||||||
secretPath: importPath
|
secretPath: importPath
|
||||||
@@ -772,7 +739,6 @@ export const secretServiceFactory = ({
|
|||||||
if (secretBlindIndex === importedSecrets[i].secrets[j].secretBlindIndex) {
|
if (secretBlindIndex === importedSecrets[i].secrets[j].secretBlindIndex) {
|
||||||
return {
|
return {
|
||||||
...importedSecrets[i].secrets[j],
|
...importedSecrets[i].secrets[j],
|
||||||
secretValueHidden: false,
|
|
||||||
workspace: projectId,
|
workspace: projectId,
|
||||||
environment: importedSecrets[i].environment,
|
environment: importedSecrets[i].environment,
|
||||||
secretPath: importedSecrets[i].secretPath
|
secretPath: importedSecrets[i].secretPath
|
||||||
@@ -783,13 +749,7 @@ export const secretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
if (!secret) throw new NotFoundError({ message: `Secret with name '${secretName}' not found` });
|
if (!secret) throw new NotFoundError({ message: `Secret with name '${secretName}' not found` });
|
||||||
|
|
||||||
return {
|
return { ...secret, workspace: projectId, environment, secretPath: path };
|
||||||
...secret,
|
|
||||||
secretValueHidden: false, // Always false because we check permission at the beginning of the function
|
|
||||||
workspace: projectId,
|
|
||||||
environment,
|
|
||||||
secretPath: path
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createManySecret = async ({
|
const createManySecret = async ({
|
||||||
@@ -811,7 +771,7 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -899,7 +859,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -941,8 +901,8 @@ export const secretServiceFactory = ({
|
|||||||
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "One or more tags not found" });
|
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "One or more tags not found" });
|
||||||
|
|
||||||
const references = await getSecretReference(projectId);
|
const references = await getSecretReference(projectId);
|
||||||
const secrets = await secretDAL.transaction(async (tx) => {
|
const secrets = await secretDAL.transaction(async (tx) =>
|
||||||
const updatedSecrets = await fnSecretBulkUpdate({
|
fnSecretBulkUpdate({
|
||||||
folderId,
|
folderId,
|
||||||
projectId,
|
projectId,
|
||||||
tx,
|
tx,
|
||||||
@@ -972,18 +932,8 @@ export const secretServiceFactory = ({
|
|||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL
|
secretVersionTagDAL
|
||||||
});
|
})
|
||||||
|
);
|
||||||
const secretValueHidden = !permission.can(
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
return updatedSecrets.map((secret) => ({
|
|
||||||
...secret,
|
|
||||||
...conditionallyHideSecretValue(secretValueHidden, secret)
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
@@ -1017,7 +967,7 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1069,15 +1019,7 @@ export const secretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretValueHidden = !permission.can(
|
return secrets;
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
return secrets.map((secret) => ({
|
|
||||||
...secret,
|
|
||||||
...conditionallyHideSecretValue(secretValueHidden, secret)
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
@@ -1238,7 +1180,6 @@ export const secretServiceFactory = ({
|
|||||||
secretName,
|
secretName,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
environment,
|
environment,
|
||||||
viewSecretValue: false,
|
|
||||||
type: "shared"
|
type: "shared"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1253,11 +1194,10 @@ export const secretServiceFactory = ({
|
|||||||
| (typeof groupPermissions)[number]
|
| (typeof groupPermissions)[number]
|
||||||
) => {
|
) => {
|
||||||
const allowedActions = [
|
const allowedActions = [
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionActions.Edit
|
||||||
ProjectPermissionSecretActions.Edit
|
|
||||||
].filter((action) =>
|
].filter((action) =>
|
||||||
entityPermission.permission.can(
|
entityPermission.permission.can(
|
||||||
action,
|
action,
|
||||||
@@ -1294,13 +1234,11 @@ export const secretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
viewSecretValue,
|
|
||||||
environment,
|
environment,
|
||||||
includeImports,
|
includeImports,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
recursive,
|
recursive,
|
||||||
tagSlugs = [],
|
tagSlugs = [],
|
||||||
throwOnMissingReadValuePermission = true,
|
|
||||||
...paramsV2
|
...paramsV2
|
||||||
}: TGetSecretsRawDTO) => {
|
}: TGetSecretsRawDTO) => {
|
||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
@@ -1311,8 +1249,6 @@ export const secretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
viewSecretValue,
|
|
||||||
throwOnMissingReadValuePermission,
|
|
||||||
environment,
|
environment,
|
||||||
path,
|
path,
|
||||||
recursive,
|
recursive,
|
||||||
@@ -1321,7 +1257,6 @@ export const secretServiceFactory = ({
|
|||||||
tagSlugs,
|
tagSlugs,
|
||||||
...paramsV2
|
...paramsV2
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secrets, imports };
|
return { secrets, imports };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1350,20 +1285,14 @@ export const secretServiceFactory = ({
|
|||||||
recursive
|
recursive
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecrets = secrets.map((el) => decryptSecretRaw({ ...el, secretValueHidden: false }, botKey));
|
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
|
||||||
const filteredSecrets = tagSlugs.length
|
const filteredSecrets = tagSlugs.length
|
||||||
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
||||||
: decryptedSecrets;
|
: decryptedSecrets;
|
||||||
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
||||||
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
||||||
decryptSecretRaw(
|
decryptSecretRaw(
|
||||||
{
|
{ ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath },
|
||||||
...sec,
|
|
||||||
environment: el.environment,
|
|
||||||
workspace: projectId,
|
|
||||||
secretPath: el.secretPath,
|
|
||||||
secretValueHidden: false
|
|
||||||
},
|
|
||||||
botKey
|
botKey
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -1374,7 +1303,6 @@ export const secretServiceFactory = ({
|
|||||||
const importedEntries = decryptedImportSecrets.reduce(
|
const importedEntries = decryptedImportSecrets.reduce(
|
||||||
(
|
(
|
||||||
accum: {
|
accum: {
|
||||||
secretValueHidden: boolean;
|
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
@@ -1418,7 +1346,6 @@ export const secretServiceFactory = ({
|
|||||||
Object.keys(secretsGroupByPath).map((groupedPath) =>
|
Object.keys(secretsGroupByPath).map((groupedPath) =>
|
||||||
Promise.allSettled(
|
Promise.allSettled(
|
||||||
secretsGroupByPath[groupedPath].map(async (decryptedSecret, index) => {
|
secretsGroupByPath[groupedPath].map(async (decryptedSecret, index) => {
|
||||||
if (decryptedSecret.secretValueHidden) return;
|
|
||||||
const expandedSecretValue = await expandSecret({
|
const expandedSecretValue = await expandSecret({
|
||||||
value: decryptedSecret.secretValue,
|
value: decryptedSecret.secretValue,
|
||||||
secretPath: groupedPath,
|
secretPath: groupedPath,
|
||||||
@@ -1435,7 +1362,6 @@ export const secretServiceFactory = ({
|
|||||||
processedImports.map((processedImport) =>
|
processedImports.map((processedImport) =>
|
||||||
Promise.allSettled(
|
Promise.allSettled(
|
||||||
processedImport.secrets.map(async (decryptedSecret, index) => {
|
processedImport.secrets.map(async (decryptedSecret, index) => {
|
||||||
if (decryptedSecret.secretValueHidden) return;
|
|
||||||
const expandedSecretValue = await expandSecret({
|
const expandedSecretValue = await expandSecret({
|
||||||
value: decryptedSecret.secretValue,
|
value: decryptedSecret.secretValue,
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
@@ -1461,7 +1387,6 @@ export const secretServiceFactory = ({
|
|||||||
path,
|
path,
|
||||||
actor,
|
actor,
|
||||||
environment,
|
environment,
|
||||||
viewSecretValue,
|
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
@@ -1481,7 +1406,6 @@ export const secretServiceFactory = ({
|
|||||||
includeImports,
|
includeImports,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
path,
|
path,
|
||||||
viewSecretValue,
|
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -1512,7 +1436,6 @@ export const secretServiceFactory = ({
|
|||||||
message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`,
|
message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`,
|
||||||
name: "bot_not_found_error"
|
name: "bot_not_found_error"
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecret = decryptSecretRaw(encryptedSecret, botKey);
|
const decryptedSecret = decryptSecretRaw(encryptedSecret, botKey);
|
||||||
|
|
||||||
if (expandSecretReferences) {
|
if (expandSecretReferences) {
|
||||||
@@ -1531,10 +1454,7 @@ export const secretServiceFactory = ({
|
|||||||
decryptedSecret.secretValue = expandedSecretValue || "";
|
decryptedSecret.secretValue = expandedSecretValue || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { secretMetadata: undefined, ...decryptedSecret };
|
||||||
secretMetadata: undefined,
|
|
||||||
...decryptedSecret
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createSecretRaw = async ({
|
const createSecretRaw = async ({
|
||||||
@@ -1685,16 +1605,7 @@ export const secretServiceFactory = ({
|
|||||||
tags: tagIds
|
tags: tagIds
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return { type: SecretProtectionType.Direct as const, secret: decryptSecretRaw(secret, botKey) };
|
||||||
type: SecretProtectionType.Direct as const,
|
|
||||||
secret: decryptSecretRaw(
|
|
||||||
{
|
|
||||||
...secret,
|
|
||||||
secretValueHidden: false
|
|
||||||
},
|
|
||||||
botKey
|
|
||||||
)
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSecretRaw = async ({
|
const updateSecretRaw = async ({
|
||||||
@@ -2090,7 +2001,7 @@ export const secretServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
type: SecretProtectionType.Direct as const,
|
type: SecretProtectionType.Direct as const,
|
||||||
secrets: secrets.map((secret) =>
|
secrets: secrets.map((secret) =>
|
||||||
decryptSecretRaw({ ...secret, workspace: projectId, environment, secretPath, secretValueHidden: false }, botKey)
|
decryptSecretRaw({ ...secret, workspace: projectId, environment, secretPath }, botKey)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -2379,12 +2290,6 @@ export const secretServiceFactory = ({
|
|||||||
const folder = await folderDAL.findById(secret.folderId);
|
const folder = await folderDAL.findById(secret.folderId);
|
||||||
if (!folder) throw new NotFoundError({ message: `Folder with ID '${secret.folderId}' not found` });
|
if (!folder) throw new NotFoundError({ message: `Folder with ID '${secret.folderId}' not found` });
|
||||||
|
|
||||||
const [folderWithPath] = await folderDAL.findSecretPathByFolderIds(folder.projectId, [folder.id]);
|
|
||||||
|
|
||||||
if (!folderWithPath) {
|
|
||||||
throw new NotFoundError({ message: `Folder with ID '${folder.id}' not found` });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { botKey } = await projectBotService.getBotKey(folder.projectId);
|
const { botKey } = await projectBotService.getBotKey(folder.projectId);
|
||||||
if (!botKey)
|
if (!botKey)
|
||||||
throw new NotFoundError({ message: `Project bot for project with ID '${folder.projectId}' not found` });
|
throw new NotFoundError({ message: `Project bot for project with ID '${folder.projectId}' not found` });
|
||||||
@@ -2398,42 +2303,18 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
const secretVersions = await secretVersionDAL.findBySecretId(secretId, {
|
const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] });
|
||||||
offset,
|
return secretVersions.map((el) =>
|
||||||
limit,
|
decryptSecretRaw(
|
||||||
sort: [["createdAt", "desc"]]
|
|
||||||
});
|
|
||||||
return secretVersions.map((el) => {
|
|
||||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
|
||||||
ciphertext: secret.secretKeyCiphertext,
|
|
||||||
iv: secret.secretKeyIV,
|
|
||||||
tag: secret.secretKeyTag,
|
|
||||||
key: botKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretValueHidden = permission.cannot(
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: folder.environment.envSlug,
|
|
||||||
secretPath: folderWithPath.path,
|
|
||||||
secretName: secretKey,
|
|
||||||
...(el.tags?.length && {
|
|
||||||
secretTags: el.tags.map((tag) => tag.slug)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return decryptSecretRaw(
|
|
||||||
{
|
{
|
||||||
secretValueHidden,
|
|
||||||
...el,
|
...el,
|
||||||
workspace: folder.projectId,
|
workspace: folder.projectId,
|
||||||
environment: folder.environment.envSlug,
|
environment: folder.environment.envSlug,
|
||||||
secretPath: folderWithPath.path
|
secretPath: "/"
|
||||||
},
|
},
|
||||||
botKey
|
botKey
|
||||||
);
|
)
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachTags = async ({
|
const attachTags = async ({
|
||||||
@@ -2459,7 +2340,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2565,7 +2446,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2731,7 +2612,7 @@ export const secretServiceFactory = ({
|
|||||||
message: `Project with slug '${projectSlug}' not found`
|
message: `Project with slug '${projectSlug}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (project.version === ProjectVersion.V3) {
|
if (project.version === 3) {
|
||||||
return secretV2BridgeService.moveSecrets({
|
return secretV2BridgeService.moveSecrets({
|
||||||
sourceEnvironment,
|
sourceEnvironment,
|
||||||
sourceSecretPath,
|
sourceSecretPath,
|
||||||
@@ -2756,6 +2637,30 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: sourceEnvironment,
|
||||||
|
secretPath: sourceSecretPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: destinationEnvironment,
|
||||||
|
secretPath: destinationSecretPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: destinationEnvironment,
|
||||||
|
secretPath: destinationSecretPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const { botKey } = await projectBotService.getBotKey(project.id);
|
const { botKey } = await projectBotService.getBotKey(project.id);
|
||||||
if (!botKey) {
|
if (!botKey) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -2783,9 +2688,11 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceSecrets = await secretDAL.findManySecretsWithTags({
|
const sourceSecrets = await secretDAL.find({
|
||||||
type: SecretType.Shared,
|
type: SecretType.Shared,
|
||||||
secretIds
|
$in: {
|
||||||
|
id: secretIds
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sourceSecrets.length !== secretIds.length) {
|
if (sourceSecrets.length !== secretIds.length) {
|
||||||
@@ -2794,52 +2701,21 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceActions = [
|
const decryptedSourceSecrets = sourceSecrets.map((secret) => ({
|
||||||
ProjectPermissionSecretActions.Delete,
|
...secret,
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
secretKey: decryptSymmetric128BitHexKeyUTF8({
|
||||||
ProjectPermissionSecretActions.ReadValue
|
|
||||||
] as const;
|
|
||||||
const destinationActions = [ProjectPermissionSecretActions.Create, ProjectPermissionSecretActions.Edit] as const;
|
|
||||||
|
|
||||||
const decryptedSourceSecrets = sourceSecrets.map((secret) => {
|
|
||||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
|
||||||
ciphertext: secret.secretKeyCiphertext,
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
iv: secret.secretKeyIV,
|
iv: secret.secretKeyIV,
|
||||||
tag: secret.secretKeyTag,
|
tag: secret.secretKeyTag,
|
||||||
key: botKey
|
key: botKey
|
||||||
});
|
}),
|
||||||
|
secretValue: decryptSymmetric128BitHexKeyUTF8({
|
||||||
for (const destinationAction of destinationActions) {
|
ciphertext: secret.secretValueCiphertext,
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
iv: secret.secretValueIV,
|
||||||
destinationAction,
|
tag: secret.secretValueTag,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
key: botKey
|
||||||
environment: destinationEnvironment,
|
})
|
||||||
secretPath: destinationSecretPath
|
}));
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const sourceAction of sourceActions) {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
sourceAction,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: sourceEnvironment,
|
|
||||||
secretPath: sourceSecretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...secret,
|
|
||||||
secretKey,
|
|
||||||
secretValue: decryptSymmetric128BitHexKeyUTF8({
|
|
||||||
ciphertext: secret.secretValueCiphertext,
|
|
||||||
iv: secret.secretValueIV,
|
|
||||||
tag: secret.secretValueTag,
|
|
||||||
key: botKey
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let isSourceUpdated = false;
|
let isSourceUpdated = false;
|
||||||
let isDestinationUpdated = false;
|
let isDestinationUpdated = false;
|
||||||
|
|||||||
@@ -180,8 +180,6 @@ export type TGetSecretsRawDTO = {
|
|||||||
expandSecretReferences?: boolean;
|
expandSecretReferences?: boolean;
|
||||||
path: string;
|
path: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
viewSecretValue: boolean;
|
|
||||||
throwOnMissingReadValuePermission?: boolean;
|
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
tagSlugs?: string[];
|
tagSlugs?: string[];
|
||||||
@@ -207,7 +205,6 @@ export type TGetASecretRawDTO = {
|
|||||||
secretName: string;
|
secretName: string;
|
||||||
path: string;
|
path: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
viewSecretValue: boolean;
|
|
||||||
expandSecretReferences?: boolean;
|
expandSecretReferences?: boolean;
|
||||||
type: "shared" | "personal";
|
type: "shared" | "personal";
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
@@ -412,7 +409,7 @@ export type TCreateManySecretsRawFnFactory = {
|
|||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
secretV2BridgeDAL: Pick<
|
secretV2BridgeDAL: Pick<
|
||||||
TSecretV2BridgeDALFactory,
|
TSecretV2BridgeDALFactory,
|
||||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany" | "find"
|
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany"
|
||||||
>;
|
>;
|
||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
@@ -449,7 +446,7 @@ export type TUpdateManySecretsRawFnFactory = {
|
|||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
secretV2BridgeDAL: Pick<
|
secretV2BridgeDAL: Pick<
|
||||||
TSecretV2BridgeDALFactory,
|
TSecretV2BridgeDALFactory,
|
||||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany" | "find"
|
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany"
|
||||||
>;
|
>;
|
||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { SecretVersionsSchema, TableName, TSecretVersions, TSecretVersionsUpdate } from "@app/db/schemas";
|
import { TableName, TSecretVersions, TSecretVersionsUpdate } from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
|
|
||||||
@@ -12,50 +12,6 @@ export type TSecretVersionDALFactory = ReturnType<typeof secretVersionDALFactory
|
|||||||
export const secretVersionDALFactory = (db: TDbClient) => {
|
export const secretVersionDALFactory = (db: TDbClient) => {
|
||||||
const secretVersionOrm = ormify(db, TableName.SecretVersion);
|
const secretVersionOrm = ormify(db, TableName.SecretVersion);
|
||||||
|
|
||||||
const findBySecretId = async (secretId: string, { offset, limit, sort, tx }: TFindOpt<TSecretVersions> = {}) => {
|
|
||||||
try {
|
|
||||||
const query = (tx || db.replicaNode())(TableName.SecretVersion)
|
|
||||||
.where(`${TableName.SecretVersion}.secretId`, secretId)
|
|
||||||
.leftJoin(TableName.Secret, `${TableName.SecretVersion}.secretId`, `${TableName.Secret}.id`)
|
|
||||||
.leftJoin(TableName.JnSecretTag, `${TableName.Secret}.id`, `${TableName.JnSecretTag}.${TableName.Secret}Id`)
|
|
||||||
.leftJoin(TableName.SecretTag, `${TableName.JnSecretTag}.${TableName.SecretTag}Id`, `${TableName.SecretTag}.id`)
|
|
||||||
.select(selectAllTableCols(TableName.SecretVersion))
|
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
|
||||||
|
|
||||||
if (limit) void query.limit(limit);
|
|
||||||
if (offset) void query.offset(offset);
|
|
||||||
if (sort) {
|
|
||||||
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
|
||||||
}
|
|
||||||
|
|
||||||
const docs = await query;
|
|
||||||
|
|
||||||
const data = sqlNestRelationships({
|
|
||||||
data: docs,
|
|
||||||
key: "id",
|
|
||||||
parentMapper: (el) => ({ _id: el.id, ...SecretVersionsSchema.parse(el) }),
|
|
||||||
childrenMapper: [
|
|
||||||
{
|
|
||||||
key: "tagId",
|
|
||||||
label: "tags" as const,
|
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
|
||||||
id,
|
|
||||||
color,
|
|
||||||
slug,
|
|
||||||
name: slug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: `${TableName.SecretVersion}: FindBySecretId` });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This will fetch all latest secret versions from a folder
|
// This will fetch all latest secret versions from a folder
|
||||||
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
@@ -193,7 +149,6 @@ export const secretVersionDALFactory = (db: TDbClient) => {
|
|||||||
findLatestVersionMany,
|
findLatestVersionMany,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
findLatestVersionByFolderId,
|
findLatestVersionByFolderId,
|
||||||
findBySecretId,
|
|
||||||
bulkUpdateNoVersionIncrement
|
bulkUpdateNoVersionIncrement
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import bcrypt from "bcrypt";
|
|||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -71,7 +67,7 @@ export const serviceTokenServiceFactory = ({
|
|||||||
|
|
||||||
scopes.forEach(({ environment, secretPath }) => {
|
scopes.forEach(({ environment, secretPath }) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export type TCreateServiceTokenDTO = {
|
|||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
expiresIn?: number | null;
|
expiresIn?: number | null;
|
||||||
permissions: ("read" | "write" | "readValue")[];
|
permissions: ("read" | "write")[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetServiceTokenInfoDTO = Omit<TProjectPermission, "projectId">;
|
export type TGetServiceTokenInfoDTO = Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
import { useState } from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { faChevronRight, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
|
||||||
import {
|
|
||||||
faChevronRight,
|
|
||||||
faExclamationTriangle,
|
|
||||||
faEye,
|
|
||||||
faEyeSlash
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import { FormControl, FormLabel, SecretInput, Spinner, Tooltip } from "@app/components/v2";
|
import { FormControl, FormLabel, SecretInput, Spinner, Tooltip } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useGetSecretReferenceTree } from "@app/hooks/api";
|
import { useGetSecretReferenceTree } from "@app/hooks/api";
|
||||||
import { ApiErrorTypes, TApiErrors, TSecretReferenceTraceNode } from "@app/hooks/api/types";
|
import { TSecretReferenceTraceNode } from "@app/hooks/api/types";
|
||||||
|
|
||||||
import style from "./SecretReferenceDetails.module.css";
|
import style from "./SecretReferenceDetails.module.css";
|
||||||
|
|
||||||
@@ -92,7 +84,7 @@ export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Prop
|
|||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const projectId = currentWorkspace?.id || "";
|
const projectId = currentWorkspace?.id || "";
|
||||||
|
|
||||||
const { data, isPending, isError, error } = useGetSecretReferenceTree({
|
const { data, isPending } = useGetSecretReferenceTree({
|
||||||
secretPath,
|
secretPath,
|
||||||
environmentSlug: environment,
|
environmentSlug: environment,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -102,26 +94,6 @@ export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Prop
|
|||||||
const tree = data?.tree;
|
const tree = data?.tree;
|
||||||
const secretValue = data?.value;
|
const secretValue = data?.value;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
const err = error?.response?.data as TApiErrors;
|
|
||||||
|
|
||||||
if (err?.error === ApiErrorTypes.CustomForbiddenError) {
|
|
||||||
createNotification({
|
|
||||||
title: "You don't have permission to view reference tree",
|
|
||||||
text: "You don't have permission to view one or more of the referenced secrets.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
createNotification({
|
|
||||||
title: "Error fetching secret reference tree",
|
|
||||||
text: "Please try again later.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [error]);
|
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-4">
|
<div className="flex items-center justify-center py-4">
|
||||||
@@ -142,16 +114,11 @@ export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Prop
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="mb-2" label="Reference Tree" />
|
<FormLabel className="mb-2" label="Reference Tree" />
|
||||||
<div className="thin-scrollbar relative max-h-96 overflow-auto rounded-md border border-mineshaft-600 bg-bunker-700 py-6 text-sm text-mineshaft-200">
|
<div className="thin-scrollbar relative max-h-96 overflow-auto rounded-md border border-mineshaft-600 bg-bunker-700 py-6 text-sm text-mineshaft-200">
|
||||||
{isError ? (
|
{tree && (
|
||||||
<div className="flex items-center justify-center py-4">
|
|
||||||
<FontAwesomeIcon icon={faExclamationTriangle} className="mr-2 text-red-500" />
|
|
||||||
<p className="text-red-500">Error fetching secret reference tree</p>
|
|
||||||
</div>
|
|
||||||
) : tree ? (
|
|
||||||
<ul className={style.tree}>
|
<ul className={style.tree}>
|
||||||
<SecretReferenceNode node={tree} isRoot secretKey={secretKey} />
|
<SecretReferenceNode node={tree} isRoot secretKey={secretKey} />
|
||||||
</ul>
|
</ul>
|
||||||
) : null}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 text-sm text-mineshaft-400">
|
<div className="mt-2 text-sm text-mineshaft-400">
|
||||||
Click a secret key to view its sub-references.
|
Click a secret key to view its sub-references.
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { Tooltip } from "../Tooltip/Tooltip";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
className?: string;
|
|
||||||
tooltipText?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Blur = ({ className, tooltipText }: IProps) => {
|
|
||||||
return (
|
|
||||||
<Tooltip content={tooltipText} isDisabled={!tooltipText}>
|
|
||||||
<div
|
|
||||||
className={twMerge("flex w-80 flex-grow items-center py-1 pl-4 pr-2", className)}
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<span className="blur">********</span>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { Blur } from "./Blur";
|
|
||||||
@@ -120,7 +120,6 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
|
|
||||||
const isPopupOpen = Boolean(suggestionSource.isOpen) && isFocused;
|
const isPopupOpen = Boolean(suggestionSource.isOpen) && isFocused;
|
||||||
const { data: secrets } = useGetProjectSecrets({
|
const { data: secrets } = useGetProjectSecrets({
|
||||||
viewSecretValue: false,
|
|
||||||
environment: suggestionSource.environment || "",
|
environment: suggestionSource.environment || "",
|
||||||
secretPath: suggestionSource.secretPath || "",
|
secretPath: suggestionSource.secretPath || "",
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ export enum ProjectPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretActions {
|
|
||||||
DescribeSecret = "read",
|
|
||||||
ReadValue = "readValue",
|
|
||||||
Create = "create",
|
|
||||||
Edit = "edit",
|
|
||||||
Delete = "delete"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProjectPermissionDynamicSecretActions {
|
export enum ProjectPermissionDynamicSecretActions {
|
||||||
ReadRootCredential = "read-root-credential",
|
ReadRootCredential = "read-root-credential",
|
||||||
CreateRootCredential = "create-root-credential",
|
CreateRootCredential = "create-root-credential",
|
||||||
@@ -146,7 +138,7 @@ export type SecretImportSubjectFields = {
|
|||||||
|
|
||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionActions,
|
||||||
(
|
(
|
||||||
| ProjectPermissionSub.Secrets
|
| ProjectPermissionSub.Secrets
|
||||||
| (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
| (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
||||||
|
|||||||
@@ -207,7 +207,6 @@ export const useGetProjectSecretsDetails = (
|
|||||||
search = "",
|
search = "",
|
||||||
includeSecrets,
|
includeSecrets,
|
||||||
includeFolders,
|
includeFolders,
|
||||||
viewSecretValue,
|
|
||||||
includeImports,
|
includeImports,
|
||||||
includeDynamicSecrets,
|
includeDynamicSecrets,
|
||||||
tags
|
tags
|
||||||
@@ -232,7 +231,6 @@ export const useGetProjectSecretsDetails = (
|
|||||||
limit,
|
limit,
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
viewSecretValue,
|
|
||||||
offset,
|
offset,
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
@@ -249,7 +247,6 @@ export const useGetProjectSecretsDetails = (
|
|||||||
limit,
|
limit,
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
viewSecretValue,
|
|
||||||
offset,
|
offset,
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ export type TGetDashboardProjectSecretsDetailsDTO = Omit<
|
|||||||
TGetDashboardProjectSecretsOverviewDTO,
|
TGetDashboardProjectSecretsOverviewDTO,
|
||||||
"environments"
|
"environments"
|
||||||
> & {
|
> & {
|
||||||
viewSecretValue: boolean;
|
|
||||||
environment: string;
|
environment: string;
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
tags: Record<string, boolean>;
|
tags: Record<string, boolean>;
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ export const decryptSecrets = (
|
|||||||
id: encSecret.id,
|
id: encSecret.id,
|
||||||
env: encSecret.environment,
|
env: encSecret.environment,
|
||||||
key: secretKey,
|
key: secretKey,
|
||||||
secretValueHidden: encSecret.secretValueHidden,
|
|
||||||
value: secretValue,
|
value: secretValue,
|
||||||
tags: encSecret.tags,
|
tags: encSecret.tags,
|
||||||
comment: secretComment,
|
comment: secretComment,
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ export const useGetImportedSecretsSingleEnv = ({
|
|||||||
env: encSecret.environment,
|
env: encSecret.environment,
|
||||||
key: encSecret.secretKey,
|
key: encSecret.secretKey,
|
||||||
value: encSecret.secretValue,
|
value: encSecret.secretValue,
|
||||||
secretValueHidden: encSecret.secretValueHidden,
|
|
||||||
tags: encSecret.tags,
|
tags: encSecret.tags,
|
||||||
comment: encSecret.secretComment,
|
comment: encSecret.secretComment,
|
||||||
createdAt: encSecret.createdAt,
|
createdAt: encSecret.createdAt,
|
||||||
@@ -177,7 +176,6 @@ export const useGetImportedSecretsAllEnvs = ({
|
|||||||
env: encSecret.environment,
|
env: encSecret.environment,
|
||||||
key: encSecret.secretKey,
|
key: encSecret.secretKey,
|
||||||
value: encSecret.secretValue,
|
value: encSecret.secretValue,
|
||||||
secretValueHidden: encSecret.secretValueHidden,
|
|
||||||
tags: encSecret.tags,
|
tags: encSecret.tags,
|
||||||
comment: encSecret.secretComment,
|
comment: encSecret.secretComment,
|
||||||
createdAt: encSecret.createdAt,
|
createdAt: encSecret.createdAt,
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export const useGetSnapshotSecrets = ({ snapshotId }: TSnapshotDataProps) =>
|
|||||||
id: secretVersion.secretId,
|
id: secretVersion.secretId,
|
||||||
env: data.environment.slug,
|
env: data.environment.slug,
|
||||||
key: secretVersion.secretKey,
|
key: secretVersion.secretKey,
|
||||||
secretValueHidden: secretVersion.secretValueHidden,
|
|
||||||
value: secretVersion.secretValue || "",
|
value: secretVersion.secretValue || "",
|
||||||
tags: secretVersion.tags,
|
tags: secretVersion.tags,
|
||||||
comment: secretVersion.secretComment,
|
comment: secretVersion.secretComment,
|
||||||
|
|||||||
@@ -26,13 +26,8 @@ import {
|
|||||||
|
|
||||||
export const secretKeys = {
|
export const secretKeys = {
|
||||||
// this is also used in secretSnapshot part
|
// this is also used in secretSnapshot part
|
||||||
getProjectSecret: ({
|
getProjectSecret: ({ workspaceId, environment, secretPath }: TGetProjectSecretsKey) =>
|
||||||
workspaceId,
|
[{ workspaceId, environment, secretPath }, "secrets"] as const,
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
viewSecretValue
|
|
||||||
}: TGetProjectSecretsKey) =>
|
|
||||||
[{ workspaceId, environment, secretPath, viewSecretValue }, "secrets"] as const,
|
|
||||||
getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const,
|
getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const,
|
||||||
getSecretAccessList: ({
|
getSecretAccessList: ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -49,15 +44,13 @@ export const fetchProjectSecrets = async ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
includeImports,
|
includeImports,
|
||||||
expandSecretReferences,
|
expandSecretReferences
|
||||||
viewSecretValue
|
|
||||||
}: TGetProjectSecretsKey) => {
|
}: TGetProjectSecretsKey) => {
|
||||||
const { data } = await apiRequest.get<SecretV3RawResponse>("/api/v3/secrets/raw", {
|
const { data } = await apiRequest.get<SecretV3RawResponse>("/api/v3/secrets/raw", {
|
||||||
params: {
|
params: {
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretPath,
|
secretPath,
|
||||||
viewSecretValue,
|
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
include_imports: includeImports
|
include_imports: includeImports
|
||||||
}
|
}
|
||||||
@@ -75,7 +68,6 @@ export const mergePersonalSecrets = (rawSecrets: SecretV3Raw[]) => {
|
|||||||
env: el.environment,
|
env: el.environment,
|
||||||
key: el.secretKey,
|
key: el.secretKey,
|
||||||
value: el.secretValue,
|
value: el.secretValue,
|
||||||
secretValueHidden: el.secretValueHidden,
|
|
||||||
tags: el.tags || [],
|
tags: el.tags || [],
|
||||||
comment: el.secretComment || "",
|
comment: el.secretComment || "",
|
||||||
reminderRepeatDays: el.secretReminderRepeatDays,
|
reminderRepeatDays: el.secretReminderRepeatDays,
|
||||||
@@ -115,7 +107,6 @@ export const useGetProjectSecrets = ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
viewSecretValue,
|
|
||||||
options
|
options
|
||||||
}: TGetProjectSecretsDTO & {
|
}: TGetProjectSecretsDTO & {
|
||||||
options?: Omit<
|
options?: Omit<
|
||||||
@@ -132,13 +123,8 @@ export const useGetProjectSecrets = ({
|
|||||||
...options,
|
...options,
|
||||||
// wait for all values to be available
|
// wait for all values to be available
|
||||||
enabled: Boolean(workspaceId && environment) && (options?.enabled ?? true),
|
enabled: Boolean(workspaceId && environment) && (options?.enabled ?? true),
|
||||||
queryKey: secretKeys.getProjectSecret({
|
queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }),
|
||||||
workspaceId,
|
queryFn: () => fetchProjectSecrets({ workspaceId, environment, secretPath }),
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
viewSecretValue
|
|
||||||
}),
|
|
||||||
queryFn: () => fetchProjectSecrets({ workspaceId, environment, secretPath, viewSecretValue }),
|
|
||||||
select: useCallback(
|
select: useCallback(
|
||||||
(data: Awaited<ReturnType<typeof fetchProjectSecrets>>) => mergePersonalSecrets(data.secrets),
|
(data: Awaited<ReturnType<typeof fetchProjectSecrets>>) => mergePersonalSecrets(data.secrets),
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export type EncryptedSecret = {
|
|||||||
secretValueCiphertext: string;
|
secretValueCiphertext: string;
|
||||||
secretValueIV: string;
|
secretValueIV: string;
|
||||||
secretValueTag: string;
|
secretValueTag: string;
|
||||||
secretValueHidden: boolean;
|
|
||||||
__v: number;
|
__v: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -38,7 +37,6 @@ export type SecretV3RawSanitized = {
|
|||||||
version: number;
|
version: number;
|
||||||
key: string;
|
key: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
secretValueHidden: boolean;
|
|
||||||
comment?: string;
|
comment?: string;
|
||||||
reminderRepeatDays?: number | null;
|
reminderRepeatDays?: number | null;
|
||||||
reminderNote?: string | null;
|
reminderNote?: string | null;
|
||||||
@@ -63,7 +61,6 @@ export type SecretV3Raw = {
|
|||||||
environment: string;
|
environment: string;
|
||||||
version: number;
|
version: number;
|
||||||
type: string;
|
type: string;
|
||||||
secretValueHidden: boolean;
|
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretValue?: string;
|
secretValue?: string;
|
||||||
@@ -98,7 +95,6 @@ export type SecretVersions = {
|
|||||||
envId: string;
|
envId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretValue?: string;
|
secretValue?: string;
|
||||||
secretValueHidden: boolean;
|
|
||||||
secretComment?: string;
|
secretComment?: string;
|
||||||
tags: WsTag[];
|
tags: WsTag[];
|
||||||
__v: number;
|
__v: number;
|
||||||
@@ -113,7 +109,6 @@ export type TGetProjectSecretsKey = {
|
|||||||
environment: string;
|
environment: string;
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
viewSecretValue?: boolean;
|
|
||||||
expandSecretReferences?: boolean;
|
expandSecretReferences?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ export enum ApiErrorTypes {
|
|||||||
ValidationError = "ValidationFailure",
|
ValidationError = "ValidationFailure",
|
||||||
BadRequestError = "BadRequest",
|
BadRequestError = "BadRequest",
|
||||||
UnauthorizedError = "UnauthorizedError",
|
UnauthorizedError = "UnauthorizedError",
|
||||||
ForbiddenError = "PermissionDenied",
|
ForbiddenError = "PermissionDenied"
|
||||||
CustomForbiddenError = "ForbiddenError"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TApiErrors =
|
export type TApiErrors =
|
||||||
@@ -70,12 +69,6 @@ export type TApiErrors =
|
|||||||
details: PureAbility["rules"];
|
details: PureAbility["rules"];
|
||||||
statusCode: 403;
|
statusCode: 403;
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
reqId: string;
|
|
||||||
error: ApiErrorTypes.CustomForbiddenError;
|
|
||||||
message: string;
|
|
||||||
statusCode: 403;
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
reqId: string;
|
reqId: string;
|
||||||
statusCode: 400;
|
statusCode: 400;
|
||||||
|
|||||||
+1
-7
@@ -58,7 +58,6 @@ const schema = z.object({
|
|||||||
permissions: z
|
permissions: z
|
||||||
.object({
|
.object({
|
||||||
read: z.boolean(),
|
read: z.boolean(),
|
||||||
readValue: z.boolean(),
|
|
||||||
write: z.boolean()
|
write: z.boolean()
|
||||||
})
|
})
|
||||||
.required()
|
.required()
|
||||||
@@ -297,19 +296,14 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
name="permissions"
|
name="permissions"
|
||||||
defaultValue={{
|
defaultValue={{
|
||||||
read: true,
|
read: true,
|
||||||
readValue: false,
|
|
||||||
write: false
|
write: false
|
||||||
}}
|
}}
|
||||||
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
label: "Describe Secret (default)",
|
label: "Read (default)",
|
||||||
value: "read"
|
value: "read"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Read Value (optional)",
|
|
||||||
value: "readValue"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Write (optional)",
|
label: "Write (optional)",
|
||||||
value: "write"
|
value: "write"
|
||||||
|
|||||||
+12
-15
@@ -117,27 +117,24 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
|
|||||||
<div className="flex flex-grow flex-wrap justify-start gap-8">
|
<div className="flex flex-grow flex-wrap justify-start gap-8">
|
||||||
{actions.map(({ label, value }) => {
|
{actions.map(({ label, value }) => {
|
||||||
if (typeof value !== "string") return undefined;
|
if (typeof value !== "string") return undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
key={`${el.id}-${label}`}
|
key={`${el.id}-${label}`}
|
||||||
name={`permissions.${subject}.${rootIndex}.${value}` as any}
|
name={`permissions.${subject}.${rootIndex}.${value}` as any}
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
render={({ field }) => {
|
render={({ field }) => (
|
||||||
return (
|
<div className="flex items-center justify-center">
|
||||||
<div className="flex items-center justify-center">
|
<Checkbox
|
||||||
<Checkbox
|
isDisabled={isDisabled}
|
||||||
isDisabled={isDisabled}
|
isChecked={Boolean(field.value)}
|
||||||
isChecked={Boolean(field.value)}
|
onCheckedChange={field.onChange}
|
||||||
onCheckedChange={field.onChange}
|
id={`permissions.${subject}.${rootIndex}.${String(value)}`}
|
||||||
id={`permissions.${subject}.${rootIndex}.${String(value)}`}
|
>
|
||||||
>
|
{label}
|
||||||
{label}
|
</Checkbox>
|
||||||
</Checkbox>
|
</div>
|
||||||
</div>
|
)}
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
+2
-34
@@ -9,7 +9,6 @@ import {
|
|||||||
PermissionConditionOperators,
|
PermissionConditionOperators,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
ProjectPermissionKmipActions,
|
ProjectPermissionKmipActions,
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSecretSyncActions,
|
ProjectPermissionSecretSyncActions,
|
||||||
TPermissionCondition,
|
TPermissionCondition,
|
||||||
TPermissionConditionOperators
|
TPermissionConditionOperators
|
||||||
@@ -23,14 +22,6 @@ const GeneralPolicyActionSchema = z.object({
|
|||||||
create: z.boolean().optional()
|
create: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const SecretPolicyActionSchema = z.object({
|
|
||||||
read: z.boolean().optional(), // describe secret
|
|
||||||
edit: z.boolean().optional(),
|
|
||||||
delete: z.boolean().optional(),
|
|
||||||
create: z.boolean().optional(),
|
|
||||||
readValue: z.boolean().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
const CmekPolicyActionSchema = z.object({
|
const CmekPolicyActionSchema = z.object({
|
||||||
read: z.boolean().optional(),
|
read: z.boolean().optional(),
|
||||||
edit: z.boolean().optional(),
|
edit: z.boolean().optional(),
|
||||||
@@ -123,7 +114,7 @@ export const projectRoleFormSchema = z.object({
|
|||||||
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
|
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
|
||||||
permissions: z
|
permissions: z
|
||||||
.object({
|
.object({
|
||||||
[ProjectPermissionSub.Secrets]: SecretPolicyActionSchema.extend({
|
[ProjectPermissionSub.Secrets]: GeneralPolicyActionSchema.extend({
|
||||||
inverted: z.boolean().optional(),
|
inverted: z.boolean().optional(),
|
||||||
conditions: ConditionSchema
|
conditions: ConditionSchema
|
||||||
})
|
})
|
||||||
@@ -292,28 +283,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.Secrets) {
|
|
||||||
const canRead = action.includes(ProjectPermissionSecretActions.DescribeSecret);
|
|
||||||
const canEdit = action.includes(ProjectPermissionSecretActions.Edit);
|
|
||||||
const canDelete = action.includes(ProjectPermissionSecretActions.Delete);
|
|
||||||
const canCreate = action.includes(ProjectPermissionSecretActions.Create);
|
|
||||||
const canReadValue = action.includes(ProjectPermissionSecretActions.ReadValue);
|
|
||||||
|
|
||||||
// from above statement we are sure it won't be undefined
|
|
||||||
formVal[subject]!.push({
|
|
||||||
read: canRead,
|
|
||||||
create: canCreate,
|
|
||||||
edit: canEdit,
|
|
||||||
delete: canDelete,
|
|
||||||
readValue: canReadValue,
|
|
||||||
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
|
||||||
inverted
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for other subjects
|
// for other subjects
|
||||||
const canRead = action.includes(ProjectPermissionActions.Read);
|
const canRead = action.includes(ProjectPermissionActions.Read);
|
||||||
const canEdit = action.includes(ProjectPermissionActions.Edit);
|
const canEdit = action.includes(ProjectPermissionActions.Edit);
|
||||||
@@ -514,9 +483,8 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
[ProjectPermissionSub.Secrets]: {
|
[ProjectPermissionSub.Secrets]: {
|
||||||
title: "Secrets",
|
title: "Secrets",
|
||||||
actions: [
|
actions: [
|
||||||
{ label: "Describe Secret", value: "read" },
|
{ label: "Read", value: "read" },
|
||||||
{ label: "Create", value: "create" },
|
{ label: "Create", value: "create" },
|
||||||
{ label: "Read Value", value: "readValue" },
|
|
||||||
{ label: "Modify", value: "edit" },
|
{ label: "Modify", value: "edit" },
|
||||||
{ label: "Remove", value: "delete" }
|
{ label: "Remove", value: "delete" }
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-2
@@ -19,7 +19,6 @@ import {
|
|||||||
import { getKeyValue } from "@app/helpers/parseEnvVar";
|
import { getKeyValue } from "@app/helpers/parseEnvVar";
|
||||||
import { useCreateFolder, useCreateSecretV3, useCreateWsTag, useGetWsTags } from "@app/hooks/api";
|
import { useCreateFolder, useCreateSecretV3, useCreateWsTag, useGetWsTags } from "@app/hooks/api";
|
||||||
import { SecretType } from "@app/hooks/api/types";
|
import { SecretType } from "@app/hooks/api/types";
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
|
||||||
|
|
||||||
const typeSchema = z
|
const typeSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -276,7 +275,7 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => {
|
|||||||
isMulti
|
isMulti
|
||||||
options={environments.filter((environment) =>
|
options={environments.filter((environment) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: environment.slug,
|
environment: environment.slug,
|
||||||
secretPath,
|
secretPath,
|
||||||
|
|||||||
+17
-26
@@ -25,7 +25,6 @@ import {
|
|||||||
ModalTrigger,
|
ModalTrigger,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
@@ -40,7 +39,6 @@ type Props = {
|
|||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
isImportedSecret: boolean;
|
isImportedSecret: boolean;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretValueHidden: boolean;
|
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
|
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
|
||||||
onSecretUpdate: (
|
onSecretUpdate: (
|
||||||
@@ -60,7 +58,6 @@ export const SecretEditRow = ({
|
|||||||
isImportedSecret,
|
isImportedSecret,
|
||||||
onSecretUpdate,
|
onSecretUpdate,
|
||||||
secretName,
|
secretName,
|
||||||
secretValueHidden,
|
|
||||||
onSecretCreate,
|
onSecretCreate,
|
||||||
onSecretDelete,
|
onSecretDelete,
|
||||||
environment,
|
environment,
|
||||||
@@ -143,29 +140,24 @@ export const SecretEditRow = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex-grow border-r border-r-mineshaft-600 pl-1 pr-2">
|
<div className="flex-grow border-r border-r-mineshaft-600 pl-1 pr-2">
|
||||||
{secretValueHidden ? (
|
<Controller
|
||||||
<Blur tooltipText="You do not have permission to read the value of this secret." />
|
disabled={isImportedSecret && !defaultValue}
|
||||||
) : (
|
control={control}
|
||||||
<Controller
|
name="value"
|
||||||
disabled={isImportedSecret && !defaultValue}
|
render={({ field }) => (
|
||||||
control={control}
|
<InfisicalSecretInput
|
||||||
name="value"
|
{...field}
|
||||||
render={({ field }) => (
|
isReadOnly={isImportedSecret}
|
||||||
<InfisicalSecretInput
|
value={field.value as string}
|
||||||
{...field}
|
key="secret-input"
|
||||||
isReadOnly={isImportedSecret}
|
isVisible={isVisible}
|
||||||
value={field.value as string}
|
secretPath={secretPath}
|
||||||
key="secret-input"
|
environment={environment}
|
||||||
isVisible={isVisible}
|
isImport={isImportedSecret}
|
||||||
secretPath={secretPath}
|
/>
|
||||||
environment={environment}
|
)}
|
||||||
isImport={isImportedSecret}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex w-24 justify-center space-x-3 pl-2 transition-all",
|
"flex w-24 justify-center space-x-3 pl-2 transition-all",
|
||||||
@@ -219,7 +211,6 @@ export const SecretEditRow = ({
|
|||||||
<div className="opacity-0 group-hover:opacity-100">
|
<div className="opacity-0 group-hover:opacity-100">
|
||||||
<Tooltip content="Copy Secret">
|
<Tooltip content="Copy Secret">
|
||||||
<IconButton
|
<IconButton
|
||||||
isDisabled={secretValueHidden}
|
|
||||||
ariaLabel="copy-value"
|
ariaLabel="copy-value"
|
||||||
onClick={handleCopySecretToClipboard}
|
onClick={handleCopySecretToClipboard}
|
||||||
variant="plain"
|
variant="plain"
|
||||||
|
|||||||
+1
-2
@@ -3,7 +3,6 @@ import { faLock } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { Td, Tooltip, Tr } from "@app/components/v2";
|
import { Td, Tooltip, Tr } from "@app/components/v2";
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
environments: { name: string; slug: string }[];
|
environments: { name: string; slug: string }[];
|
||||||
@@ -26,7 +25,7 @@ export const SecretNoAccessOverviewTableRow = ({ environments = [], count }: Pro
|
|||||||
<div className="text-bunker-300">
|
<div className="text-bunker-300">
|
||||||
<FontAwesomeIcon className="block" icon={faLock} />
|
<FontAwesomeIcon className="block" icon={faLock} />
|
||||||
</div>
|
</div>
|
||||||
<Blur />
|
<div className="blur-sm">NO ACCESS</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+3
-6
@@ -221,13 +221,10 @@ export const SecretOverviewTableRow = ({
|
|||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
isVisible={isSecretVisible}
|
isVisible={isSecretVisible}
|
||||||
secretName={secretKey}
|
secretName={secretKey}
|
||||||
secretValueHidden={secret?.secretValueHidden || false}
|
|
||||||
defaultValue={
|
defaultValue={
|
||||||
secret?.secretValueHidden
|
secret?.valueOverride ||
|
||||||
? ""
|
secret?.value ||
|
||||||
: secret?.valueOverride ||
|
importedSecret?.secret?.value
|
||||||
secret?.value ||
|
|
||||||
importedSecret?.secret?.value
|
|
||||||
}
|
}
|
||||||
secretId={secret?.id}
|
secretId={secret?.id}
|
||||||
isOverride={Boolean(secret?.valueOverride)}
|
isOverride={Boolean(secret?.valueOverride)}
|
||||||
|
|||||||
+8
-4
@@ -10,8 +10,12 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { IconButton, Input, Spinner, Tooltip } from "@app/components/v2";
|
import { IconButton, Input, Spinner, Tooltip } from "@app/components/v2";
|
||||||
import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
|
import {
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useProjectPermission,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { useUpdateSecretV3 } from "@app/hooks/api";
|
import { useUpdateSecretV3 } from "@app/hooks/api";
|
||||||
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types";
|
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types";
|
||||||
@@ -51,8 +55,8 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
|
|||||||
secretTags: (secretDetails?.tags || []).map((i) => i.slug)
|
secretTags: (secretDetails?.tags || []).map((i) => i.slug)
|
||||||
});
|
});
|
||||||
const isSecretInEnvReadOnly =
|
const isSecretInEnvReadOnly =
|
||||||
permission.can(ProjectPermissionSecretActions.DescribeSecret, secretPermissionSubject) &&
|
permission.can(ProjectPermissionActions.Read, secretPermissionSubject) &&
|
||||||
permission.cannot(ProjectPermissionSecretActions.Edit, secretPermissionSubject);
|
permission.cannot(ProjectPermissionActions.Edit, secretPermissionSubject);
|
||||||
if (isSecretInEnvReadOnly) {
|
if (isSecretInEnvReadOnly) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-41
@@ -110,31 +110,21 @@ export const QuickSearchSecretItem = ({
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{isSingleEnv ? (
|
{isSingleEnv ? (
|
||||||
<Tooltip
|
<IconButton
|
||||||
isDisabled={!groupSecret?.secretValueHidden}
|
size="md"
|
||||||
content={
|
variant="plain"
|
||||||
groupSecret?.secretValueHidden
|
colorSchema="secondary"
|
||||||
? "You do not have permission to view this secret value"
|
ariaLabel="Copy secret value"
|
||||||
: ""
|
onClick={(e) => {
|
||||||
}
|
e.stopPropagation();
|
||||||
|
const el = envSlugMap.get(groupSecret.env)?.name;
|
||||||
|
if (el) {
|
||||||
|
handleCopy(groupSecret.value!, el);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
|
||||||
size="md"
|
</IconButton>
|
||||||
isDisabled={groupSecret?.secretValueHidden}
|
|
||||||
variant="plain"
|
|
||||||
colorSchema="secondary"
|
|
||||||
ariaLabel="Copy secret value"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const el = envSlugMap.get(groupSecret.env)?.name;
|
|
||||||
if (el) {
|
|
||||||
handleCopy(groupSecret.value!, el);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={isUrlCopied ? faCheck : faCopy} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
) : (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@@ -168,24 +158,14 @@ export const QuickSearchSecretItem = ({
|
|||||||
)}
|
)}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Tooltip
|
<IconButton
|
||||||
isDisabled={!groupSecret?.secretValueHidden}
|
size="md"
|
||||||
content={
|
variant="plain"
|
||||||
groupSecret?.secretValueHidden
|
colorSchema="secondary"
|
||||||
? "You do not have permission to view this secret value"
|
ariaLabel="View secret value"
|
||||||
: ""
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<IconButton
|
<FontAwesomeIcon icon={faEye} />
|
||||||
size="md"
|
</IconButton>
|
||||||
isDisabled={groupSecret?.secretValueHidden}
|
|
||||||
variant="plain"
|
|
||||||
colorSchema="secondary"
|
|
||||||
ariaLabel="View secret value"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faEye} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuLabel>Hover to Reveal...</DropdownMenuLabel>
|
<DropdownMenuLabel>Hover to Reveal...</DropdownMenuLabel>
|
||||||
|
|||||||
+2
-3
@@ -11,7 +11,6 @@ import {
|
|||||||
useProjectPermission,
|
useProjectPermission,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api";
|
import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api";
|
||||||
import {
|
import {
|
||||||
@@ -59,7 +58,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
|
|||||||
// user should have the ability to delete secrets/folders in at least one of the envs
|
// user should have the ability to delete secrets/folders in at least one of the envs
|
||||||
const shouldShowDelete = userAvailableEnvs.some((env) =>
|
const shouldShowDelete = userAvailableEnvs.some((env) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: env.slug,
|
environment: env.slug,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -111,7 +110,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
|
|||||||
(accum: TDeleteSecretBatchDTO["secrets"], secretRecord) => {
|
(accum: TDeleteSecretBatchDTO["secrets"], secretRecord) => {
|
||||||
const entry = secretRecord[env.slug];
|
const entry = secretRecord[env.slug];
|
||||||
const canDeleteSecret = permission.can(
|
const canDeleteSecret = permission.can(
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: env.slug,
|
environment: env.slug,
|
||||||
secretPath,
|
secretPath,
|
||||||
|
|||||||
+2
-3
@@ -25,8 +25,7 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
Switch
|
Switch
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ProjectPermissionSub, useProjectPermission } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
|
||||||
import { useDebounce } from "@app/hooks";
|
import { useDebounce } from "@app/hooks";
|
||||||
import { useMoveSecrets } from "@app/hooks/api";
|
import { useMoveSecrets } from "@app/hooks/api";
|
||||||
import { useGetProjectSecretsQuickSearch } from "@app/hooks/api/dashboard";
|
import { useGetProjectSecretsQuickSearch } from "@app/hooks/api/dashboard";
|
||||||
@@ -96,7 +95,7 @@ const Content = ({
|
|||||||
env.slug,
|
env.slug,
|
||||||
{
|
{
|
||||||
missingPermissions: permission.cannot(
|
missingPermissions: permission.cannot(
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: env.slug,
|
environment: env.slug,
|
||||||
secretPath: sourceSecretPath,
|
secretPath: sourceSecretPath,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
useProjectPermission,
|
useProjectPermission,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
|
||||||
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useGetImportedSecretsSingleEnv,
|
useGetImportedSecretsSingleEnv,
|
||||||
@@ -104,16 +103,7 @@ const Page = () => {
|
|||||||
const projectSlug = currentWorkspace?.slug || "";
|
const projectSlug = currentWorkspace?.slug || "";
|
||||||
const secretPath = (routerQueryParams.secretPath as string) || "/";
|
const secretPath = (routerQueryParams.secretPath as string) || "/";
|
||||||
const canReadSecret = permission.can(
|
const canReadSecret = permission.can(
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secretName: "*",
|
|
||||||
secretTags: ["*"]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const canReadSecretValue = permission.can(
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -186,7 +176,6 @@ const Page = () => {
|
|||||||
orderDirection,
|
orderDirection,
|
||||||
includeImports: canReadSecretImports && filter.include.import,
|
includeImports: canReadSecretImports && filter.include.import,
|
||||||
includeFolders: filter.include.folder,
|
includeFolders: filter.include.folder,
|
||||||
viewSecretValue: canReadSecretValue,
|
|
||||||
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
|
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
|
||||||
includeSecrets: canReadSecret && filter.include.secret,
|
includeSecrets: canReadSecret && filter.include.secret,
|
||||||
tags: filter.tags
|
tags: filter.tags
|
||||||
|
|||||||
+40
-61
@@ -21,7 +21,6 @@ import {
|
|||||||
faTrash
|
faTrash
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ import {
|
|||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
|
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
|
||||||
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
|
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
|
||||||
import { ApiErrorTypes, SecretType, TApiErrors, WsTag } from "@app/hooks/api/types";
|
import { SecretType, WsTag } from "@app/hooks/api/types";
|
||||||
import { SecretSearchInput } from "@app/pages/secret-manager/OverviewPage/components/SecretSearchInput";
|
import { SecretSearchInput } from "@app/pages/secret-manager/OverviewPage/components/SecretSearchInput";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -153,71 +152,51 @@ export const ActionBar = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSecretDownload = async () => {
|
const handleSecretDownload = async () => {
|
||||||
try {
|
const { secrets: localSecrets, imports: localImportedSecrets } = await fetchProjectSecrets({
|
||||||
const { secrets: localSecrets, imports: localImportedSecrets } = await fetchProjectSecrets({
|
workspaceId,
|
||||||
workspaceId,
|
expandSecretReferences: true,
|
||||||
expandSecretReferences: true,
|
includeImports: true,
|
||||||
includeImports: true,
|
environment,
|
||||||
environment,
|
secretPath
|
||||||
secretPath
|
});
|
||||||
});
|
const secretsPicked = new Set<string>();
|
||||||
const secretsPicked = new Set<string>();
|
const secretsToDownload: { key: string; value?: string; comment?: string }[] = [];
|
||||||
const secretsToDownload: { key: string; value?: string; comment?: string }[] = [];
|
localSecrets.forEach((el) => {
|
||||||
localSecrets.forEach((el) => {
|
secretsPicked.add(el.secretKey);
|
||||||
secretsPicked.add(el.secretKey);
|
secretsToDownload.push({
|
||||||
secretsToDownload.push({
|
key: el.secretKey,
|
||||||
key: el.secretKey,
|
value: el.secretValue,
|
||||||
value: el.secretValue,
|
comment: el.secretComment
|
||||||
comment: el.secretComment
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
for (let i = localImportedSecrets.length - 1; i >= 0; i -= 1) {
|
for (let i = localImportedSecrets.length - 1; i >= 0; i -= 1) {
|
||||||
for (let j = localImportedSecrets[i].secrets.length - 1; j >= 0; j -= 1) {
|
for (let j = localImportedSecrets[i].secrets.length - 1; j >= 0; j -= 1) {
|
||||||
const secret = localImportedSecrets[i].secrets[j];
|
const secret = localImportedSecrets[i].secrets[j];
|
||||||
if (!secretsPicked.has(secret.secretKey)) {
|
if (!secretsPicked.has(secret.secretKey)) {
|
||||||
secretsToDownload.push({
|
secretsToDownload.push({
|
||||||
key: secret.secretKey,
|
key: secret.secretKey,
|
||||||
value: secret.secretValue,
|
value: secret.secretValue,
|
||||||
comment: secret.secretComment
|
comment: secret.secretComment
|
||||||
});
|
|
||||||
}
|
|
||||||
secretsPicked.add(secret.secretKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = secretsToDownload
|
|
||||||
.sort((a, b) => a.key.toLowerCase().localeCompare(b.key.toLowerCase()))
|
|
||||||
.reduce(
|
|
||||||
(prev, { key, comment, value }, index) =>
|
|
||||||
prev +
|
|
||||||
(comment
|
|
||||||
? `${index === 0 ? "#" : "\n#"} ${comment}\n${key}=${value}\n`
|
|
||||||
: `${key}=${value}\n`),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
|
|
||||||
FileSaver.saveAs(blob, `${environment}.env`);
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof AxiosError) {
|
|
||||||
const error = err?.response?.data as TApiErrors;
|
|
||||||
|
|
||||||
if (error?.error === ApiErrorTypes.ForbiddenError && error.message.includes("readValue")) {
|
|
||||||
createNotification({
|
|
||||||
title: "You don't have permission to download secrets",
|
|
||||||
text: "You don't have permission to view one or more of the secrets in the current folder. Please contact your administrator.",
|
|
||||||
type: "error"
|
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
secretsPicked.add(secret.secretKey);
|
||||||
}
|
}
|
||||||
createNotification({
|
|
||||||
title: "Failed to download secrets",
|
|
||||||
text: "Please try again later.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const file = secretsToDownload
|
||||||
|
.sort((a, b) => a.key.toLowerCase().localeCompare(b.key.toLowerCase()))
|
||||||
|
.reduce(
|
||||||
|
(prev, { key, comment, value }, index) =>
|
||||||
|
prev +
|
||||||
|
(comment
|
||||||
|
? `${index === 0 ? "#" : "\n#"} ${comment}\n${key}=${value}\n`
|
||||||
|
: `${key}=${value}\n`),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
|
||||||
|
FileSaver.saveAs(blob, `${environment}.env`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSecretBulkDelete = async () => {
|
const handleSecretBulkDelete = async () => {
|
||||||
|
|||||||
+131
-199
@@ -9,14 +9,12 @@ import {
|
|||||||
faPlus,
|
faPlus,
|
||||||
faShare,
|
faShare,
|
||||||
faTag,
|
faTag,
|
||||||
faTrash,
|
faTrash
|
||||||
faTriangleExclamation
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -46,7 +44,6 @@ import {
|
|||||||
useProjectPermission,
|
useProjectPermission,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
import { useGetSecretVersion } from "@app/hooks/api";
|
import { useGetSecretVersion } from "@app/hooks/api";
|
||||||
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
|
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
|
||||||
@@ -125,7 +122,7 @@ export const SecretDetailSidebar = ({
|
|||||||
const selectTagSlugs = selectedTags.map((i) => i.slug);
|
const selectTagSlugs = selectedTags.map((i) => i.slug);
|
||||||
|
|
||||||
const cannotEditSecret = permission.cannot(
|
const cannotEditSecret = permission.cannot(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -133,29 +130,16 @@ export const SecretDetailSidebar = ({
|
|||||||
secretTags: selectTagSlugs
|
secretTags: selectTagSlugs
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const cannotReadSecretValue = permission.cannot(
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secretName: secretKey,
|
|
||||||
secretTags: selectTagSlugs
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const isReadOnly =
|
const isReadOnly =
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: secretKey,
|
secretName: secretKey,
|
||||||
secretTags: selectTagSlugs
|
secretTags: selectTagSlugs
|
||||||
})
|
})
|
||||||
) &&
|
) && cannotEditSecret;
|
||||||
cannotEditSecret &&
|
|
||||||
cannotReadSecretValue;
|
|
||||||
|
|
||||||
const overrideAction = watch("overrideAction");
|
const overrideAction = watch("overrideAction");
|
||||||
const isOverridden =
|
const isOverridden =
|
||||||
@@ -208,16 +192,9 @@ export const SecretDetailSidebar = ({
|
|||||||
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReminderSubmit = async (
|
const handleReminderSubmit = async (reminderRepeatDays: number | null | undefined, reminderNote: string | null | undefined) => {
|
||||||
reminderRepeatDays: number | null | undefined,
|
await onSaveSecret(secret, { ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true }, () => { });
|
||||||
reminderNote: string | null | undefined
|
}
|
||||||
) => {
|
|
||||||
await onSaveSecret(
|
|
||||||
secret,
|
|
||||||
{ ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true },
|
|
||||||
() => {}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
|
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
|
||||||
|
|
||||||
@@ -236,7 +213,7 @@ export const SecretDetailSidebar = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
setValue("reminderRepeatDays", data.days, { shouldDirty: false });
|
setValue("reminderRepeatDays", data.days, { shouldDirty: false });
|
||||||
setValue("reminderNote", data.note, { shouldDirty: false });
|
setValue("reminderNote", data.note, { shouldDirty: false });
|
||||||
handleReminderSubmit(data.days, data.note);
|
handleReminderSubmit(data.days, data.note)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -284,63 +261,38 @@ export const SecretDetailSidebar = ({
|
|||||||
key="secret-value"
|
key="secret-value"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<div className="flex items-center gap-2">
|
<FormControl label="Value">
|
||||||
<FormControl
|
<InfisicalSecretInput
|
||||||
className="flex-1"
|
isReadOnly={isReadOnly}
|
||||||
helperText={
|
environment={environment}
|
||||||
cannotReadSecretValue ? (
|
secretPath={secretPath}
|
||||||
<div className="flex space-x-2">
|
key="secret-value"
|
||||||
<FontAwesomeIcon
|
isDisabled={isOverridden || !isAllowed}
|
||||||
icon={faTriangleExclamation}
|
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
||||||
className="mt-0.5 text-yellow-400"
|
{...field}
|
||||||
/>
|
autoFocus={false}
|
||||||
<span>
|
/>
|
||||||
The value of this secret is hidden because you do not have the
|
</FormControl>
|
||||||
read secret value permission.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
label="Value"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<InfisicalSecretInput
|
|
||||||
isReadOnly={isReadOnly}
|
|
||||||
environment={environment}
|
|
||||||
secretPath={secretPath}
|
|
||||||
key="secret-value"
|
|
||||||
isDisabled={isOverridden || !isAllowed}
|
|
||||||
containerClassName="text-bunker-300 w-full hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
|
||||||
{...field}
|
|
||||||
autoFocus={false}
|
|
||||||
/>
|
|
||||||
<Tooltip
|
|
||||||
content="You don't have permission to view the secret value."
|
|
||||||
isDisabled={!secret?.secretValueHidden}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
isDisabled={secret?.secretValueHidden}
|
|
||||||
className="px-2 py-[0.43rem] font-normal"
|
|
||||||
variant="outline_bg"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faShare} />}
|
|
||||||
onClick={() => {
|
|
||||||
const value = secret?.valueOverride ?? secret?.value;
|
|
||||||
if (value) {
|
|
||||||
handleSecretShare(value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Share
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="ml-1 mt-1.5 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="w-full px-2 py-[0.43rem] font-normal"
|
||||||
|
variant="outline_bg"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faShare} />}
|
||||||
|
onClick={() => {
|
||||||
|
const value = secret?.valueOverride ?? secret?.value;
|
||||||
|
if (value) {
|
||||||
|
handleSecretShare(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-2 rounded border border-mineshaft-600 bg-mineshaft-900 p-4 px-0 pb-0">
|
<div className="mb-2 rounded border border-mineshaft-600 bg-mineshaft-900 p-4 px-0 pb-0">
|
||||||
<div className="mb-4 px-4">
|
<div className="mb-4 px-4">
|
||||||
@@ -666,34 +618,51 @@ export const SecretDetailSidebar = ({
|
|||||||
<div className="mb-4flex-grow dark cursor-default text-sm text-bunker-300">
|
<div className="mb-4flex-grow dark cursor-default text-sm text-bunker-300">
|
||||||
<div className="mb-2 pl-1">Version History</div>
|
<div className="mb-2 pl-1">Version History</div>
|
||||||
<div className="thin-scrollbar flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
<div className="thin-scrollbar flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
||||||
{secretVersion?.map(
|
{secretVersion?.map(({ createdAt, secretValue, version, id }) => (
|
||||||
({ createdAt, secretValue, version, id, secretValueHidden }, index) => (
|
<div className="flex flex-row">
|
||||||
<div key={`secret-version-${index + 1}`} className="flex flex-row">
|
<div key={id} className="flex w-full flex-col space-y-1">
|
||||||
<div key={id} className="flex w-full flex-col space-y-1">
|
<div className="flex items-center">
|
||||||
<div className="flex items-center">
|
<div className="w-10">
|
||||||
<div className="w-10">
|
<div className="w-fit rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 text-sm text-mineshaft-300">
|
||||||
<div className="w-fit rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 text-sm text-mineshaft-300">
|
v{version}
|
||||||
v{version}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>{format(new Date(createdAt), "Pp")}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full cursor-default">
|
<div>{format(new Date(createdAt), "Pp")}</div>
|
||||||
<div className="relative w-10">
|
</div>
|
||||||
<div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
|
<div className="flex w-full cursor-default">
|
||||||
|
<div className="relative w-10">
|
||||||
|
<div className="absolute bottom-0 left-3 top-0 mt-0.5 border-l border-mineshaft-400/60" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div className="h-min w-fit rounded-sm bg-primary-500/10 px-1 text-primary-300/70">
|
||||||
|
Value:
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
<div className="group break-all pl-1 font-mono">
|
||||||
<div className="h-min w-fit rounded-sm bg-primary-500/10 px-1 text-primary-300/70">
|
<div className="relative hidden cursor-pointer transition-all duration-200 group-[.show-value]:inline">
|
||||||
Value:
|
<button
|
||||||
</div>
|
type="button"
|
||||||
<div className="group break-all pl-1 font-mono">
|
className="select-none"
|
||||||
<div className="relative hidden cursor-pointer transition-all duration-200 group-[.show-value]:inline">
|
onClick={(e) => {
|
||||||
<button
|
navigator.clipboard.writeText(secretValue || "");
|
||||||
type="button"
|
const target = e.currentTarget;
|
||||||
className="select-none"
|
target.style.borderBottom = "1px dashed";
|
||||||
onClick={(e) => {
|
target.style.paddingBottom = "-1px";
|
||||||
if (secretValueHidden) return;
|
|
||||||
|
|
||||||
|
// Create and insert popup
|
||||||
|
const popup = document.createElement("div");
|
||||||
|
popup.className =
|
||||||
|
"w-16 flex justify-center absolute top-6 left-0 text-xs text-primary-100 bg-mineshaft-800 px-1 py-0.5 rounded-md border border-primary-500/50";
|
||||||
|
popup.textContent = "Copied!";
|
||||||
|
target.parentElement?.appendChild(popup);
|
||||||
|
|
||||||
|
// Remove popup and border after delay
|
||||||
|
setTimeout(() => {
|
||||||
|
popup.remove();
|
||||||
|
target.style.borderBottom = "none";
|
||||||
|
}, 3000);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
navigator.clipboard.writeText(secretValue || "");
|
navigator.clipboard.writeText(secretValue || "");
|
||||||
const target = e.currentTarget;
|
const target = e.currentTarget;
|
||||||
target.style.borderBottom = "1px dashed";
|
target.style.borderBottom = "1px dashed";
|
||||||
@@ -711,109 +680,72 @@ export const SecretDetailSidebar = ({
|
|||||||
popup.remove();
|
popup.remove();
|
||||||
target.style.borderBottom = "none";
|
target.style.borderBottom = "none";
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}}
|
}
|
||||||
onKeyDown={(e) => {
|
}}
|
||||||
if (secretValueHidden) return;
|
>
|
||||||
|
{secretValue}
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
</button>
|
||||||
navigator.clipboard.writeText(secretValue || "");
|
<button
|
||||||
const target = e.currentTarget;
|
type="button"
|
||||||
target.style.borderBottom = "1px dashed";
|
className="ml-1 cursor-pointer"
|
||||||
target.style.paddingBottom = "-1px";
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
// Create and insert popup
|
e.currentTarget
|
||||||
const popup = document.createElement("div");
|
.closest(".group")
|
||||||
popup.className =
|
?.classList.remove("show-value");
|
||||||
"w-16 flex justify-center absolute top-6 left-0 text-xs text-primary-100 bg-mineshaft-800 px-1 py-0.5 rounded-md border border-primary-500/50";
|
}}
|
||||||
popup.textContent = "Copied!";
|
onKeyDown={(e) => {
|
||||||
target.parentElement?.appendChild(popup);
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
|
|
||||||
// Remove popup and border after delay
|
|
||||||
setTimeout(() => {
|
|
||||||
popup.remove();
|
|
||||||
target.style.borderBottom = "none";
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
className="break-normal text-xs"
|
|
||||||
content="You do not have permission to view this secret value"
|
|
||||||
isDisabled={!secretValueHidden}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={twMerge(
|
|
||||||
secretValueHidden && "text-xs text-bunker-300 opacity-40"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{secretValueHidden ? "Hidden" : secretValue}
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="ml-1 cursor-pointer"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.currentTarget
|
e.currentTarget
|
||||||
.closest(".group")
|
.closest(".group")
|
||||||
?.classList.remove("show-value");
|
?.classList.remove("show-value");
|
||||||
}}
|
}
|
||||||
onKeyDown={(e) => {
|
}}
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
>
|
||||||
e.stopPropagation();
|
<FontAwesomeIcon icon={faEyeSlash} />
|
||||||
e.currentTarget
|
</button>
|
||||||
.closest(".group")
|
</div>
|
||||||
?.classList.remove("show-value");
|
<span className="group-[.show-value]:hidden">
|
||||||
}
|
{secretValue?.replace(/./g, "*")}
|
||||||
}}
|
<button
|
||||||
>
|
type="button"
|
||||||
<FontAwesomeIcon icon={faEyeSlash} />
|
className="ml-1 cursor-pointer"
|
||||||
</button>
|
onClick={(e) => {
|
||||||
</div>
|
e.currentTarget.closest(".group")?.classList.add("show-value");
|
||||||
<span className="group-[.show-value]:hidden">
|
}}
|
||||||
{secretValueHidden ? "******" : secretValue?.replace(/./g, "*")}
|
onKeyDown={(e) => {
|
||||||
<button
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
type="button"
|
|
||||||
className="ml-1 cursor-pointer"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.currentTarget
|
e.currentTarget
|
||||||
.closest(".group")
|
.closest(".group")
|
||||||
?.classList.add("show-value");
|
?.classList.add("show-value");
|
||||||
}}
|
}
|
||||||
onKeyDown={(e) => {
|
}}
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
>
|
||||||
e.currentTarget
|
<FontAwesomeIcon icon={faEye} />
|
||||||
.closest(".group")
|
</button>
|
||||||
?.classList.add("show-value");
|
</span>
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faEye} />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
|
|
||||||
>
|
|
||||||
<Tooltip content="Restore Secret Value">
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="Restore"
|
|
||||||
variant="outline_bg"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 rounded-md"
|
|
||||||
onClick={() => setValue("value", secretValue)}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faArrowRotateRight} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
<div
|
||||||
)}
|
className={`flex items-center justify-center ${version === secretVersion.length ? "hidden" : ""}`}
|
||||||
|
>
|
||||||
|
<Tooltip content="Restore Secret Value">
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Restore"
|
||||||
|
variant="outline_bg"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 rounded-md"
|
||||||
|
onClick={() => setValue("value", secretValue)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faArrowRotateRight} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
||||||
|
|||||||
+4
-20
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
/* eslint-disable simple-import-sort/imports */
|
/* eslint-disable simple-import-sort/imports */
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
@@ -45,8 +44,6 @@ import {
|
|||||||
SecretReferenceTree
|
SecretReferenceTree
|
||||||
} from "@app/components/secrets/SecretReferenceDetails";
|
} from "@app/components/secrets/SecretReferenceDetails";
|
||||||
|
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
|
||||||
import {
|
import {
|
||||||
FontAwesomeSpriteName,
|
FontAwesomeSpriteName,
|
||||||
formSchema,
|
formSchema,
|
||||||
@@ -102,14 +99,8 @@ export const SecretItem = memo(
|
|||||||
trigger,
|
trigger,
|
||||||
formState: { isDirty, isSubmitting, errors }
|
formState: { isDirty, isSubmitting, errors }
|
||||||
} = useForm<TFormSchema>({
|
} = useForm<TFormSchema>({
|
||||||
defaultValues: {
|
defaultValues: secret,
|
||||||
...secret,
|
values: secret,
|
||||||
value: secret.secretValueHidden ? "" : secret.value
|
|
||||||
},
|
|
||||||
values: {
|
|
||||||
...secret,
|
|
||||||
value: secret.secretValueHidden ? "" : secret.value
|
|
||||||
},
|
|
||||||
resolver: zodResolver(formSchema)
|
resolver: zodResolver(formSchema)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,7 +123,7 @@ export const SecretItem = memo(
|
|||||||
|
|
||||||
const isReadOnly =
|
const isReadOnly =
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -141,7 +132,7 @@ export const SecretItem = memo(
|
|||||||
})
|
})
|
||||||
) &&
|
) &&
|
||||||
permission.cannot(
|
permission.cannot(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -150,8 +141,6 @@ export const SecretItem = memo(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const { secretValueHidden } = secret;
|
|
||||||
|
|
||||||
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
|
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer: NodeJS.Timeout;
|
let timer: NodeJS.Timeout;
|
||||||
@@ -283,8 +272,6 @@ export const SecretItem = memo(
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : secretValueHidden ? (
|
|
||||||
<Blur tooltipText="You do not have permission to read the value of this secret." />
|
|
||||||
) : (
|
) : (
|
||||||
<Controller
|
<Controller
|
||||||
name="value"
|
name="value"
|
||||||
@@ -298,7 +285,6 @@ export const SecretItem = memo(
|
|||||||
environment={environment}
|
environment={environment}
|
||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
{...field}
|
{...field}
|
||||||
defaultValue={secretValueHidden ? "" : undefined}
|
|
||||||
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -307,7 +293,6 @@ export const SecretItem = memo(
|
|||||||
<div key="actions" className="flex h-8 flex-shrink-0 self-start transition-all">
|
<div key="actions" className="flex h-8 flex-shrink-0 self-start transition-all">
|
||||||
<Tooltip content="Copy secret">
|
<Tooltip content="Copy secret">
|
||||||
<IconButton
|
<IconButton
|
||||||
isDisabled={secret.secretValueHidden}
|
|
||||||
ariaLabel="copy-value"
|
ariaLabel="copy-value"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -515,7 +500,6 @@ export const SecretItem = memo(
|
|||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
<IconButton
|
<IconButton
|
||||||
isDisabled={secret.secretValueHidden}
|
|
||||||
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
|
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
size="md"
|
size="md"
|
||||||
|
|||||||
+7
-2
@@ -1,5 +1,4 @@
|
|||||||
import { FontAwesomeSymbol, Input, Tooltip } from "@app/components/v2";
|
import { FontAwesomeSymbol, Input, Tooltip } from "@app/components/v2";
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
|
||||||
|
|
||||||
import { FontAwesomeSpriteName } from "./SecretListView.utils";
|
import { FontAwesomeSpriteName } from "./SecretListView.utils";
|
||||||
|
|
||||||
@@ -35,7 +34,13 @@ export const SecretNoAccessListView = ({ count }: Props) => {
|
|||||||
className="w-full px-0 blur-sm placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent"
|
className="w-full px-0 blur-sm placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Blur />
|
<div
|
||||||
|
className="flex w-80 flex-grow items-center border-x border-mineshaft-600 py-1 pl-4 pr-2"
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<span className="blur">********</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+2
-17
@@ -20,7 +20,6 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
||||||
|
|
||||||
@@ -121,25 +120,11 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
|
|||||||
<Td className="border-r border-mineshaft-600">Value</Td>
|
<Td className="border-r border-mineshaft-600">Value</Td>
|
||||||
{isModified && (
|
{isModified && (
|
||||||
<Td className="border-r border-mineshaft-600">
|
<Td className="border-r border-mineshaft-600">
|
||||||
{preSecret?.secretValueHidden ? (
|
<SecretInput value={preSecret?.value} />
|
||||||
<Blur
|
|
||||||
className="w-min"
|
|
||||||
tooltipText="You do not have permission to read the value of this secret."
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<SecretInput value={preSecret?.value} />
|
|
||||||
)}
|
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
<Td>
|
<Td>
|
||||||
{postSecret?.secretValueHidden ? (
|
<SecretInput value={postSecret?.value} />
|
||||||
<Blur
|
|
||||||
className="w-min"
|
|
||||||
tooltipText="You do not have permission to read the value of this secret."
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<SecretInput value={postSecret?.value} />
|
|
||||||
)}
|
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
{Boolean(preSecret?.idOverride || postSecret?.idOverride) && (
|
{Boolean(preSecret?.idOverride || postSecret?.idOverride) && (
|
||||||
|
|||||||
Reference in New Issue
Block a user