feat: updated folder, import, approvals and some more to projectid

This commit is contained in:
=
2025-09-12 22:59:57 +05:30
parent fd2b067e14
commit 02dc2e2106
25 changed files with 1380 additions and 267 deletions

View File

@@ -0,0 +1,293 @@
import { nanoid } from "nanoid";
import { z } from "zod";
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
import { removeTrailingSlash } from "@app/lib/fn";
import { EnforcementLevel } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerDepreciatedSecretApprovalPolicyRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
body: z
.object({
workspaceId: z.string(),
name: z.string().optional(),
environment: z.string().optional(),
environments: z.string().array().optional(),
secretPath: z
.string()
.min(1, { message: "Secret path cannot be empty" })
.transform((val) => removeTrailingSlash(val)),
approvers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
z.object({
type: z.literal(ApproverType.User),
id: z.string().optional(),
username: z.string().optional()
})
])
.array()
.min(1, { message: "At least one approver should be provided" })
.max(100, "Cannot have more than 100 approvers"),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
z.object({
type: z.literal(BypasserType.User),
id: z.string().optional(),
username: z.string().optional()
})
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true)
})
.refine((data) => data.environment || data.environments, "At least one environment should be provided"),
response: {
200: z.object({
approval: sapPubSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.body.workspaceId,
...req.body,
name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`,
enforcementLevel: req.body.enforcementLevel
});
return { approval };
}
});
server.route({
url: "/:sapId",
method: "PATCH",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
sapId: z.string()
}),
body: z.object({
name: z.string().optional(),
approvers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
])
.array()
.min(1, { message: "At least one approver should be provided" })
.max(100, "Cannot have more than 100 approvers"),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvals: z.number().min(1).default(1),
secretPath: z
.string()
.trim()
.min(1, { message: "Secret path cannot be empty" })
.optional()
.transform((val) => (val ? removeTrailingSlash(val) : undefined)),
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
allowedSelfApprovals: z.boolean().default(true),
environments: z.array(z.string()).optional()
}),
response: {
200: z.object({
approval: sapPubSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
secretPolicyId: req.params.sapId
});
return { approval };
}
});
server.route({
url: "/:sapId",
method: "DELETE",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
sapId: z.string()
}),
response: {
200: z.object({
approval: sapPubSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
secretPolicyId: req.params.sapId
});
return { approval };
}
});
server.route({
url: "/",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
approvals: sapPubSchema
.extend({
approvers: z
.object({
id: z.string().nullable().optional(),
type: z.nativeEnum(ApproverType)
})
.array(),
bypassers: z
.object({
id: z.string().nullable().optional(),
type: z.nativeEnum(BypasserType)
})
.array()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId
});
return { approvals };
}
});
server.route({
url: "/:sapId",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
sapId: z.string()
}),
response: {
200: z.object({
approval: sapPubSchema.extend({
approvers: z
.object({
id: z.string().nullable().optional(),
type: z.nativeEnum(ApproverType),
username: z.string().nullable().optional()
})
.array(),
bypassers: z
.object({
id: z.string().nullable().optional(),
type: z.nativeEnum(BypasserType),
username: z.string().nullable().optional()
})
.array()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const approval = await server.services.secretApprovalPolicy.getSecretApprovalPolicyById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.params
});
return { approval };
}
});
server.route({
url: "/board",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().transform(removeTrailingSlash)
}),
response: {
200: z.object({
policy: sapPubSchema
.extend({
userApprovers: z.object({ userId: z.string().nullable().optional() }).array()
})
.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId,
...req.query
});
return { policy };
}
});
};

View File

@@ -29,7 +29,7 @@ import { registerRateLimitRouter } from "./rate-limit-router";
import { registerRelayRouter } from "./relay-router";
import { registerSamlRouter } from "./saml-router";
import { registerScimRouter } from "./scim-router";
import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router";
import { registerDepreciatedSecretApprovalPolicyRouter } from "./depreciated-secret-approval-policy-router";
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
import { registerSecretRotationRouter } from "./secret-rotation-router";
@@ -71,7 +71,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerSnapshotRouter, { prefix: "/secret-snapshot" });
await server.register(registerPITRouter, { prefix: "/pit" });
await server.register(registerSecretApprovalPolicyRouter, { prefix: "/secret-approvals" });
await server.register(registerDepreciatedSecretApprovalPolicyRouter, { prefix: "/secret-approvals" });
await server.register(registerSecretApprovalRequestRouter, {
prefix: "/secret-approval-requests"
});

View File

@@ -27,7 +27,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
},
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
projectId: z.string().trim(),
environment: z.string().trim().optional(),
committer: z.string().trim().optional(),
search: z.string().trim().optional(),
@@ -80,7 +80,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
projectId: req.query.projectId
});
return { approvals, totalCount };
}
@@ -94,7 +94,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
},
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
projectId: z.string().trim(),
policyId: z.string().trim().optional()
}),
response: {
@@ -113,7 +113,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId,
projectId: req.query.projectId,
policyId: req.query.policyId
});
return { approvals };

View File

@@ -10,6 +10,7 @@ import {
import { registerGatewayV2Router } from "./gateway-router";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerDepreciatedProjectRoleRouter } from "./depreciated-project-role-router";
import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router";
export const registerV2EERoutes = async (server: FastifyZodProvider) => {
await server.register(
@@ -26,6 +27,8 @@ export const registerV2EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerGatewayV2Router, { prefix: "/gateways" });
await server.register(registerSecretApprovalPolicyRouter, { prefix: "/secret-approvals" });
await server.register(
async (secretRotationV2Router) => {
// register generic secret rotation endpoints

View File

@@ -19,7 +19,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
schema: {
body: z
.object({
workspaceId: z.string(),
projectId: z.string(),
name: z.string().optional(),
environment: z.string().optional(),
environments: z.string().array().optional(),
@@ -69,7 +69,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.body.workspaceId,
...req.body,
name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`,
enforcementLevel: req.body.enforcementLevel
@@ -174,7 +173,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
},
schema: {
querystring: z.object({
workspaceId: z.string().trim()
projectId: z.string().trim()
}),
response: {
200: z.object({
@@ -204,7 +203,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId
projectId: req.query.projectId
});
return { approvals };
}
@@ -263,7 +262,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
},
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
projectId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().transform(removeTrailingSlash)
}),
@@ -284,7 +283,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId,
...req.query
});
return { policy };

