mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 21:51:56 +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 (
|
||||
scopes: { environment: string; secretPath: string }[],
|
||||
permissions: ("read" | "write" | "readValue")[]
|
||||
permissions: ("read" | "write")[]
|
||||
) => {
|
||||
const projectKeyRes = await testServer.inject({
|
||||
method: "GET",
|
||||
@@ -139,7 +139,7 @@ describe("Service token secret ops", async () => {
|
||||
beforeAll(async () => {
|
||||
serviceToken = await createServiceToken(
|
||||
[{ secretPath: "/**", environment: seedData1.environment.slug }],
|
||||
["read", "write", "readValue"]
|
||||
["read", "write"]
|
||||
);
|
||||
|
||||
// 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 () => {
|
||||
const serviceToken = await createServiceToken(
|
||||
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
||||
["read", "readValue", "write"]
|
||||
["read", "write"]
|
||||
);
|
||||
const fetchSecrets = await testServer.inject({
|
||||
method: "GET",
|
||||
@@ -518,7 +518,7 @@ describe("Service token fail cases", async () => {
|
||||
test("Unauthorized secret environment access", async () => {
|
||||
const serviceToken = await createServiceToken(
|
||||
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
||||
["read", "readValue", "write"]
|
||||
["read", "write"]
|
||||
);
|
||||
const fetchSecrets = await testServer.inject({
|
||||
method: "GET",
|
||||
@@ -540,7 +540,7 @@ describe("Service token fail cases", async () => {
|
||||
test("Unauthorized write operation", async () => {
|
||||
const serviceToken = await createServiceToken(
|
||||
[{ secretPath: "/", environment: seedData1.environment.slug }],
|
||||
["read", "readValue"]
|
||||
["read"]
|
||||
);
|
||||
const writeSecrets = await testServer.inject({
|
||||
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 { 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 { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
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 { 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({
|
||||
method: "GET",
|
||||
url: "/:id",
|
||||
@@ -278,7 +291,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
.omit({ _id: true, environment: true, workspace: true, type: true, version: true })
|
||||
.extend({
|
||||
op: z.string(),
|
||||
tags: SanitizedTagSchema.array().optional(),
|
||||
tags: tagSchema,
|
||||
secretMetadata: ResourceMetadataSchema.nullish(),
|
||||
secret: z
|
||||
.object({
|
||||
@@ -297,7 +310,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
secretKey: z.string(),
|
||||
secretValue: z.string().optional(),
|
||||
secretComment: z.string().optional(),
|
||||
tags: SanitizedTagSchema.array().optional(),
|
||||
tags: tagSchema,
|
||||
secretMetadata: ResourceMetadataSchema.nullish()
|
||||
})
|
||||
.optional()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
@@ -9,7 +9,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
const AccessListEntrySchema = z
|
||||
.object({
|
||||
allowedActions: z.nativeEnum(ProjectPermissionSecretActions).array(),
|
||||
allowedActions: z.nativeEnum(ProjectPermissionActions).array(),
|
||||
id: z.string(),
|
||||
membershipId: z.string(),
|
||||
name: z.string()
|
||||
|
||||
@@ -22,11 +22,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secretVersions: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean()
|
||||
})
|
||||
.array()
|
||||
secretVersions: secretRawSchema.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -41,7 +37,6 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
||||
offset: req.query.offset,
|
||||
secretId: req.params.secretId
|
||||
});
|
||||
|
||||
return { secretVersions };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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 { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
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";
|
||||
|
||||
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
@@ -31,9 +31,12 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
secretVersions: secretRawSchema
|
||||
.omit({ _id: true, environment: true, workspace: true, type: true })
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretId: z.string(),
|
||||
tags: SanitizedTagSchema.array()
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
color: true
|
||||
}).array()
|
||||
})
|
||||
.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,
|
||||
id: req.params.secretSnapshotId
|
||||
});
|
||||
|
||||
return { secretSnapshot };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,14 +17,6 @@ export enum ProjectPermissionActions {
|
||||
Delete = "delete"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSecretActions {
|
||||
DescribeSecret = "read",
|
||||
ReadValue = "readValue",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionCmekActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
@@ -123,7 +115,7 @@ export type IdentityManagementSubjectFields = {
|
||||
|
||||
export type ProjectPermissionSet =
|
||||
| [
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionActions,
|
||||
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", [
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||
@@ -469,7 +460,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||
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."
|
||||
),
|
||||
conditions: SecretConditionV2Schema.describe(
|
||||
@@ -526,6 +517,7 @@ const buildAdminPermissionRules = () => {
|
||||
|
||||
// Admins get full access to everything
|
||||
[
|
||||
ProjectPermissionSub.Secrets,
|
||||
ProjectPermissionSub.SecretFolders,
|
||||
ProjectPermissionSub.SecretImports,
|
||||
ProjectPermissionSub.SecretApproval,
|
||||
@@ -558,21 +550,10 @@ const buildAdminPermissionRules = () => {
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionActions.Delete
|
||||
],
|
||||
el
|
||||
el as ProjectPermissionSub
|
||||
);
|
||||
});
|
||||
|
||||
can(
|
||||
[
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionSecretActions.Create,
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionSecretActions.Delete
|
||||
],
|
||||
ProjectPermissionSub.Secrets
|
||||
);
|
||||
|
||||
can(
|
||||
[
|
||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||
@@ -632,11 +613,10 @@ const buildMemberPermissionRules = () => {
|
||||
|
||||
can(
|
||||
[
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionSecretActions.Create,
|
||||
ProjectPermissionSecretActions.Delete
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionActions.Delete
|
||||
],
|
||||
ProjectPermissionSub.Secrets
|
||||
);
|
||||
@@ -808,8 +788,7 @@ export const projectMemberPermissions = buildMemberPermissionRules();
|
||||
const buildViewerPermissionRules = () => {
|
||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
|
||||
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
||||
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||
@@ -852,8 +831,6 @@ export const buildServiceTokenProjectPermission = (
|
||||
) => {
|
||||
const canWrite = permission.includes("write");
|
||||
const canRead = permission.includes("read");
|
||||
const canReadValue = permission.includes("readValue");
|
||||
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
scopes.forEach(({ secretPath, environment }) => {
|
||||
[ProjectPermissionSub.Secrets, ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretFolders].forEach(
|
||||
@@ -883,14 +860,6 @@ export const buildServiceTokenProjectPermission = (
|
||||
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
|
||||
}));
|
||||
|
||||
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
|
||||
|
||||
.map(({ subject, ...el }) => ({
|
||||
...el,
|
||||
// read permission is not needed anymore
|
||||
@@ -999,7 +958,6 @@ export const backfillPermissionV1SchemaToV2Schema = (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-error this is valid ts
|
||||
secretImportPolicies,
|
||||
secretPolicies,
|
||||
dynamicSecretPolicies,
|
||||
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
||||
);
|
||||
|
||||
@@ -58,7 +58,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-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 { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
||||
@@ -88,12 +88,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
||||
secretDAL: TSecretDALFactory;
|
||||
secretTagDAL: Pick<
|
||||
TSecretTagDALFactory,
|
||||
| "findManyTagsById"
|
||||
| "saveTagsToSecret"
|
||||
| "deleteTagsManySecret"
|
||||
| "saveTagsToSecretV2"
|
||||
| "deleteTagsToSecretV2"
|
||||
| "find"
|
||||
"findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret" | "saveTagsToSecretV2" | "deleteTagsToSecretV2"
|
||||
>;
|
||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||
@@ -111,7 +106,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
|
||||
secretV2BridgeDAL: Pick<
|
||||
TSecretV2BridgeDALFactory,
|
||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany" | "find"
|
||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany"
|
||||
>;
|
||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
@@ -919,7 +914,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
|
||||
@@ -1006,7 +1001,6 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
: keyName2BlindIndex[secretName];
|
||||
// add tags
|
||||
if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds;
|
||||
|
||||
return {
|
||||
...latestSecretVersions[secretId],
|
||||
...el,
|
||||
@@ -1369,9 +1363,9 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
const tagsGroupById = groupBy(tags, (i) => i.id);
|
||||
|
||||
commits.forEach((commit) => {
|
||||
let action = ProjectPermissionSecretActions.Create;
|
||||
if (commit.op === SecretOperations.Update) action = ProjectPermissionSecretActions.Edit;
|
||||
if (commit.op === SecretOperations.Delete) action = ProjectPermissionSecretActions.Delete;
|
||||
let action = ProjectPermissionActions.Create;
|
||||
if (commit.op === SecretOperations.Update) action = ProjectPermissionActions.Edit;
|
||||
if (commit.op === SecretOperations.Delete) action = ProjectPermissionActions.Delete;
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
action,
|
||||
|
||||
@@ -265,7 +265,6 @@ export const secretReplicationServiceFactory = ({
|
||||
folderDAL,
|
||||
secretImportDAL,
|
||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||
viewSecretValue: true,
|
||||
hasSecretAccess: () => true
|
||||
});
|
||||
// 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 { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "../permission/project-permission";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TSecretRotationDALFactory } from "./secret-rotation-dal";
|
||||
import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
|
||||
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
|
||||
@@ -110,7 +106,7 @@ export const secretRotationServiceFactory = ({
|
||||
});
|
||||
}
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionActions.Edit,
|
||||
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 { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "../permission/project-permission";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import {
|
||||
TGetSnapshotDataDTO,
|
||||
TProjectSnapshotCountDTO,
|
||||
@@ -38,7 +34,6 @@ import { TSnapshotFolderDALFactory } from "./snapshot-folder-dal";
|
||||
import { TSnapshotSecretDALFactory } from "./snapshot-secret-dal";
|
||||
import { TSnapshotSecretV2DALFactory } from "./snapshot-secret-v2-dal";
|
||||
import { getFullFolderPath } from "./snapshot-service-fns";
|
||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "@app/services/secret/secret-fns";
|
||||
|
||||
type TSecretSnapshotServiceFactoryDep = {
|
||||
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.
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionActions.Read,
|
||||
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.
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
@@ -166,7 +161,6 @@ export const secretSnapshotServiceFactory = ({
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
|
||||
const shouldUseBridge = snapshot.projectVersion === 3;
|
||||
let snapshotDetails;
|
||||
if (shouldUseBridge) {
|
||||
@@ -175,110 +169,68 @@ export const secretSnapshotServiceFactory = ({
|
||||
projectId: snapshot.projectId
|
||||
});
|
||||
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotV2DataById(id);
|
||||
|
||||
const fullFolderPath = await getFullFolderPath({
|
||||
folderDAL,
|
||||
folderId: encryptedSnapshotDetails.folderId,
|
||||
envId: encryptedSnapshotDetails.environment.id
|
||||
});
|
||||
|
||||
snapshotDetails = {
|
||||
...encryptedSnapshotDetails,
|
||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => {
|
||||
const canReadValue = permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: encryptedSnapshotDetails.environment.slug,
|
||||
secretPath: fullFolderPath,
|
||||
secretName: el.key,
|
||||
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
||||
})
|
||||
);
|
||||
|
||||
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()
|
||||
: ""
|
||||
};
|
||||
})
|
||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
|
||||
...el,
|
||||
secretKey: el.key,
|
||||
secretValue: el.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||
: "",
|
||||
secretComment: el.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||
: ""
|
||||
}))
|
||||
};
|
||||
} else {
|
||||
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);
|
||||
if (!botKey)
|
||||
throw new NotFoundError({ message: `Project bot key not found for project with ID '${snapshot.projectId}'` });
|
||||
snapshotDetails = {
|
||||
...encryptedSnapshotDetails,
|
||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => {
|
||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
|
||||
...el,
|
||||
secretKey: decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: el.secretKeyCiphertext,
|
||||
iv: el.secretKeyIV,
|
||||
tag: el.secretKeyTag,
|
||||
key: botKey
|
||||
});
|
||||
|
||||
const canReadValue = permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: encryptedSnapshotDetails.environment.slug,
|
||||
secretPath: fullFolderPath,
|
||||
secretName: secretKey,
|
||||
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
||||
})
|
||||
);
|
||||
|
||||
let secretValue = "";
|
||||
|
||||
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
|
||||
})
|
||||
: ""
|
||||
};
|
||||
})
|
||||
}),
|
||||
secretValue: decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: el.secretValueCiphertext,
|
||||
iv: el.secretValueIV,
|
||||
tag: el.secretValueTag,
|
||||
key: botKey
|
||||
}),
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -666,7 +666,6 @@ export const SECRETS = {
|
||||
secretPath: "The path of the secret to attach tags to.",
|
||||
type: "The type of the secret to attach tags to. (shared/personal)",
|
||||
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.",
|
||||
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.",
|
||||
environment: "The slug of the environment 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.",
|
||||
tagSlugs: "The comma separated tag slugs to filter secrets.",
|
||||
metadataFilter:
|
||||
@@ -719,7 +717,6 @@ export const RAW_SECRETS = {
|
||||
secretPath: "The path of the secret to get.",
|
||||
version: "The version 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."
|
||||
},
|
||||
UPDATE: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
export class DatabaseError extends Error {
|
||||
name: string;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
ProjectRolesSchema,
|
||||
ProjectsSchema,
|
||||
SecretApprovalPoliciesSchema,
|
||||
SecretTagsSchema,
|
||||
UsersSchema
|
||||
} from "@app/db/schemas";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
@@ -233,11 +232,3 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
|
||||
kmsCertificateKeyId: 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 { 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 {
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
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 { getUserAgentType } from "@app/server/plugins/audit-log";
|
||||
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 { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||
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(),
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretPath: z.string().optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
color: true
|
||||
})
|
||||
.extend({ name: z.string() })
|
||||
.array()
|
||||
.optional()
|
||||
})
|
||||
.array()
|
||||
.optional(),
|
||||
@@ -289,7 +294,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||
secrets = await server.services.secret.getSecretsRawMultiEnv({
|
||||
viewSecretValue: true,
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
@@ -389,7 +393,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
.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(),
|
||||
viewSecretValue: booleanSchema.default(true),
|
||||
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
||||
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
||||
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
||||
@@ -407,10 +410,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
dynamicSecrets: SanitizedDynamicSecretSchema.array().optional(),
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretPath: z.string().optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
color: true
|
||||
})
|
||||
.extend({ name: z.string() })
|
||||
.array()
|
||||
.optional()
|
||||
})
|
||||
.array()
|
||||
.optional(),
|
||||
@@ -592,25 +601,23 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
});
|
||||
|
||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||
secrets = (
|
||||
await server.services.secret.getSecretsRaw({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
viewSecretValue: req.query.viewSecretValue,
|
||||
throwOnMissingReadValuePermission: false,
|
||||
actorOrgId: req.permission.orgId,
|
||||
environment,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId,
|
||||
path: secretPath,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
search,
|
||||
limit: remainingLimit,
|
||||
offset: adjustedOffset,
|
||||
tagSlugs: tags
|
||||
})
|
||||
).secrets;
|
||||
const secretsRaw = await server.services.secret.getSecretsRaw({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
environment,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId,
|
||||
path: secretPath,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
search,
|
||||
limit: remainingLimit,
|
||||
offset: adjustedOffset,
|
||||
tagSlugs: tags
|
||||
});
|
||||
|
||||
secrets = secretsRaw.secrets;
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
projectId,
|
||||
@@ -689,10 +696,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
.optional(),
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretPath: z.string().optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
color: true
|
||||
})
|
||||
.extend({ name: z.string() })
|
||||
.array()
|
||||
.optional()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
@@ -736,7 +749,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
const secrets = await server.services.secret.getSecretsRawByFolderMappings(
|
||||
{
|
||||
filterByAction: ProjectPermissionSecretActions.DescribeSecret,
|
||||
projectId,
|
||||
folderMappings,
|
||||
filters: {
|
||||
@@ -850,17 +862,22 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
projectId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
keys: z.string().trim().transform(decodeURIComponent),
|
||||
viewSecretValue: booleanSchema.default(false)
|
||||
keys: z.string().trim().transform(decodeURIComponent)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretPath: z.string().optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
color: true
|
||||
})
|
||||
.extend({ name: z.string() })
|
||||
.array()
|
||||
.optional()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
@@ -869,7 +886,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
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())) ?? [];
|
||||
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,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
viewSecretValue,
|
||||
environment,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId,
|
||||
|
||||
@@ -94,7 +94,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
|
||||
iv: z.string().trim(),
|
||||
tag: z.string().trim(),
|
||||
expiresIn: z.number().nullable(),
|
||||
permissions: z.enum(["read", "write", "readValue"]).array()
|
||||
permissions: z.enum(["read", "write"]).array()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import picomatch from "picomatch";
|
||||
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 { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
|
||||
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 { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||
|
||||
import { SanitizedTagSchema, secretRawSchema } from "../sanitizedSchemas";
|
||||
import { secretRawSchema } from "../sanitizedSchemas";
|
||||
|
||||
const SecretReferenceNode = z.object({
|
||||
key: z.string(),
|
||||
@@ -25,14 +31,6 @@ const SecretReferenceNode = z.object({
|
||||
environment: 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[] };
|
||||
|
||||
const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReferenceNode.extend({
|
||||
@@ -77,9 +75,17 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
||||
tags: SanitizedTagSchema.array()
|
||||
})
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||
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: {
|
||||
200: z.object({
|
||||
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),
|
||||
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
||||
viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.LIST.viewSecretValue),
|
||||
expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.LIST.expand),
|
||||
recursive: convertStringBoolean().describe(RAW_SECRETS.LIST.recursive),
|
||||
include_imports: convertStringBoolean().describe(RAW_SECRETS.LIST.includeImports),
|
||||
expandSecretReferences: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.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
|
||||
.string()
|
||||
.describe(RAW_SECRETS.LIST.tagSlugs)
|
||||
@@ -251,9 +274,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretPath: z.string().optional(),
|
||||
secretValueHidden: z.boolean(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
color: true
|
||||
})
|
||||
.extend({ name: z.string() })
|
||||
.array()
|
||||
.optional()
|
||||
})
|
||||
.array(),
|
||||
imports: z
|
||||
@@ -264,7 +293,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
secrets: secretRawSchema
|
||||
.omit({ createdAt: true, updatedAt: true })
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretMetadata: ResourceMetadataSchema.optional()
|
||||
})
|
||||
.array()
|
||||
@@ -314,7 +342,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
expandSecretReferences: req.query.expandSecretReferences,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId: workspaceId,
|
||||
viewSecretValue: req.query.viewSecretValue,
|
||||
path: secretPath,
|
||||
metadataFilter: req.query.metadataFilter,
|
||||
includeImports: req.query.include_imports,
|
||||
@@ -349,7 +376,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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),
|
||||
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
||||
viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.GET.viewSecretValue),
|
||||
expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.GET.expand),
|
||||
include_imports: convertStringBoolean().describe(RAW_SECRETS.GET.includeImports)
|
||||
expandSecretReferences: z
|
||||
.enum(["true", "false"])
|
||||
.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: {
|
||||
200: z.object({
|
||||
secret: secretRawSchema.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
tags: SanitizedTagSchema.array().optional(),
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
color: true
|
||||
})
|
||||
.extend({ name: z.string() })
|
||||
.array()
|
||||
.optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional()
|
||||
})
|
||||
})
|
||||
@@ -417,7 +456,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
expandSecretReferences: req.query.expandSecretReferences,
|
||||
environment,
|
||||
projectId: workspaceId,
|
||||
viewSecretValue: req.query.viewSecretValue,
|
||||
projectSlug: workspaceSlug,
|
||||
path: secretPath,
|
||||
secretName: req.params.secretName,
|
||||
@@ -624,9 +662,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
response: {
|
||||
200: z.union([
|
||||
z.object({
|
||||
secret: secretRawSchema.extend({
|
||||
secretValueHidden: z.boolean()
|
||||
})
|
||||
secret: secretRawSchema
|
||||
}),
|
||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||
])
|
||||
@@ -722,9 +758,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
response: {
|
||||
200: z.union([
|
||||
z.object({
|
||||
secret: secretRawSchema.extend({
|
||||
secretValueHidden: z.boolean()
|
||||
})
|
||||
secret: secretRawSchema
|
||||
}),
|
||||
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) {
|
||||
return { approval: secretOperation.approval };
|
||||
}
|
||||
|
||||
const { secret } = secretOperation;
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
@@ -809,7 +842,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
workspace: z.string(),
|
||||
environment: z.string(),
|
||||
secretPath: z.string().optional(),
|
||||
tags: SanitizedTagSchema.array()
|
||||
tags: SecretTagsSchema.pick({
|
||||
id: true,
|
||||
slug: true,
|
||||
color: true
|
||||
})
|
||||
.extend({ name: z.string() })
|
||||
.array()
|
||||
})
|
||||
.array(),
|
||||
imports: z
|
||||
@@ -905,7 +944,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
||||
version: z.coerce.number().optional(),
|
||||
include_imports: convertStringBoolean()
|
||||
include_imports: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.transform((value) => value === "true")
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@@ -1176,7 +1218,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||
z.object({
|
||||
secretValueHidden: z.boolean(),
|
||||
_id: z.string(),
|
||||
workspace: z.string(),
|
||||
environment: z.string()
|
||||
@@ -1346,12 +1387,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
response: {
|
||||
200: z.union([
|
||||
z.object({
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
||||
_id: z.string(),
|
||||
secretValueHidden: z.boolean(),
|
||||
workspace: z.string(),
|
||||
environment: z.string()
|
||||
})
|
||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||
z.object({
|
||||
_id: z.string(),
|
||||
workspace: z.string(),
|
||||
environment: z.string()
|
||||
})
|
||||
)
|
||||
}),
|
||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||
])
|
||||
@@ -1663,7 +1705,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
response: {
|
||||
200: z.union([
|
||||
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")
|
||||
])
|
||||
@@ -1778,11 +1820,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
response: {
|
||||
200: z.union([
|
||||
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")
|
||||
])
|
||||
@@ -2044,7 +2082,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
response: {
|
||||
200: z.union([
|
||||
z.object({
|
||||
secrets: secretRawSchema.extend({ secretValueHidden: z.boolean() }).array()
|
||||
secrets: secretRawSchema.array()
|
||||
}),
|
||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||
])
|
||||
@@ -2166,11 +2204,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
response: {
|
||||
200: z.union([
|
||||
z.object({
|
||||
secrets: secretRawSchema
|
||||
.extend({
|
||||
secretValueHidden: z.boolean()
|
||||
})
|
||||
.array()
|
||||
secrets: secretRawSchema.array()
|
||||
}),
|
||||
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">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "find">;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create" | "find">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
||||
|
||||
@@ -27,9 +27,9 @@ export type TExternalMigrationQueueFactoryDep = {
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "find">;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create" | "find">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findOne" | "findById">;
|
||||
|
||||
@@ -68,8 +68,7 @@ const getIntegrationSecretsV2 = async (
|
||||
secretDAL: secretV2BridgeDAL,
|
||||
secretImportDAL,
|
||||
secretImports,
|
||||
hasSecretAccess: () => true,
|
||||
viewSecretValue: true
|
||||
hasSecretAccess: () => true
|
||||
});
|
||||
|
||||
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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { NotFoundError } from "@app/lib/errors";
|
||||
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(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: sourceEnvironment,
|
||||
secretPath
|
||||
@@ -179,7 +175,7 @@ export const integrationServiceFactory = ({
|
||||
|
||||
if (environment || secretPath) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: newEnvironment,
|
||||
secretPath: newSecretPath
|
||||
|
||||
@@ -11,11 +11,7 @@ import {
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
|
||||
import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types";
|
||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||
@@ -751,10 +747,7 @@ export const projectServiceFactory = ({
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionSub.Secrets
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||
|
||||
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 { TSecretDALFactory } from "../secret/secret-dal";
|
||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||
import { TSecretImportDALFactory } from "./secret-import-dal";
|
||||
@@ -33,12 +32,6 @@ type TSecretImportSecretsV2 = {
|
||||
folderId: string | undefined;
|
||||
importFolderId: string;
|
||||
secrets: (TSecretsV2 & {
|
||||
secretTags: {
|
||||
slug: string;
|
||||
name: string;
|
||||
color?: string | null;
|
||||
id: string;
|
||||
}[];
|
||||
workspace: string;
|
||||
environment: string;
|
||||
_id: string;
|
||||
@@ -46,7 +39,6 @@ type TSecretImportSecretsV2 = {
|
||||
// akhilmhdh: yes i know you can put ?.
|
||||
// But for somereason ts consider ? and undefined explicit as different just ts things
|
||||
secretValue: string;
|
||||
secretValueHidden: boolean;
|
||||
secretComment: string;
|
||||
secretMetadata?: ResourceMetadataDTO;
|
||||
})[];
|
||||
@@ -158,14 +150,12 @@ export const fnSecretsV2FromImports = async ({
|
||||
secretImportDAL,
|
||||
decryptor,
|
||||
expandSecretReferences,
|
||||
hasSecretAccess,
|
||||
viewSecretValue
|
||||
hasSecretAccess
|
||||
}: {
|
||||
secretImports: (Omit<TSecretImports, "importEnv"> & {
|
||||
importEnv: { id: string; slug: string; name: string };
|
||||
})[];
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findByManySecretPath">;
|
||||
viewSecretValue: boolean;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "find">;
|
||||
secretImportDAL: Pick<TSecretImportDALFactory, "findByFolderIds">;
|
||||
decryptor: (value?: Buffer | null) => string;
|
||||
@@ -178,14 +168,9 @@ export const fnSecretsV2FromImports = async ({
|
||||
hasSecretAccess: (environment: string, secretPath: string, secretName: string, secretTagSlugs: string[]) => boolean;
|
||||
}) => {
|
||||
const cyclicDetector = new Set();
|
||||
const stack: {
|
||||
secretImports: typeof rootSecretImports;
|
||||
depth: number;
|
||||
parentImportedSecrets: (TSecretsV2 & {
|
||||
secretValueHidden: boolean;
|
||||
secretTags: { slug: string; name: string; id: string; color?: string | null }[];
|
||||
})[];
|
||||
}[] = [{ secretImports: rootSecretImports, depth: 0, parentImportedSecrets: [] }];
|
||||
const stack: { secretImports: typeof rootSecretImports; depth: number; parentImportedSecrets: TSecretsV2[] }[] = [
|
||||
{ secretImports: rootSecretImports, depth: 0, parentImportedSecrets: [] }
|
||||
];
|
||||
|
||||
const processedImports: TSecretImportSecretsV2[] = [];
|
||||
|
||||
@@ -244,9 +229,7 @@ export const fnSecretsV2FromImports = async ({
|
||||
.map((item) => ({
|
||||
...item,
|
||||
secretKey: item.key,
|
||||
secretValue: viewSecretValue ? decryptor(item.encryptedValue) : INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
||||
secretValueHidden: !viewSecretValue,
|
||||
secretTags: item.tags,
|
||||
secretValue: decryptor(item.encryptedValue),
|
||||
secretComment: decryptor(item.encryptedComment),
|
||||
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.
|
||||
@@ -284,8 +267,6 @@ export const fnSecretsV2FromImports = async ({
|
||||
processedImport.secrets = unique(processedImport.secrets, (i) => i.key);
|
||||
return Promise.allSettled(
|
||||
processedImport.secrets.map(async (decryptedSecret, index) => {
|
||||
if (decryptedSecret.secretValueHidden) return;
|
||||
|
||||
const expandedSecretValue = await expandSecretReferences({
|
||||
value: decryptedSecret.secretValue,
|
||||
secretPath: processedImport.secretPath,
|
||||
|
||||
@@ -5,11 +5,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { getReplicationFolderName } from "@app/ee/services/secret-replication/secret-replication-service";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
@@ -94,7 +90,7 @@ export const secretImportServiceFactory = ({
|
||||
|
||||
// check if user has permission to import from target path
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: data.environment,
|
||||
secretPath: data.path
|
||||
@@ -406,7 +402,7 @@ export const secretImportServiceFactory = ({
|
||||
|
||||
// check if user has permission to import from target path
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: secretImportDoc.importEnv.slug,
|
||||
secretPath: secretImportDoc.importPath
|
||||
@@ -600,7 +596,7 @@ export const secretImportServiceFactory = ({
|
||||
const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false });
|
||||
const allowedImports = secretImports.filter((el) =>
|
||||
permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: el.importEnv.slug,
|
||||
secretPath: el.importPath
|
||||
@@ -646,13 +642,12 @@ export const secretImportServiceFactory = ({
|
||||
const importedSecrets = await fnSecretsV2FromImports({
|
||||
secretImports,
|
||||
folderDAL,
|
||||
viewSecretValue: true,
|
||||
secretDAL: secretV2BridgeDAL,
|
||||
secretImportDAL,
|
||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
||||
permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: expandEnvironment,
|
||||
secretPath: expandSecretPath,
|
||||
@@ -672,7 +667,7 @@ export const secretImportServiceFactory = ({
|
||||
|
||||
const allowedImports = secretImports.filter((el) =>
|
||||
permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: el.importEnv.slug,
|
||||
secretPath: el.importPath
|
||||
@@ -688,10 +683,7 @@ export const secretImportServiceFactory = ({
|
||||
return importedSecrets.map((el) => ({
|
||||
...el,
|
||||
secrets: el.secrets.map((encryptedSecret) =>
|
||||
decryptSecretRaw(
|
||||
{ ...encryptedSecret, workspace: projectId, environment, secretPath, secretValueHidden: false },
|
||||
botKey
|
||||
)
|
||||
decryptSecretRaw({ ...encryptedSecret, workspace: projectId, environment, secretPath }, botKey)
|
||||
)
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -249,8 +249,7 @@ export const secretSyncQueueFactory = ({
|
||||
expandSecretReferences,
|
||||
secretImportDAL,
|
||||
secretImports,
|
||||
hasSecretAccess: () => true,
|
||||
viewSecretValue: true
|
||||
hasSecretAccess: () => true
|
||||
});
|
||||
|
||||
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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSecretSyncActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
@@ -179,7 +179,7 @@ export const secretSyncServiceFactory = ({
|
||||
);
|
||||
|
||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath
|
||||
@@ -270,7 +270,7 @@ export const secretSyncServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Must specify both source environment and secret path" });
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: updatedEnvironment,
|
||||
secretPath: updatedSecretPath
|
||||
|
||||
@@ -47,7 +47,6 @@ export const secretTagDALFactory = (db: TDbClient) => {
|
||||
throw new DatabaseError({ error, name: "Find all by ids" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretTagOrm,
|
||||
saveTagsToSecret: secretJnTagOrm.insertMany,
|
||||
|
||||
@@ -7,7 +7,6 @@ import { logger } from "@app/lib/logger";
|
||||
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
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 { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
|
||||
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
|
||||
}))
|
||||
);
|
||||
|
||||
const secretVersions = await secretVersionDAL.insertMany(
|
||||
sanitizedInputSecrets.map((el) => ({
|
||||
...el,
|
||||
@@ -139,7 +137,6 @@ export const fnSecretBulkInsert = async ({
|
||||
if (newSecretTags.length) {
|
||||
const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx);
|
||||
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
|
||||
|
||||
const newSecretVersionTags = secTags.flatMap(({ secrets_v2Id, secret_tagsId }) => ({
|
||||
[`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id,
|
||||
[`${TableName.SecretTag}Id` as const]: secret_tagsId
|
||||
@@ -148,16 +145,7 @@ export const fnSecretBulkInsert = async ({
|
||||
await secretVersionTagDAL.insertMany(newSecretVersionTags, tx);
|
||||
}
|
||||
|
||||
const secretsWithTags = await secretDAL.find(
|
||||
{
|
||||
$in: {
|
||||
[`${TableName.SecretV2}.id` as "id"]: newSecrets.map((s) => s.id)
|
||||
}
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
return secretsWithTags.map((secret) => ({ ...secret, _id: secret.id }));
|
||||
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
||||
};
|
||||
|
||||
export const fnSecretBulkUpdate = async ({
|
||||
@@ -295,15 +283,7 @@ export const fnSecretBulkUpdate = async ({
|
||||
tx
|
||||
);
|
||||
|
||||
const secretsWithTags = await secretDAL.find(
|
||||
{
|
||||
$in: {
|
||||
[`${TableName.SecretV2}.id` as "id"]: newSecrets.map((s) => s.id)
|
||||
}
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
return secretsWithTags.map((secret) => ({ ...secret, _id: secret.id }));
|
||||
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
|
||||
};
|
||||
|
||||
export const fnSecretBulkDelete = async ({
|
||||
@@ -536,7 +516,7 @@ export const expandSecretReferencesFactory = ({
|
||||
const referredValue = await fetchSecret(environment, secretPath, secretKey);
|
||||
if (!canExpandValue(environment, secretPath, secretKey, referredValue.tags))
|
||||
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);
|
||||
@@ -555,7 +535,7 @@ export const expandSecretReferencesFactory = ({
|
||||
const referedValue = await fetchSecret(secretReferenceEnvironment, secretReferencePath, secretReferenceKey);
|
||||
if (!canExpandValue(secretReferenceEnvironment, secretReferencePath, secretReferenceKey, referedValue.tags))
|
||||
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);
|
||||
@@ -643,13 +623,13 @@ export const reshapeBridgeSecret = (
|
||||
name: string;
|
||||
}[];
|
||||
secretMetadata?: ResourceMetadataDTO;
|
||||
},
|
||||
secretValueHidden: boolean
|
||||
}
|
||||
) => ({
|
||||
secretKey: secret.key,
|
||||
secretPath,
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
secretValue: secret.value || "",
|
||||
secretComment: secret.comment || "",
|
||||
version: secret.version,
|
||||
type: secret.type,
|
||||
@@ -663,15 +643,5 @@ export const reshapeBridgeSecret = (
|
||||
metadata: secret.metadata,
|
||||
secretMetadata: secret.secretMetadata,
|
||||
createdAt: secret.createdAt,
|
||||
updatedAt: secret.updatedAt,
|
||||
|
||||
...(secretValueHidden
|
||||
? {
|
||||
secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
||||
secretValueHidden: true
|
||||
}
|
||||
: {
|
||||
secretValue: secret.value || "",
|
||||
secretValueHidden: false
|
||||
})
|
||||
updatedAt: secret.updatedAt
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
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 { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||
@@ -37,8 +36,6 @@ export type TGetSecretsDTO = {
|
||||
includeImports?: boolean;
|
||||
recursive?: boolean;
|
||||
tagSlugs?: string[];
|
||||
viewSecretValue: boolean;
|
||||
throwOnMissingReadValuePermission?: boolean;
|
||||
metadataFilter?: {
|
||||
key?: string;
|
||||
value?: string;
|
||||
@@ -51,11 +48,6 @@ export type TGetSecretsDTO = {
|
||||
keys?: string[];
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetSecretsMissingReadValuePermissionDTO = Omit<
|
||||
TGetSecretsDTO,
|
||||
"viewSecretValue" | "recursive" | "expandSecretReferences"
|
||||
>;
|
||||
|
||||
export type TGetASecretDTO = {
|
||||
secretName: string;
|
||||
path: string;
|
||||
@@ -65,7 +57,6 @@ export type TGetASecretDTO = {
|
||||
includeImports?: boolean;
|
||||
version?: number;
|
||||
projectId: string;
|
||||
viewSecretValue: boolean;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TCreateSecretDTO = TProjectPermission & {
|
||||
@@ -173,9 +164,9 @@ export type TFnSecretBulkInsert = {
|
||||
}
|
||||
>;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "find">;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">;
|
||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "find">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2">;
|
||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
};
|
||||
|
||||
@@ -197,9 +188,9 @@ export type TFnSecretBulkUpdate = {
|
||||
data: TRequireReferenceIfValue & { tags?: string[]; secretMetadata?: ResourceMetadataDTO };
|
||||
}[];
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences" | "find">;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">;
|
||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2" | "find">;
|
||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2">;
|
||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
tx?: Knex;
|
||||
};
|
||||
@@ -341,5 +332,4 @@ export type TGetSecretsRawByFolderMappingsDTO = {
|
||||
folderMappings: { folderId: string; path: string; environment: string }[];
|
||||
userId: string;
|
||||
filters: TFindSecretsByFolderIdsFilter;
|
||||
filterByAction?: ProjectPermissionSecretActions;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
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 { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueName } from "@app/queue";
|
||||
|
||||
@@ -12,58 +12,6 @@ export type TSecretVersionV2DALFactory = ReturnType<typeof secretVersionV2Bridge
|
||||
export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
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
|
||||
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
||||
try {
|
||||
@@ -176,7 +124,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => {
|
||||
pruneExcessVersions,
|
||||
findLatestVersionMany,
|
||||
bulkUpdate,
|
||||
findLatestVersionByFolderId,
|
||||
findBySecretId
|
||||
findLatestVersionByFolderId
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
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)
|
||||
@@ -485,7 +443,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
||||
upsertSecretReferences,
|
||||
findReferencedSecretReferences,
|
||||
findAllProjectSecretValues,
|
||||
pruneSecretReminders,
|
||||
findManySecretsWithTags
|
||||
pruneSecretReminders
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
TSecrets
|
||||
} from "@app/db/schemas";
|
||||
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 {
|
||||
buildSecretBlindIndexFromName,
|
||||
@@ -51,8 +51,6 @@ import {
|
||||
TUpdateManySecretsRawFnFactory
|
||||
} from "./secret-types";
|
||||
|
||||
export const INFISICAL_SECRET_VALUE_HIDDEN_MASK = "<hidden-by-infisical>";
|
||||
|
||||
export const generateSecretBlindIndexBySalt = async (secretName: string, secretBlindIndexDoc: TSecretBlindIndexes) => {
|
||||
const appCfg = getConfig();
|
||||
const secretBlindIndex = await buildSecretBlindIndexFromName({
|
||||
@@ -192,7 +190,7 @@ export const recursivelyGetSecretPaths = ({
|
||||
const allowedPaths = paths.filter(
|
||||
(folder) =>
|
||||
permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath: folder.path
|
||||
@@ -346,7 +344,6 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
|
||||
|
||||
export const decryptSecretRaw = (
|
||||
secret: TSecrets & {
|
||||
secretValueHidden: boolean;
|
||||
workspace: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
@@ -365,14 +362,12 @@ export const decryptSecretRaw = (
|
||||
key
|
||||
});
|
||||
|
||||
const secretValue = !secret.secretValueHidden
|
||||
? decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretValueCiphertext,
|
||||
iv: secret.secretValueIV,
|
||||
tag: secret.secretValueTag,
|
||||
key
|
||||
})
|
||||
: INFISICAL_SECRET_VALUE_HIDDEN_MASK;
|
||||
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretValueCiphertext,
|
||||
iv: secret.secretValueIV,
|
||||
tag: secret.secretValueTag,
|
||||
key
|
||||
});
|
||||
|
||||
let secretComment = "";
|
||||
|
||||
@@ -390,7 +385,6 @@ export const decryptSecretRaw = (
|
||||
secretPath: secret.secretPath,
|
||||
workspace: secret.workspace,
|
||||
environment: secret.environment,
|
||||
secretValueHidden: secret.secretValueHidden,
|
||||
secretValue,
|
||||
secretComment,
|
||||
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,
|
||||
secretImportDAL,
|
||||
secretImports,
|
||||
hasSecretAccess: () => true,
|
||||
viewSecretValue: true
|
||||
hasSecretAccess: () => true
|
||||
});
|
||||
|
||||
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
ActionProjectType,
|
||||
ProjectMembershipRole,
|
||||
ProjectUpgradeStatus,
|
||||
ProjectVersion,
|
||||
SecretEncryptionAlgo,
|
||||
SecretKeyEncoding,
|
||||
SecretsSchema,
|
||||
@@ -14,11 +13,7 @@ import {
|
||||
} from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
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 { 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 { TSecretDALFactory } from "./secret-dal";
|
||||
import {
|
||||
conditionallyHideSecretValue,
|
||||
decryptSecretRaw,
|
||||
fnSecretBlindIndexCheck,
|
||||
fnSecretBulkDelete,
|
||||
@@ -101,7 +95,7 @@ type TSecretServiceFactoryDep = {
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
folderDAL: Pick<
|
||||
TSecretFolderDALFactory,
|
||||
"findBySecretPath" | "updateById" | "findById" | "findByManySecretPath" | "find" | "findSecretPathByFolderIds"
|
||||
"findBySecretPath" | "updateById" | "findById" | "findByManySecretPath" | "find"
|
||||
>;
|
||||
secretV2BridgeService: TSecretV2BridgeServiceFactory;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
@@ -210,7 +204,7 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Create,
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
@@ -328,7 +322,7 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
@@ -450,22 +444,7 @@ export const secretServiceFactory = ({
|
||||
environmentSlug: folder.environment.slug
|
||||
});
|
||||
}
|
||||
|
||||
const secretValueHidden = !permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath: path
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
...updatedSecret[0],
|
||||
...conditionallyHideSecretValue(secretValueHidden, updatedSecret[0]),
|
||||
workspace: projectId,
|
||||
environment,
|
||||
secretPath: path
|
||||
};
|
||||
return { ...updatedSecret[0], workspace: projectId, environment, secretPath: path };
|
||||
};
|
||||
|
||||
const deleteSecret = async ({
|
||||
@@ -488,7 +467,7 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Delete,
|
||||
ProjectPermissionActions.Delete,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
@@ -561,19 +540,7 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const secretValueHidden = !permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
return {
|
||||
...deletedSecret[0],
|
||||
...conditionallyHideSecretValue(secretValueHidden, deletedSecret[0]),
|
||||
_id: deletedSecret[0].id,
|
||||
workspace: projectId,
|
||||
environment,
|
||||
secretPath: path
|
||||
};
|
||||
return { ...deletedSecret[0], _id: deletedSecret[0].id, workspace: projectId, environment, secretPath: path };
|
||||
};
|
||||
|
||||
const getSecrets = async ({
|
||||
@@ -622,7 +589,7 @@ export const secretServiceFactory = ({
|
||||
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
||||
} else {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
@@ -647,7 +614,7 @@ export const secretServiceFactory = ({
|
||||
actor === ActorType.SERVICE
|
||||
? true
|
||||
: permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: importEnv.slug,
|
||||
secretPath: importPath
|
||||
@@ -704,7 +671,7 @@ export const secretServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||
@@ -754,7 +721,7 @@ export const secretServiceFactory = ({
|
||||
actor === ActorType.SERVICE
|
||||
? true
|
||||
: permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: importEnv.slug,
|
||||
secretPath: importPath
|
||||
@@ -772,7 +739,6 @@ export const secretServiceFactory = ({
|
||||
if (secretBlindIndex === importedSecrets[i].secrets[j].secretBlindIndex) {
|
||||
return {
|
||||
...importedSecrets[i].secrets[j],
|
||||
secretValueHidden: false,
|
||||
workspace: projectId,
|
||||
environment: importedSecrets[i].environment,
|
||||
secretPath: importedSecrets[i].secretPath
|
||||
@@ -783,13 +749,7 @@ export const secretServiceFactory = ({
|
||||
}
|
||||
if (!secret) throw new NotFoundError({ message: `Secret with name '${secretName}' not found` });
|
||||
|
||||
return {
|
||||
...secret,
|
||||
secretValueHidden: false, // Always false because we check permission at the beginning of the function
|
||||
workspace: projectId,
|
||||
environment,
|
||||
secretPath: path
|
||||
};
|
||||
return { ...secret, workspace: projectId, environment, secretPath: path };
|
||||
};
|
||||
|
||||
const createManySecret = async ({
|
||||
@@ -811,7 +771,7 @@ export const secretServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Create,
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
@@ -899,7 +859,7 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionActions.Edit,
|
||||
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" });
|
||||
|
||||
const references = await getSecretReference(projectId);
|
||||
const secrets = await secretDAL.transaction(async (tx) => {
|
||||
const updatedSecrets = await fnSecretBulkUpdate({
|
||||
const secrets = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkUpdate({
|
||||
folderId,
|
||||
projectId,
|
||||
tx,
|
||||
@@ -972,18 +932,8 @@ export const secretServiceFactory = ({
|
||||
secretVersionDAL,
|
||||
secretTagDAL,
|
||||
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 secretQueueService.syncSecrets({
|
||||
@@ -1017,7 +967,7 @@ export const secretServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Delete,
|
||||
ProjectPermissionActions.Delete,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
@@ -1069,15 +1019,7 @@ export const secretServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
const secretValueHidden = !permission.can(
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||
);
|
||||
|
||||
return secrets.map((secret) => ({
|
||||
...secret,
|
||||
...conditionallyHideSecretValue(secretValueHidden, secret)
|
||||
}));
|
||||
return secrets;
|
||||
});
|
||||
|
||||
await snapshotService.performSnapshot(folderId);
|
||||
@@ -1238,7 +1180,6 @@ export const secretServiceFactory = ({
|
||||
secretName,
|
||||
path: secretPath,
|
||||
environment,
|
||||
viewSecretValue: false,
|
||||
type: "shared"
|
||||
});
|
||||
|
||||
@@ -1253,11 +1194,10 @@ export const secretServiceFactory = ({
|
||||
| (typeof groupPermissions)[number]
|
||||
) => {
|
||||
const allowedActions = [
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionSecretActions.Delete,
|
||||
ProjectPermissionSecretActions.Create,
|
||||
ProjectPermissionSecretActions.Edit
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionActions.Edit
|
||||
].filter((action) =>
|
||||
entityPermission.permission.can(
|
||||
action,
|
||||
@@ -1294,13 +1234,11 @@ export const secretServiceFactory = ({
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
viewSecretValue,
|
||||
environment,
|
||||
includeImports,
|
||||
expandSecretReferences,
|
||||
recursive,
|
||||
tagSlugs = [],
|
||||
throwOnMissingReadValuePermission = true,
|
||||
...paramsV2
|
||||
}: TGetSecretsRawDTO) => {
|
||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||
@@ -1311,8 +1249,6 @@ export const secretServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
viewSecretValue,
|
||||
throwOnMissingReadValuePermission,
|
||||
environment,
|
||||
path,
|
||||
recursive,
|
||||
@@ -1321,7 +1257,6 @@ export const secretServiceFactory = ({
|
||||
tagSlugs,
|
||||
...paramsV2
|
||||
});
|
||||
|
||||
return { secrets, imports };
|
||||
}
|
||||
|
||||
@@ -1350,20 +1285,14 @@ export const secretServiceFactory = ({
|
||||
recursive
|
||||
});
|
||||
|
||||
const decryptedSecrets = secrets.map((el) => decryptSecretRaw({ ...el, secretValueHidden: false }, botKey));
|
||||
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
|
||||
const filteredSecrets = tagSlugs.length
|
||||
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
||||
: decryptedSecrets;
|
||||
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
||||
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
||||
decryptSecretRaw(
|
||||
{
|
||||
...sec,
|
||||
environment: el.environment,
|
||||
workspace: projectId,
|
||||
secretPath: el.secretPath,
|
||||
secretValueHidden: false
|
||||
},
|
||||
{ ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath },
|
||||
botKey
|
||||
)
|
||||
);
|
||||
@@ -1374,7 +1303,6 @@ export const secretServiceFactory = ({
|
||||
const importedEntries = decryptedImportSecrets.reduce(
|
||||
(
|
||||
accum: {
|
||||
secretValueHidden: boolean;
|
||||
secretKey: string;
|
||||
secretPath: string;
|
||||
workspace: string;
|
||||
@@ -1418,7 +1346,6 @@ export const secretServiceFactory = ({
|
||||
Object.keys(secretsGroupByPath).map((groupedPath) =>
|
||||
Promise.allSettled(
|
||||
secretsGroupByPath[groupedPath].map(async (decryptedSecret, index) => {
|
||||
if (decryptedSecret.secretValueHidden) return;
|
||||
const expandedSecretValue = await expandSecret({
|
||||
value: decryptedSecret.secretValue,
|
||||
secretPath: groupedPath,
|
||||
@@ -1435,7 +1362,6 @@ export const secretServiceFactory = ({
|
||||
processedImports.map((processedImport) =>
|
||||
Promise.allSettled(
|
||||
processedImport.secrets.map(async (decryptedSecret, index) => {
|
||||
if (decryptedSecret.secretValueHidden) return;
|
||||
const expandedSecretValue = await expandSecret({
|
||||
value: decryptedSecret.secretValue,
|
||||
secretPath: path,
|
||||
@@ -1461,7 +1387,6 @@ export const secretServiceFactory = ({
|
||||
path,
|
||||
actor,
|
||||
environment,
|
||||
viewSecretValue,
|
||||
projectId: workspaceId,
|
||||
expandSecretReferences,
|
||||
projectSlug,
|
||||
@@ -1481,7 +1406,6 @@ export const secretServiceFactory = ({
|
||||
includeImports,
|
||||
actorAuthMethod,
|
||||
path,
|
||||
viewSecretValue,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
@@ -1512,7 +1436,6 @@ export const secretServiceFactory = ({
|
||||
message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`,
|
||||
name: "bot_not_found_error"
|
||||
});
|
||||
|
||||
const decryptedSecret = decryptSecretRaw(encryptedSecret, botKey);
|
||||
|
||||
if (expandSecretReferences) {
|
||||
@@ -1531,10 +1454,7 @@ export const secretServiceFactory = ({
|
||||
decryptedSecret.secretValue = expandedSecretValue || "";
|
||||
}
|
||||
|
||||
return {
|
||||
secretMetadata: undefined,
|
||||
...decryptedSecret
|
||||
};
|
||||
return { secretMetadata: undefined, ...decryptedSecret };
|
||||
};
|
||||
|
||||
const createSecretRaw = async ({
|
||||
@@ -1685,16 +1605,7 @@ export const secretServiceFactory = ({
|
||||
tags: tagIds
|
||||
});
|
||||
|
||||
return {
|
||||
type: SecretProtectionType.Direct as const,
|
||||
secret: decryptSecretRaw(
|
||||
{
|
||||
...secret,
|
||||
secretValueHidden: false
|
||||
},
|
||||
botKey
|
||||
)
|
||||
};
|
||||
return { type: SecretProtectionType.Direct as const, secret: decryptSecretRaw(secret, botKey) };
|
||||
};
|
||||
|
||||
const updateSecretRaw = async ({
|
||||
@@ -2090,7 +2001,7 @@ export const secretServiceFactory = ({
|
||||
return {
|
||||
type: SecretProtectionType.Direct as const,
|
||||
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);
|
||||
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);
|
||||
if (!botKey)
|
||||
throw new NotFoundError({ message: `Project bot for project with ID '${folder.projectId}' not found` });
|
||||
@@ -2398,42 +2303,18 @@ export const secretServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
const secretVersions = await secretVersionDAL.findBySecretId(secretId, {
|
||||
offset,
|
||||
limit,
|
||||
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(
|
||||
const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] });
|
||||
return secretVersions.map((el) =>
|
||||
decryptSecretRaw(
|
||||
{
|
||||
secretValueHidden,
|
||||
...el,
|
||||
workspace: folder.projectId,
|
||||
environment: folder.environment.envSlug,
|
||||
secretPath: folderWithPath.path
|
||||
secretPath: "/"
|
||||
},
|
||||
botKey
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const attachTags = async ({
|
||||
@@ -2459,7 +2340,7 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
|
||||
@@ -2565,7 +2446,7 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
|
||||
@@ -2731,7 +2612,7 @@ export const secretServiceFactory = ({
|
||||
message: `Project with slug '${projectSlug}' not found`
|
||||
});
|
||||
}
|
||||
if (project.version === ProjectVersion.V3) {
|
||||
if (project.version === 3) {
|
||||
return secretV2BridgeService.moveSecrets({
|
||||
sourceEnvironment,
|
||||
sourceSecretPath,
|
||||
@@ -2756,6 +2637,30 @@ export const secretServiceFactory = ({
|
||||
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);
|
||||
if (!botKey) {
|
||||
throw new NotFoundError({
|
||||
@@ -2783,9 +2688,11 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const sourceSecrets = await secretDAL.findManySecretsWithTags({
|
||||
const sourceSecrets = await secretDAL.find({
|
||||
type: SecretType.Shared,
|
||||
secretIds
|
||||
$in: {
|
||||
id: secretIds
|
||||
}
|
||||
});
|
||||
|
||||
if (sourceSecrets.length !== secretIds.length) {
|
||||
@@ -2794,52 +2701,21 @@ export const secretServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const sourceActions = [
|
||||
ProjectPermissionSecretActions.Delete,
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
ProjectPermissionSecretActions.ReadValue
|
||||
] as const;
|
||||
const destinationActions = [ProjectPermissionSecretActions.Create, ProjectPermissionSecretActions.Edit] as const;
|
||||
|
||||
const decryptedSourceSecrets = sourceSecrets.map((secret) => {
|
||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||
const decryptedSourceSecrets = sourceSecrets.map((secret) => ({
|
||||
...secret,
|
||||
secretKey: decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretKeyCiphertext,
|
||||
iv: secret.secretKeyIV,
|
||||
tag: secret.secretKeyTag,
|
||||
key: botKey
|
||||
});
|
||||
|
||||
for (const destinationAction of destinationActions) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
destinationAction,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
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
|
||||
})
|
||||
};
|
||||
});
|
||||
}),
|
||||
secretValue: decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretValueCiphertext,
|
||||
iv: secret.secretValueIV,
|
||||
tag: secret.secretValueTag,
|
||||
key: botKey
|
||||
})
|
||||
}));
|
||||
|
||||
let isSourceUpdated = false;
|
||||
let isDestinationUpdated = false;
|
||||
|
||||
@@ -180,8 +180,6 @@ export type TGetSecretsRawDTO = {
|
||||
expandSecretReferences?: boolean;
|
||||
path: string;
|
||||
environment: string;
|
||||
viewSecretValue: boolean;
|
||||
throwOnMissingReadValuePermission?: boolean;
|
||||
includeImports?: boolean;
|
||||
recursive?: boolean;
|
||||
tagSlugs?: string[];
|
||||
@@ -207,7 +205,6 @@ export type TGetASecretRawDTO = {
|
||||
secretName: string;
|
||||
path: string;
|
||||
environment: string;
|
||||
viewSecretValue: boolean;
|
||||
expandSecretReferences?: boolean;
|
||||
type: "shared" | "personal";
|
||||
includeImports?: boolean;
|
||||
@@ -412,7 +409,7 @@ export type TCreateManySecretsRawFnFactory = {
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
secretV2BridgeDAL: Pick<
|
||||
TSecretV2BridgeDALFactory,
|
||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany" | "find"
|
||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany"
|
||||
>;
|
||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
@@ -449,7 +446,7 @@ export type TUpdateManySecretsRawFnFactory = {
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
secretV2BridgeDAL: Pick<
|
||||
TSecretV2BridgeDALFactory,
|
||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany" | "find"
|
||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany"
|
||||
>;
|
||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
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 { ormify, selectAllTableCols, sqlNestRelationships, TFindOpt } from "@app/lib/knex";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueName } from "@app/queue";
|
||||
|
||||
@@ -12,50 +12,6 @@ export type TSecretVersionDALFactory = ReturnType<typeof secretVersionDALFactory
|
||||
export const secretVersionDALFactory = (db: TDbClient) => {
|
||||
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
|
||||
const findLatestVersionByFolderId = async (folderId: string, tx?: Knex) => {
|
||||
try {
|
||||
@@ -193,7 +149,6 @@ export const secretVersionDALFactory = (db: TDbClient) => {
|
||||
findLatestVersionMany,
|
||||
bulkUpdate,
|
||||
findLatestVersionByFolderId,
|
||||
findBySecretId,
|
||||
bulkUpdateNoVersionIncrement
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,11 +5,7 @@ import bcrypt from "bcrypt";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
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 { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
|
||||
@@ -71,7 +67,7 @@ export const serviceTokenServiceFactory = ({
|
||||
|
||||
scopes.forEach(({ environment, secretPath }) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretActions.Create,
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ export type TCreateServiceTokenDTO = {
|
||||
iv: string;
|
||||
tag: string;
|
||||
expiresIn?: number | null;
|
||||
permissions: ("read" | "write" | "readValue")[];
|
||||
permissions: ("read" | "write")[];
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetServiceTokenInfoDTO = Omit<TProjectPermission, "projectId">;
|
||||
|
||||
Reference in New Issue
Block a user