View File

@@ -901,7 +901,7 @@ export const ENVIRONMENTS = {
export const FOLDERS = {
LIST: {
workspaceId: "The ID of the project to list folders from.",
projectId: "The ID of the project to list folders from.",
environment: "The slug of the environment to list folders from.",
path: "The path to list folders from.",
directory: "The directory to list folders from. (Deprecated in favor of path)",
@@ -913,7 +913,7 @@ export const FOLDERS = {
folderId: "The ID of the folder to get details."
},
CREATE: {
workspaceId: "The ID of the project to create the folder in.",
projectId: "The ID of the project to create the folder in.",
environment: "The slug of the environment to create the folder in.",
name: "The name of the folder to create.",
path: "The path of the folder to create.",
@@ -927,12 +927,12 @@ export const FOLDERS = {
path: "The path of the folder to update.",
directory: "The new directory of the folder to update. (Deprecated in favor of path)",
projectSlug: "The slug of the project where the folder is located.",
workspaceId: "The ID of the project where the folder is located.",
projectId: "The ID of the project where the folder is located.",
description: "An optional description label for the folder."
},
DELETE: {
folderIdOrName: "The ID or name of the folder to delete.",
workspaceId: "The ID of the project to delete the folder from.",
projectId: "The ID of the project to delete the folder from.",
environment: "The slug of the environment where the folder is located.",
directory: "The directory of the folder to delete. (Deprecated in favor of path)",
path: "The path of the folder to delete."
@@ -1043,7 +1043,7 @@ export const RAW_SECRETS = {
export const SECRET_IMPORTS = {
LIST: {
workspaceId: "The ID of the project to list secret imports from.",
projectId: "The ID of the project to list secret imports from.",
environment: "The slug of the environment to list secret imports from.",
path: "The path to list secret imports from."
},
@@ -1053,7 +1053,7 @@ export const SECRET_IMPORTS = {
CREATE: {
environment: "The slug of the environment to import into.",
path: "The path to import into.",
workspaceId: "The ID of the project you are working in.",
projectId: "The ID of the project you are working in.",
isReplication:
"When true, secrets from the source will be automatically sent to the destination. If approval policies exist at the destination, the secrets will be sent as approval requests instead of being applied immediately.",
import: {
@@ -1070,10 +1070,10 @@ export const SECRET_IMPORTS = {
position: "The new position of the secret import. The lowest number will be displayed as the first import."
},
path: "The path of the secret import to update.",
workspaceId: "The ID of the project where the secret import is located."
projectId: "The ID of the project where the secret import is located."
},
DELETE: {
workspaceId: "The ID of the project to delete the secret import from.",
projectId: "The ID of the project to delete the secret import from.",
secretImportId: "The ID of the secret import to delete.",
environment: "The slug of the environment where the secret import is located.",
path: "The path of the secret import to delete."

View File

@@ -0,0 +1,444 @@
import { z } from "zod";
import { SecretFoldersSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, FOLDERS } from "@app/lib/api-docs";
import { prefixWithSlash, removeTrailingSlash } from "@app/lib/fn";
import { isValidFolderName } from "@app/lib/validator";
import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { booleanSchema } from "../sanitizedSchemas";
export const registerDepreciatedSecretFolderRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
description: "Create folders",
security: [
{
bearerAuth: []
}
],
body: z.object({
workspaceId: z.string().trim().describe(FOLDERS.CREATE.projectId),
environment: z.string().trim().describe(FOLDERS.CREATE.environment),
name: z
.string()
.trim()
.describe(FOLDERS.CREATE.name)
.refine((name) => isValidFolderName(name), {
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
}),
path: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.CREATE.path)
.optional(),
// backward compatibility with cli
directory: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if directory is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.CREATE.directory)
.optional(),
description: z.string().optional().nullable().describe(FOLDERS.CREATE.description)
}),
response: {
200: z.object({
folder: SecretFoldersSchema.extend({
path: z.string()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const path = req.body.path || req.body.directory || "/";
const folder = await server.services.folder.createFolder({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectId: req.body.workspaceId,
path,
description: req.body.description
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderName: folder.name,
folderPath: path,
...(req.body.description ? { description: req.body.description } : {})
}
}
});
return { folder };
}
});
server.route({
url: "/:folderId",
method: "PATCH",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
description: "Update folder",
security: [
{
bearerAuth: []
}
],
params: z.object({
// old way this was name
folderId: z.string().describe(FOLDERS.UPDATE.folderId)
}),
body: z.object({
workspaceId: z.string().trim().describe(FOLDERS.UPDATE.projectId),
environment: z.string().trim().describe(FOLDERS.UPDATE.environment),
name: z
.string()
.trim()
.describe(FOLDERS.UPDATE.name)
.refine((name) => isValidFolderName(name), {
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
}),
path: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.UPDATE.path)
.optional(),
// backward compatibility with cli
directory: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if directory is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.UPDATE.directory)
.optional(),
description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description)
}),
response: {
200: z.object({
folder: SecretFoldersSchema.extend({
path: z.string()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const path = req.body.path || req.body.directory || "/";
const { folder, old } = await server.services.folder.updateFolder({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectId: req.body.workspaceId,
id: req.params.folderId,
path
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.UPDATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: path,
newFolderName: folder.name,
oldFolderName: old.name
}
}
});
return { folder };
}
});
server.route({
url: "/batch",
method: "PATCH",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
description: "Update folders by batch",
security: [
{
bearerAuth: []
}
],
body: z.object({
projectSlug: z.string().trim().describe(FOLDERS.UPDATE.projectSlug),
folders: z
.object({
id: z.string().describe(FOLDERS.UPDATE.folderId),
environment: z.string().trim().describe(FOLDERS.UPDATE.environment),
name: z
.string()
.trim()
.describe(FOLDERS.UPDATE.name)
.refine((name) => isValidFolderName(name), {
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
}),
path: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash)
.transform(removeTrailingSlash)
.describe(FOLDERS.UPDATE.path),
description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description)
})
.array()
.min(1)
}),
response: {
200: z.object({
folders: SecretFoldersSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { newFolders, oldFolders, projectId } = await server.services.folder.updateManyFolders({
...req.body,
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await Promise.all(
req.body.folders.map(async (folder, index) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.UPDATE_FOLDER,
metadata: {
environment: oldFolders[index].envId,
folderId: oldFolders[index].id,
folderPath: folder.path,
newFolderName: newFolders[index].name,
oldFolderName: oldFolders[index].name
}
}
});
})
);
return { folders: newFolders };
}
});
// TODO(daniel): Expose this route in api reference and write docs for it.
server.route({
method: "DELETE",
url: "/:folderIdOrName",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
description: "Delete a folder",
security: [
{
bearerAuth: []
}
],
params: z.object({
folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName)
}),
body: z.object({
workspaceId: z.string().trim().describe(FOLDERS.DELETE.projectId),
environment: z.string().trim().describe(FOLDERS.DELETE.environment),
path: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.DELETE.path)
.optional(),
// keep this here as cli need directory
directory: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if directory is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.DELETE.directory)
.optional()
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const path = req.body.path || req.body.directory || "/";
const folder = await server.services.folder.deleteFolder({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectId: req.body.workspaceId,
idOrName: req.params.folderIdOrName,
path
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.DELETE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: path,
folderName: folder.name
}
}
});
return { folder };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
description: "Get folders",
security: [
{
bearerAuth: []
}
],
querystring: z.object({
workspaceId: z.string().trim().describe(FOLDERS.LIST.projectId),
environment: z.string().trim().describe(FOLDERS.LIST.environment),
lastSecretModified: z.string().datetime().trim().optional().describe(FOLDERS.LIST.lastSecretModified),
path: z
.string()
.trim()
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.LIST.path)
.optional(),
// backward compatibility with cli
directory: z
.string()
.trim()
.transform(prefixWithSlash) // Transformations get skipped if directory is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.LIST.directory)
.optional(),
recursive: booleanSchema.default(false).describe(FOLDERS.LIST.recursive)
}),
response: {
200: z.object({
folders: SecretFoldersSchema.extend({
relativePath: z.string().optional()
}).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const path = req.query.path || req.query.directory || "/";
const folders = await server.services.folder.getFolders({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId,
path
});
return { folders };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
description: "Get folder by id",
security: [
{
bearerAuth: []
}
],
params: z.object({
id: z.string().trim().describe(FOLDERS.GET_BY_ID.folderId)
}),
response: {
200: z.object({
folder: SecretFoldersSchema.extend({
environment: z.object({
envId: z.string(),
envName: z.string(),
envSlug: z.string()
}),
path: z.string(),
projectId: z.string()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const folder = await server.services.folder.getFolderById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { folder };
}
});
};

View File

@@ -0,0 +1,472 @@
import { z } from "zod";
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, SECRET_IMPORTS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { secretRawSchema } from "../sanitizedSchemas";
export const registerDepreciatedSecretImportRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretImports],
description: "Create secret imports",
security: [
{
bearerAuth: []
}
],
body: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path),
import: z.object({
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.import.environment),
path: z.string().trim().transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.import.path)
}),
isReplication: z.boolean().default(false).describe(SECRET_IMPORTS.CREATE.isReplication)
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const secretImport = await server.services.secretImport.createImport({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectId: req.body.workspaceId,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
importFromSecretPath: secretImport.importPath,
importFromEnvironment: secretImport.importEnv.slug,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully created secret import", secretImport };
}
});
server.route({
method: "PATCH",
url: "/:secretImportId",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretImports],
description: "Update secret imports",
security: [
{
bearerAuth: []
}
],
params: z.object({
secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId)
}),
body: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path),
import: z.object({
environment: z.string().trim().optional().describe(SECRET_IMPORTS.UPDATE.import.environment),
path: z
.string()
.trim()
.optional()
.transform((val) => (val ? removeTrailingSlash(val) : val))
.describe(SECRET_IMPORTS.UPDATE.import.path),
position: z.number().optional().describe(SECRET_IMPORTS.UPDATE.import.position)
})
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const secretImport = await server.services.secretImport.updateImport({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
position: secretImport.position,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully updated secret import", secretImport };
}
});
server.route({
method: "DELETE",
url: "/:secretImportId",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretImports],
description: "Delete secret imports",
security: [
{
bearerAuth: []
}
],
params: z.object({
secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId)
}),
body: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path)
}),
response: {
200: z.object({
message: z.string(),
secretImport: SecretImportsSchema.omit({ importEnv: true }).merge(
z.object({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const secretImport = await server.services.secretImport.deleteImport({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.DELETE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
importFromEnvironment: secretImport.importEnv.slug,
importFromSecretPath: secretImport.importPath,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully deleted secret import", secretImport };
}
});
server.route({
method: "POST",
url: "/:secretImportId/replication-resync",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Resync secret replication of secret imports",
security: [
{
bearerAuth: []
}
],
params: z.object({
secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId)
}),
body: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path)
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { message } = await server.services.secretImport.resyncSecretImportReplication({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId
});
return { message };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretImports],
description: "Get secret imports",
security: [
{
bearerAuth: []
}
],
querystring: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path)
}),
response: {
200: z.object({
message: z.string(),
secretImports: SecretImportsSchema.omit({ importEnv: true })
.extend({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() })
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const secretImports = await server.services.secretImport.getImports({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.workspaceId,
event: {
type: EventType.GET_SECRET_IMPORTS,
metadata: {
environment: req.query.environment,
folderId: secretImports?.[0]?.folderId,
numberOfImports: secretImports.length
}
}
});
return { message: "Successfully fetched secret imports", secretImports };
}
});
server.route({
url: "/:secretImportId",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretImports],
description: "Get single secret import",
security: [
{
bearerAuth: []
}
],
params: z.object({
secretImportId: z.string().trim().describe(SECRET_IMPORTS.GET.secretImportId)
}),
response: {
200: z.object({
secretImport: SecretImportsSchema.omit({ importEnv: true }).extend({
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
}),
projectId: z.string(),
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }),
secretPath: z.string()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const secretImport = await server.services.secretImport.getImportById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.secretImportId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: secretImport.projectId,
event: {
type: EventType.GET_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId
}
}
});
return { secretImport };
}
});
server.route({
url: "/secrets",
method: "GET",
config: {
rateLimit: secretsLimit
},
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
secrets: z
.object({
secretPath: z.string(),
environment: z.string(),
environmentInfo: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
}),
folderId: z.string().optional(),
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const importedSecrets = await server.services.secretImport.getSecretsFromImports({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
});
return { secrets: importedSecrets };
}
});
server.route({
url: "/secrets/raw",
method: "GET",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretImports],
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
secrets: z
.object({
secretPath: z.string(),
environment: z.string(),
environmentInfo: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
}),
folderId: z.string().optional(),
secrets: secretRawSchema.array()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const importedSecrets = await server.services.secretImport.getRawSecretsFromImports({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
});
return { secrets: importedSecrets };
}
});
};

View File

@@ -50,8 +50,8 @@ import { registerProjectKeyRouter } from "./project-key-router";
import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router";
import { SECRET_REMINDER_REGISTER_ROUTER_MAP } from "./reminder-routers";
import { registerSecretFolderRouter } from "./secret-folder-router";
import { registerSecretImportRouter } from "./secret-import-router";
import { registerDepreciatedSecretFolderRouter } from "./depreciated-secret-folder-router";
import { registerDepreciatedSecretImportRouter } from "./depreciated-secret-import-router";
import { registerSecretRequestsRouter } from "./secret-requests-router";
import { registerSecretSharingRouter } from "./secret-sharing-router";
import { registerSecretTagRouter } from "./secret-tag-router";
@@ -93,8 +93,8 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register(registerNotificationRouter, { prefix: "/notifications" });
await server.register(registerInviteOrgRouter, { prefix: "/invite-org" });
await server.register(registerUserActionRouter, { prefix: "/user-action" });
await server.register(registerSecretImportRouter, { prefix: "/secret-imports" });
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
await server.register(registerDepreciatedSecretImportRouter, { prefix: "/secret-imports" });
await server.register(registerDepreciatedSecretFolderRouter, { prefix: "/folders" });
await server.register(
async (workflowIntegrationRouter) => {

View File

@@ -39,7 +39,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
body: z
.object({
type: z.nativeEnum(WebhookType).default(WebhookType.GENERAL),
workspaceId: z.string().trim(),
projectId: z.string().trim(),
environment: z.string().trim(),
webhookUrl: z.string().url().trim(),
webhookSecretKey: z.string().trim().optional(),
@@ -67,13 +67,12 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
event: {
type: EventType.CREATE_WEBHOOK,
metadata: {
@@ -216,7 +215,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
projectId: z.string().trim(),
environment: z.string().trim().optional(),
secretPath: z
.string()
@@ -238,7 +237,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
projectId: req.query.projectId
});
return { message: "Successfully fetched webhook", webhooks };
}

View File

@@ -10,6 +10,8 @@ import { registerDepreciatedProjectMembershipRouter } from "./depreciated-projec
import { registerDepreciatedProjectRouter } from "./depreciated-project-router";
import { registerServiceTokenRouter } from "./service-token-router";
import { registerUserRouter } from "./user-router";
import { registerSecretFolderRouter } from "./secret-folder-router";
import { registerSecretImportRouter } from "./secret-import-router";
export const registerV2Routes = async (server: FastifyZodProvider) => {
await server.register(registerMfaRouter, { prefix: "/auth" });
@@ -33,6 +35,9 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
{ prefix: "/organizations" }
);
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
await server.register(registerSecretImportRouter, { prefix: "/secret-imports" });
// moved to v1/projects
await server.register(
async (projectServer) => {

View File

@@ -28,7 +28,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}
],
body: z.object({
workspaceId: z.string().trim().describe(FOLDERS.CREATE.workspaceId),
projectId: z.string().trim().describe(FOLDERS.CREATE.projectId),
environment: z.string().trim().describe(FOLDERS.CREATE.environment),
name: z
.string()
@@ -43,17 +43,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.CREATE.path)
.optional(),
// backward compatibility with cli
directory: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if directory is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.CREATE.directory)
.optional(),
.describe(FOLDERS.CREATE.path),
description: z.string().optional().nullable().describe(FOLDERS.CREATE.description)
}),
response: {
@@ -66,27 +56,24 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const path = req.body.path || req.body.directory || "/";
const folder = await server.services.folder.createFolder({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectId: req.body.workspaceId,
path,
description: req.body.description
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
event: {
type: EventType.CREATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderName: folder.name,
folderPath: path,
folderPath: req.body.path,
...(req.body.description ? { description: req.body.description } : {})
}
}
@@ -115,7 +102,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
folderId: z.string().describe(FOLDERS.UPDATE.folderId)
}),
body: z.object({
workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId),
projectId: z.string().trim().describe(FOLDERS.UPDATE.projectId),
environment: z.string().trim().describe(FOLDERS.UPDATE.environment),
name: z
.string()
@@ -130,17 +117,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.UPDATE.path)
.optional(),
// backward compatibility with cli
directory: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if directory is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.UPDATE.directory)
.optional(),
.describe(FOLDERS.UPDATE.path),
description: z.string().optional().nullable().describe(FOLDERS.UPDATE.description)
}),
response: {
@@ -153,26 +130,23 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const path = req.body.path || req.body.directory || "/";
const { folder, old } = await server.services.folder.updateFolder({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectId: req.body.workspaceId,
id: req.params.folderId,
path
id: req.params.folderId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
event: {
type: EventType.UPDATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: path,
folderPath: req.body.path,
newFolderName: folder.name,
oldFolderName: old.name
}
@@ -198,7 +172,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}
],
body: z.object({
projectSlug: z.string().trim().describe(FOLDERS.UPDATE.projectSlug),
projectId: z.string().trim().describe(FOLDERS.UPDATE.projectId),
folders: z
.object({
id: z.string().describe(FOLDERS.UPDATE.folderId),
@@ -281,7 +255,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName)
}),
body: z.object({
workspaceId: z.string().trim().describe(FOLDERS.DELETE.workspaceId),
projectId: z.string().trim().describe(FOLDERS.DELETE.projectId),
environment: z.string().trim().describe(FOLDERS.DELETE.environment),
path: z
.string()
@@ -290,16 +264,6 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.DELETE.path)
.optional(),
// keep this here as cli need directory
directory: z
.string()
.trim()
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if directory is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.DELETE.directory)
.optional()
}),
response: {
200: z.object({
@@ -309,26 +273,23 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const path = req.body.path || req.body.directory || "/";
const folder = await server.services.folder.deleteFolder({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectId: req.body.workspaceId,
idOrName: req.params.folderIdOrName,
path
idOrName: req.params.folderIdOrName
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
event: {
type: EventType.DELETE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: path,
folderPath: req.body.path,
folderName: folder.name
}
}
@@ -353,7 +314,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}
],
querystring: z.object({
workspaceId: z.string().trim().describe(FOLDERS.LIST.workspaceId),
projectId: z.string().trim().describe(FOLDERS.LIST.projectId),
environment: z.string().trim().describe(FOLDERS.LIST.environment),
lastSecretModified: z.string().datetime().trim().optional().describe(FOLDERS.LIST.lastSecretModified),
path: z
@@ -361,16 +322,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.trim()
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.LIST.path)
.optional(),
// backward compatibility with cli
directory: z
.string()
.trim()
.transform(prefixWithSlash) // Transformations get skipped if directory is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.LIST.directory)
.optional(),
.describe(FOLDERS.LIST.path),
recursive: booleanSchema.default(false).describe(FOLDERS.LIST.recursive)
}),
response: {
@@ -383,15 +335,12 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const path = req.query.path || req.query.directory || "/";
const folders = await server.services.folder.getFolders({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId,
path
...req.query
});
return { folders };
}

View File

@@ -1,6 +1,6 @@
import { z } from "zod";
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
import { SecretImportsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, SECRET_IMPORTS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
@@ -27,7 +27,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}
],
body: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.workspaceId),
projectId: z.string().trim().describe(SECRET_IMPORTS.CREATE.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path),
import: z.object({
@@ -55,13 +55,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
event: {
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
@@ -97,7 +97,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId)
}),
body: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId),
projectId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path),
import: z.object({
@@ -131,13 +131,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
actorOrgId: req.permission.orgId,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
event: {
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
@@ -173,7 +173,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId)
}),
body: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.workspaceId),
projectId: z.string().trim().describe(SECRET_IMPORTS.DELETE.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path)
}),
@@ -197,12 +197,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
actorOrgId: req.permission.orgId,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId
projectId: req.body.projectId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
projectId: req.body.projectId,
event: {
type: EventType.DELETE_SECRET_IMPORT,
metadata: {
@@ -236,7 +236,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId)
}),
body: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId),
projectId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path)
}),
@@ -255,7 +255,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
actorOrgId: req.permission.orgId,
id: req.params.secretImportId,
...req.body,
projectId: req.body.workspaceId
projectId: req.body.projectId
});
return { message };
@@ -278,7 +278,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}
],
querystring: z.object({
workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.workspaceId),
projectId: z.string().trim().describe(SECRET_IMPORTS.LIST.projectId),
environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path)
}),
@@ -301,12 +301,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
projectId: req.query.projectId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.workspaceId,
projectId: req.query.projectId,
event: {
type: EventType.GET_SECRET_IMPORTS,
metadata: {
@@ -386,55 +386,11 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
config: {
rateLimit: secretsLimit
},
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash)
}),
response: {
200: z.object({
secrets: z
.object({
secretPath: z.string(),
environment: z.string(),
environmentInfo: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
}),
folderId: z.string().optional(),
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const importedSecrets = await server.services.secretImport.getSecretsFromImports({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
});
return { secrets: importedSecrets };
}
});
server.route({
url: "/secrets/raw",
method: "GET",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretImports],
querystring: z.object({
workspaceId: z.string().trim(),
projectId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash)
}),
@@ -463,8 +419,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
...req.query
});
return { secrets: importedSecrets };
}