Merge branch 'main' into ENG-3506-LDAP

This commit is contained in:
x032205
2025-09-15 22:25:18 -04:00
688 changed files with 13517 additions and 4929 deletions
+4 -1
View File
@@ -328,6 +328,7 @@ import { registerV1Routes } from "./v1";
import { initializeOauthConfigSync } from "./v1/sso-router";
import { registerV2Routes } from "./v2";
import { registerV3Routes } from "./v3";
import { registerV4Routes } from "./v4";
const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();
@@ -1838,7 +1839,8 @@ export const registerRoutes = async (
gatewayService,
gatewayV2Service,
gatewayDAL,
gatewayV2DAL
gatewayV2DAL,
projectDAL
});
const secretSyncService = secretSyncServiceFactory({
@@ -2295,6 +2297,7 @@ export const registerRoutes = async (
{ prefix: "/api/v2" }
);
await server.register(registerV3Routes, { prefix: "/api/v3" });
await server.register(registerV4Routes, { prefix: "/api/v4" });
server.addHook("onClose", async () => {
cronJobs.forEach((job) => job.stop());
@@ -26,6 +26,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
description?: string | null;
isPlatformManagedCredentials?: boolean;
gatewayId?: string | null;
projectId?: string;
}>;
updateSchema: z.ZodType<{
name?: string;
@@ -47,18 +48,27 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
schema: {
hide: false,
tags: [ApiDocsTags.AppConnections],
description: `List the ${appName} Connections for the current organization.`,
description: `List the ${appName} Connections for the current organization or project.`,
querystring: z.object({
projectId: z.string().optional().describe(AppConnections.LIST(app).projectId)
}),
response: {
200: z.object({ appConnections: sanitizedResponseSchema.array() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const appConnections = (await server.services.appConnection.listAppConnectionsByOrg(req.permission, app)) as T[];
const { projectId } = req.query;
const appConnections = (await server.services.appConnection.listAppConnections(
req.permission,
app,
projectId
)) as T[];
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId,
event: {
type: EventType.GET_APP_CONNECTIONS,
metadata: {
@@ -82,14 +92,19 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
schema: {
hide: false,
tags: [ApiDocsTags.AppConnections],
description: `List the ${appName} Connections the current user has permission to establish connections with.`,
description: `List the ${appName} Connections the current user has permission to establish connections within this project.`,
querystring: z.object({
projectId: z.string().optional().describe(AppConnections.LIST(app).projectId)
}),
response: {
200: z.object({
appConnections: z
.object({
app: z.literal(app),
name: z.string(),
id: z.string().uuid()
id: z.string().uuid(),
projectId: z.string().nullish(),
orgId: z.string()
})
.array()
})
@@ -97,14 +112,17 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { projectId } = req.query;
const appConnections = await server.services.appConnection.listAvailableAppConnectionsForUser(
app,
req.permission
req.permission,
projectId
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId,
event: {
type: EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS,
metadata: {
@@ -149,6 +167,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: appConnection.projectId ?? undefined,
event: {
type: EventType.GET_APP_CONNECTION,
metadata: {
@@ -178,6 +197,9 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
.min(1, "Connection name required")
.describe(AppConnections.GET_BY_NAME(app).connectionName)
}),
querystring: z.object({
projectId: z.string().trim().optional().describe(AppConnections.GET_BY_NAME(app).projectId)
}),
response: {
200: z.object({ appConnection: sanitizedResponseSchema })
}
@@ -185,16 +207,21 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { connectionName } = req.params;
const { projectId } = req.query;
const appConnection = (await server.services.appConnection.findAppConnectionByName(
app,
connectionName,
{
connectionName,
projectId
},
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: appConnection.projectId ?? undefined,
event: {
type: EventType.GET_APP_CONNECTION,
metadata: {
@@ -216,9 +243,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
schema: {
hide: false,
tags: [ApiDocsTags.AppConnections],
description: `Create ${
startsWithVowel(appName) ? "an" : "a"
} ${appName} Connection for the current organization.`,
description: `Create ${startsWithVowel(appName) ? "an" : "a"} ${appName} Connection.`,
body: createSchema,
response: {
200: z.object({ appConnection: sanitizedResponseSchema })
@@ -226,16 +251,17 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { name, method, credentials, description, isPlatformManagedCredentials, gatewayId } = req.body;
const { name, method, credentials, description, isPlatformManagedCredentials, gatewayId, projectId } = req.body;
const appConnection = (await server.services.appConnection.createAppConnection(
{ name, method, app, credentials, description, isPlatformManagedCredentials, gatewayId },
{ name, method, app, credentials, description, isPlatformManagedCredentials, gatewayId, projectId },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId,
event: {
type: EventType.CREATE_APP_CONNECTION,
metadata: {
@@ -283,6 +309,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: appConnection.projectId ?? undefined,
event: {
type: EventType.UPDATE_APP_CONNECTION,
metadata: {
@@ -329,6 +356,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: appConnection.projectId ?? undefined,
event: {
type: EventType.DELETE_APP_CONNECTION,
metadata: {
@@ -340,4 +368,81 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
return { appConnection };
}
});
// scott: we will need this once we have individual app connection page and may want to expose to API
// server.route({
// method: "GET",
// url: `/:connectionId/usage`,
// config: {
// rateLimit: readLimit
// },
// schema: {
// hide: true, // scott: we could expose this in the future but just for UI right now
// tags: [ApiDocsTags.AppConnections],
// params: z.object({
// connectionId: z.string().uuid()
// }),
// response: {
// 200: z.object({
// projects: z
// .object({
// id: z.string(),
// name: z.string(),
// type: z.nativeEnum(ProjectType),
// slug: z.string(),
// resources: z.object({
// secretSyncs: z
// .object({
// id: z.string(),
// name: z.string()
// })
// .array(),
// secretRotations: z
// .object({
// id: z.string(),
// name: z.string()
// })
// .array(),
// externalCas: z
// .object({
// id: z.string(),
// name: z.string()
// })
// .array(),
// dataSources: z
// .object({
// id: z.string(),
// name: z.string()
// })
// .array()
// })
// })
// .array()
// })
// }
// },
// onRequest: verifyAuth([AuthMode.JWT]),
// handler: async (req) => {
// const { connectionId } = req.params;
//
// const projects = await server.services.appConnection.findAppConnectionUsageById(
// app,
// connectionId,
// req.permission
// );
//
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// orgId: req.permission.orgId,
// event: {
// type: EventType.GET_APP_CONNECTION_USAGE,
// metadata: {
// connectionId
// }
// }
// });
//
// return { projects };
// }
// });
};
@@ -1,12 +1,13 @@
import { z } from "zod";
import { ProjectType } from "@app/db/schemas";
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
import {
OracleDBConnectionListItemSchema,
SanitizedOracleDBConnectionSchema
} from "@app/ee/services/app-connections/oracledb";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
import { ApiDocsTags, AppConnections } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import {
@@ -210,6 +211,9 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
hide: false,
tags: [ApiDocsTags.AppConnections],
description: "List the available App Connection Options.",
querystring: z.object({
projectType: z.nativeEnum(ProjectType).optional()
}),
response: {
200: z.object({
appConnectionOptions: AppConnectionOptionsSchema.array()
@@ -217,8 +221,8 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: () => {
const appConnectionOptions = server.services.appConnection.listAppConnectionOptions();
handler: (req) => {
const appConnectionOptions = server.services.appConnection.listAppConnectionOptions(req.query.projectType);
return { appConnectionOptions };
}
});
@@ -232,18 +236,27 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
schema: {
hide: false,
tags: [ApiDocsTags.AppConnections],
description: "List all the App Connections for the current organization.",
description: "List all the App Connections for the current organization or project.",
querystring: z.object({
projectId: z.string().optional().describe(AppConnections.LIST().projectId)
}),
response: {
200: z.object({ appConnections: SanitizedAppConnectionSchema.array() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const appConnections = await server.services.appConnection.listAppConnectionsByOrg(req.permission);
const { projectId } = req.query;
const appConnections = await server.services.appConnection.listAppConnections(
req.permission,
undefined,
projectId
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId,
event: {
type: EventType.GET_APP_CONNECTIONS,
metadata: {
@@ -207,7 +207,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
const environments = req.query.environments.split(",");
if (!projectId || environments.length === 0)
throw new BadRequestError({ message: "Missing workspace id or environment(s)" });
throw new BadRequestError({ message: "Missing project id or environment(s)" });
const { shouldUseSecretV2Bridge } = await server.services.projectBot.getBotKey(projectId);
@@ -474,7 +474,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secretCountFromEnv,
workspaceId: projectId,
projectId,
environment,
secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -696,7 +696,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
includeSecretRotations
} = req.query;
if (!projectId || !environment) throw new BadRequestError({ message: "Missing workspace id or environment" });
if (!projectId || !environment) throw new BadRequestError({ message: "Missing project id or environment" });
const { shouldUseSecretV2Bridge } = await server.services.projectBot.getBotKey(projectId);
@@ -1001,7 +1001,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secretCount,
workspaceId: projectId,
projectId,
environment,
secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1168,7 +1168,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secretCountForEnv,
workspaceId: projectId,
projectId,
environment,
secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1361,7 +1361,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId: projectId,
projectId,
environment,
secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -0,0 +1,298 @@
import { z } from "zod";
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, ENVIRONMENTS } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerDeprecatedProjectEnvRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:workspaceId/environments/:envId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Environments],
description: "Get Environment",
security: [
{
bearerAuth: []
}
],
params: z.object({
// NOTE(daniel): workspaceId isn't used, but we need to keep it for backwards compatibility. The endpoint defined below, uses no project ID, and is takes a pure environment ID.
workspaceId: z.string().trim().describe(ENVIRONMENTS.GET.projectId),
envId: z.string().trim().describe(ENVIRONMENTS.GET.id)
}),
response: {
200: z.object({
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.getEnvironmentById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.envId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.GET_ENVIRONMENT,
metadata: {
id: environment.id
}
}
});
return { environment };
}
});
server.route({
method: "GET",
url: "/environments/:envId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Environments],
description: "Get Environment by ID",
security: [
{
bearerAuth: []
}
],
params: z.object({
envId: z.string().trim().describe(ENVIRONMENTS.GET.id)
}),
response: {
200: z.object({
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.getEnvironmentById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.envId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.GET_ENVIRONMENT,
metadata: {
id: environment.id
}
}
});
return { environment };
}
});
server.route({
method: "POST",
url: "/:workspaceId/environments",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Environments],
description: "Create environment",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(ENVIRONMENTS.CREATE.projectId)
}),
body: z.object({
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
position: z.number().min(1).optional().describe(ENVIRONMENTS.CREATE.position),
slug: slugSchema({ max: 64 }).describe(ENVIRONMENTS.CREATE.slug)
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.createEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectId: req.params.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.CREATE_ENVIRONMENT,
metadata: {
name: environment.name,
slug: environment.slug
}
}
});
return {
message: "Successfully created new environment",
workspace: req.params.workspaceId,
environment
};
}
});
server.route({
method: "PATCH",
url: "/:workspaceId/environments/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Environments],
description: "Update environment",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(ENVIRONMENTS.UPDATE.projectId),
id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id)
}),
body: z.object({
slug: slugSchema({ max: 64 }).optional().describe(ENVIRONMENTS.UPDATE.slug),
name: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.name),
position: z.number().optional().describe(ENVIRONMENTS.UPDATE.position)
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { environment, old } = await server.services.projectEnv.updateEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
id: req.params.id,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.UPDATE_ENVIRONMENT,
metadata: {
oldName: old.name,
oldSlug: old.slug,
oldPos: old.position,
newName: environment.name,
newSlug: environment.slug,
newPos: environment.position
}
}
});
return {
message: "Successfully updated environment",
workspace: req.params.workspaceId,
environment
};
}
});
server.route({
method: "DELETE",
url: "/:workspaceId/environments/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Environments],
description: "Delete environment",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(ENVIRONMENTS.DELETE.projectId),
id: z.string().trim().describe(ENVIRONMENTS.DELETE.id)
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.deleteEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
id: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.DELETE_ENVIRONMENT,
metadata: {
slug: environment.slug,
name: environment.name
}
}
});
return {
message: "Successfully deleted environment",
workspace: req.params.workspaceId,
environment
};
}
});
};
@@ -0,0 +1,378 @@
import { z } from "zod";
import {
OrgMembershipsSchema,
ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, PROJECT_USERS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:workspaceId/memberships",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectUsers],
description: "Return project user memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.projectId)
}),
response: {
200: z.object({
memberships: ProjectMembershipsSchema.extend({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true,
username: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
)
})
.omit({ updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
});
return { memberships };
}
});
server.route({
method: "GET",
url: "/:workspaceId/memberships/:membershipId",
config: {
rateLimit: readLimit
},
schema: {
description: "Return project user membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.projectId),
membershipId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.membershipId)
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema.extend({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true,
username: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
)
}).omit({ updatedAt: true })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
id: req.params.membershipId
});
return { membership };
}
});
server.route({
method: "POST",
url: "/:workspaceId/memberships/details",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectUsers],
description: "Return project user memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.projectId)
}),
body: z.object({
username: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.username)
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema.extend({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
)
}).omit({ createdAt: true, updatedAt: true })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipByUsername({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
username: req.body.username
});
return { membership };
}
});
server.route({
method: "POST",
url: "/:workspaceId/memberships",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
members: z
.object({
orgMembershipId: z.string().trim(),
workspaceEncryptedKey: z.string().trim(),
workspaceEncryptedNonce: z.string().trim()
})
.array()
.min(1)
}),
response: {
200: z.object({
success: z.boolean(),
data: OrgMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.projectMembership.addUsersToProject({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
members: req.body.members
});
await server.services.auditLog.createAuditLog({
projectId: req.params.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: data.map(({ userId }) => ({
userId: userId || "",
email: ""
}))
}
});
return { data, success: true };
}
});
server.route({
method: "PATCH",
url: "/:workspaceId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectUsers],
description: "Update project user membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.projectId),
membershipId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.membershipId)
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string(),
isTemporary: z.literal(false).default(false)
}),
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
])
)
.min(1)
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least one long lived role is required")
.describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.roles)
}),
response: {
200: z.object({
roles: ProjectUserMembershipRolesSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const roles = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId,
roles: req.body.roles
});
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId: req.params.workspaceId,
// event: {
// type: EventType.UPDATE_USER_WORKSPACE_ROLE,
// metadata: {
// userId: membership.userId,
// newRole: req.body.role,
// oldRole: membership.role,
// email: ""
// }
// }
// });
return { roles };
}
});
server.route({
method: "DELETE",
url: "/:workspaceId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Delete project user membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim(),
membershipId: z.string().trim()
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
email: ""
}
}
});
return { membership };
}
});
};
@@ -0,0 +1,728 @@
import { z } from "zod";
import {
IntegrationsSchema,
ProjectRolesSchema,
ProjectSlackConfigsSchema,
ProjectSshConfigsSchema,
ProjectType,
SortDirection
} from "@app/db/schemas";
import { ProjectMicrosoftTeamsConfigsSchema } from "@app/db/schemas/project-microsoft-teams-configs";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { re2Validator } from "@app/lib/zod";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns";
import { ProjectFilterType, SearchProjectSortBy } from "@app/services/project/project-types";
import { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators";
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas";
const projectWithEnv = SanitizedProjectSchema.merge(
z.object({
_id: z.string(),
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array()
})
);
export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
includeRoles: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true"),
type: z.nativeEnum(ProjectType).optional()
}),
response: {
200: z.object({
workspaces: projectWithEnv
.extend({
roles: ProjectRolesSchema.array().optional()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaces = await server.services.project.getProjects({
includeRoles: req.query.includeRoles,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
type: req.query.type
});
return { workspaces };
}
});
server.route({
method: "GET",
url: "/:workspaceId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Projects],
description: "Get project",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.GET.projectId)
}),
response: {
200: z.object({
workspace: projectWithEnv.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspace = await server.services.project.getAProject({
filter: {
type: ProjectFilterType.ID,
projectId: req.params.workspaceId
},
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
return { workspace };
}
});
server.route({
method: "DELETE",
url: "/:workspaceId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Projects],
description: "Delete project",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.DELETE.projectId)
}),
response: {
200: z.object({
workspace: SanitizedProjectSchema.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspace = await server.services.project.deleteProject({
filter: {
type: ProjectFilterType.ID,
projectId: req.params.workspaceId
},
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: req.params.workspaceId,
event: {
type: EventType.DELETE_PROJECT,
metadata: workspace
}
});
return { workspace };
}
});
server.route({
method: "PATCH",
url: "/:workspaceId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Projects],
description: "Update project",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.UPDATE.projectId)
}),
body: z.object({
name: z
.string()
.trim()
.max(64, { message: "Name must be 64 or fewer characters" })
.optional()
.describe(PROJECTS.UPDATE.name),
description: z
.string()
.trim()
.max(256, { message: "Description must be 256 or fewer characters" })
.optional()
.describe(PROJECTS.UPDATE.projectDescription),
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization),
hasDeleteProtection: z.boolean().optional().describe(PROJECTS.UPDATE.hasDeleteProtection),
slug: z
.string()
.trim()
.max(64, { message: "Slug must be 64 characters or fewer" })
.refine(re2Validator(/^[a-z0-9]+(?:[_-][a-z0-9]+)*$/), {
message:
"Project slug can only contain lowercase letters and numbers, with optional single hyphens (-) or underscores (_) between words. Cannot start or end with a hyphen or underscore."
})
.optional()
.describe(PROJECTS.UPDATE.slug),
secretSharing: z.boolean().optional().describe(PROJECTS.UPDATE.secretSharing),
showSnapshotsLegacy: z.boolean().optional().describe(PROJECTS.UPDATE.showSnapshotsLegacy),
defaultProduct: z.nativeEnum(ProjectType).optional().describe(PROJECTS.UPDATE.defaultProduct),
secretDetectionIgnoreValues: z
.array(z.string())
.optional()
.describe(PROJECTS.UPDATE.secretDetectionIgnoreValues)
}),
response: {
200: z.object({
workspace: SanitizedProjectSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspace = await server.services.project.updateProject({
filter: {
type: ProjectFilterType.ID,
projectId: req.params.workspaceId
},
update: {
name: req.body.name,
description: req.body.description,
autoCapitalization: req.body.autoCapitalization,
defaultProduct: req.body.defaultProduct,
hasDeleteProtection: req.body.hasDeleteProtection,
slug: req.body.slug,
secretSharing: req.body.secretSharing,
showSnapshotsLegacy: req.body.showSnapshotsLegacy,
secretDetectionIgnoreValues: req.body.secretDetectionIgnoreValues
},
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: req.params.workspaceId,
event: {
type: EventType.UPDATE_PROJECT,
metadata: req.body
}
});
return {
workspace
};
}
});
server.route({
method: "PUT",
url: "/:workspaceSlug/audit-logs-retention",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceSlug: z.string().trim()
}),
body: z.object({
auditLogsRetentionDays: z.number().min(0)
}),
response: {
200: z.object({
message: z.string(),
workspace: SanitizedProjectSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspace = await server.services.project.updateAuditLogsRetention({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
filter: {
type: ProjectFilterType.SLUG,
slug: req.params.workspaceSlug,
orgId: req.permission.orgId
},
auditLogsRetentionDays: req.body.auditLogsRetentionDays
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: workspace.id,
event: {
type: EventType.UPDATE_PROJECT,
metadata: req.body
}
});
return {
message: "Successfully updated project's audit logs retention period",
workspace
};
}
});
server.route({
method: "GET",
url: "/:workspaceId/integrations",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Integrations],
description: "List integrations for a project.",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION.projectId)
}),
response: {
200: z.object({
integrations: IntegrationsSchema.merge(
z.object({
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
})
).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const integrations = await server.services.integration.listIntegrationByProject({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
});
return { integrations };
}
});
server.route({
method: "GET",
url: "/:workspaceId/authorizations",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Integrations],
description: "List integration auth objects for a workspace.",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION_AUTHORIZATION.projectId)
}),
response: {
200: z.object({
authorizations: integrationAuthPubSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const authorizations = await server.services.integrationAuth.listIntegrationAuthByProjectId({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
});
return { authorizations };
}
});
server.route({
method: "GET",
url: "/:workspaceId/ssh-config",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: ProjectSshConfigsSchema.pick({
id: true,
createdAt: true,
updatedAt: true,
projectId: true,
defaultUserSshCaId: true,
defaultHostSshCaId: true
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const sshConfig = await server.services.project.getProjectSshConfig({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: sshConfig.projectId,
event: {
type: EventType.GET_PROJECT_SSH_CONFIG,
metadata: {
id: sshConfig.id,
projectId: sshConfig.projectId
}
}
});
return sshConfig;
}
});
server.route({
method: "PATCH",
url: "/:workspaceId/ssh-config",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
defaultUserSshCaId: z.string().optional(),
defaultHostSshCaId: z.string().optional()
}),
response: {
200: ProjectSshConfigsSchema.pick({
id: true,
createdAt: true,
updatedAt: true,
projectId: true,
defaultUserSshCaId: true,
defaultHostSshCaId: true
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const sshConfig = await server.services.project.updateProjectSshConfig({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: sshConfig.projectId,
event: {
type: EventType.UPDATE_PROJECT_SSH_CONFIG,
metadata: {
id: sshConfig.id,
projectId: sshConfig.projectId,
defaultUserSshCaId: sshConfig.defaultUserSshCaId,
defaultHostSshCaId: sshConfig.defaultHostSshCaId
}
}
});
return sshConfig;
}
});
server.route({
method: "GET",
url: "/:workspaceId/workflow-integration-config/:integration",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim(),
integration: z.nativeEnum(WorkflowIntegration)
}),
response: {
200: z.discriminatedUnion("integration", [
ProjectSlackConfigsSchema.pick({
id: true,
isAccessRequestNotificationEnabled: true,
accessRequestChannels: true,
isSecretRequestNotificationEnabled: true,
secretRequestChannels: true
}).merge(
z.object({
integration: z.literal(WorkflowIntegration.SLACK),
integrationId: z.string()
})
),
ProjectMicrosoftTeamsConfigsSchema.pick({
id: true,
isAccessRequestNotificationEnabled: true,
accessRequestChannels: true,
isSecretRequestNotificationEnabled: true,
secretRequestChannels: true
}).merge(
z.object({
integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS),
integrationId: z.string()
})
)
])
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const config = await server.services.project.getProjectWorkflowIntegrationConfig({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
integration: req.params.integration
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.GET_PROJECT_WORKFLOW_INTEGRATION_CONFIG,
metadata: {
id: config.id,
integration: config.integration
}
}
});
return config;
}
});
server.route({
method: "DELETE",
url: "/:projectId/workflow-integration/:integration/:integrationId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim(),
integration: z.nativeEnum(WorkflowIntegration),
integrationId: z.string()
}),
response: {
200: z.object({
integrationConfig: z.object({
id: z.string()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const deletedIntegration = await server.services.project.deleteProjectWorkflowIntegration({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
integration: req.params.integration,
integrationId: req.params.integrationId
});
return {
integrationConfig: deletedIntegration
};
}
});
server.route({
method: "PUT",
url: "/:workspaceId/workflow-integration",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.discriminatedUnion("integration", [
z.object({
integration: z.literal(WorkflowIntegration.SLACK),
integrationId: z.string(),
accessRequestChannels: validateSlackChannelsField,
secretRequestChannels: validateSlackChannelsField,
isAccessRequestNotificationEnabled: z.boolean(),
isSecretRequestNotificationEnabled: z.boolean()
}),
z.object({
integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS),
integrationId: z.string(),
accessRequestChannels: validateMicrosoftTeamsChannelsSchema,
secretRequestChannels: validateMicrosoftTeamsChannelsSchema,
isAccessRequestNotificationEnabled: z.boolean(),
isSecretRequestNotificationEnabled: z.boolean()
})
]),
response: {
200: z.discriminatedUnion("integration", [
ProjectSlackConfigsSchema.pick({
id: true,
isAccessRequestNotificationEnabled: true,
accessRequestChannels: true,
isSecretRequestNotificationEnabled: true,
secretRequestChannels: true
}).merge(
z.object({
integration: z.literal(WorkflowIntegration.SLACK),
integrationId: z.string()
})
),
ProjectMicrosoftTeamsConfigsSchema.pick({
id: true,
isAccessRequestNotificationEnabled: true,
isSecretRequestNotificationEnabled: true
}).merge(
z.object({
integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS),
integrationId: z.string(),
accessRequestChannels: validateMicrosoftTeamsChannelsSchema,
secretRequestChannels: validateMicrosoftTeamsChannelsSchema
})
)
])
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workflowIntegrationConfig = await server.services.project.updateProjectWorkflowIntegration({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.UPDATE_PROJECT_WORKFLOW_INTEGRATION_CONFIG,
metadata: {
id: workflowIntegrationConfig.id,
integrationId: workflowIntegrationConfig.integrationId,
integration: workflowIntegrationConfig.integration,
isAccessRequestNotificationEnabled: workflowIntegrationConfig.isAccessRequestNotificationEnabled,
accessRequestChannels: workflowIntegrationConfig.accessRequestChannels,
isSecretRequestNotificationEnabled: workflowIntegrationConfig.isSecretRequestNotificationEnabled,
secretRequestChannels: workflowIntegrationConfig.secretRequestChannels
}
}
});
return workflowIntegrationConfig;
}
});
server.route({
method: "POST",
url: "/search",
config: {
rateLimit: readLimit
},
schema: {
body: z.object({
limit: z.number().default(100),
offset: z.number().default(0),
type: z.nativeEnum(ProjectType).optional(),
orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME),
orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC),
name: z
.string()
.trim()
.refine((val) => characterValidator([CharacterType.AlphaNumeric, CharacterType.Hyphen])(val), {
message: "Invalid pattern: only alphanumeric characters, - are allowed."
})
.optional()
}),
response: {
200: z.object({
projects: SanitizedProjectSchema.extend({ isMember: z.boolean() }).array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { docs: projects, totalCount } = await server.services.project.searchProjects({
permission: req.permission,
...req.body
});
return { projects, totalCount };
}
});
};
@@ -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 registerDeprecatedSecretFolderRouter = 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 };
}
});
};
@@ -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 registerDeprecatedSecretImportRouter = 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 };
}
});
};
@@ -0,0 +1,213 @@
import { z } from "zod";
import { SecretTagsSchema } from "@app/db/schemas";
import { ApiDocsTags, SECRET_TAGS } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerDeprecatedSecretTagRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:projectId/tags",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.LIST.projectId)
}),
response: {
200: z.object({
workspaceTags: SecretTagsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTags = await server.services.secretTag.getProjectTags({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
});
return { workspaceTags };
}
});
server.route({
method: "GET",
url: "/:projectId/tags/:tagId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_ID.projectId),
tagId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_ID.tagId)
}),
response: {
200: z.object({
// akhilmhdh: for terraform backward compatiability
workspaceTag: SecretTagsSchema.extend({ name: z.string() })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.getTagById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.tagId
});
return { workspaceTag };
}
});
server.route({
method: "GET",
url: "/:projectId/tags/slug/:tagSlug",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_SLUG.projectId),
tagSlug: z.string().trim().describe(SECRET_TAGS.GET_TAG_BY_SLUG.tagSlug)
}),
response: {
200: z.object({
// akhilmhdh: for terraform backward compatiability
workspaceTag: SecretTagsSchema.extend({ name: z.string() })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.getTagBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.params.tagSlug,
projectId: req.params.projectId
});
return { workspaceTag };
}
});
server.route({
method: "POST",
url: "/:projectId/tags",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId)
}),
body: z.object({
slug: slugSchema({ max: 64 }).describe(SECRET_TAGS.CREATE.slug),
color: z.string().trim().describe(SECRET_TAGS.CREATE.color)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.createTag({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
...req.body
});
return { workspaceTag };
}
});
server.route({
method: "PATCH",
url: "/:projectId/tags/:tagId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.UPDATE.projectId),
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
}),
body: z.object({
slug: slugSchema({ max: 64 }).describe(SECRET_TAGS.UPDATE.slug),
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.updateTag({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
id: req.params.tagId
});
return { workspaceTag };
}
});
server.route({
method: "DELETE",
url: "/:projectId/tags/:tagId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Folders],
params: z.object({
projectId: z.string().trim().describe(SECRET_TAGS.DELETE.projectId),
tagId: z.string().trim().describe(SECRET_TAGS.DELETE.tagId)
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.deleteTag({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.tagId
});
return { workspaceTag };
}
});
};
+27 -8
View File
@@ -13,8 +13,15 @@ import { registerCaRouter } from "./certificate-authority-router";
import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers";
import { registerCertRouter } from "./certificate-router";
import { registerCertificateTemplateRouter } from "./certificate-template-router";
import { registerDeprecatedProjectEnvRouter } from "./deprecated-project-env-router";
import { registerDeprecatedProjectMembershipRouter } from "./deprecated-project-membership-router";
import { registerDeprecatedProjectRouter } from "./deprecated-project-router";
import { registerDeprecatedSecretFolderRouter } from "./deprecated-secret-folder-router";
import { registerDeprecatedSecretImportRouter } from "./deprecated-secret-import-router";
import { registerDeprecatedSecretTagRouter } from "./deprecated-secret-tag-router";
import { registerEventRouter } from "./event-router";
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
import { registerGroupProjectRouter } from "./group-project-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAliCloudAuthRouter } from "./identity-alicloud-auth-router";
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
@@ -25,6 +32,7 @@ import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-rou
import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router";
import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router";
import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router";
import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerIdentityRouter } from "./identity-router";
import { registerIdentityTlsCertAuthRouter } from "./identity-tls-cert-auth-router";
import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router";
@@ -45,8 +53,6 @@ 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 { registerSecretRequestsRouter } from "./secret-requests-router";
import { registerSecretSharingRouter } from "./secret-sharing-router";
import { registerSecretTagRouter } from "./secret-tag-router";
@@ -87,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(registerDeprecatedSecretImportRouter, { prefix: "/secret-imports" });
await server.register(registerDeprecatedSecretFolderRouter, { prefix: "/folders" });
await server.register(
async (workflowIntegrationRouter) => {
@@ -101,15 +107,28 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register(
async (projectRouter) => {
await projectRouter.register(registerProjectRouter);
await projectRouter.register(registerProjectEnvRouter);
await projectRouter.register(registerDeprecatedProjectRouter);
await projectRouter.register(registerDeprecatedProjectEnvRouter);
// depreciated completed in use
await projectRouter.register(registerProjectKeyRouter);
await projectRouter.register(registerProjectMembershipRouter);
await projectRouter.register(registerSecretTagRouter);
await projectRouter.register(registerDeprecatedProjectMembershipRouter);
await projectRouter.register(registerDeprecatedSecretTagRouter);
},
{ prefix: "/workspace" }
);
await server.register(
async (projectRouter) => {
await projectRouter.register(registerProjectRouter);
await projectRouter.register(registerProjectMembershipRouter);
await projectRouter.register(registerProjectEnvRouter);
await projectRouter.register(registerSecretTagRouter);
await projectRouter.register(registerGroupProjectRouter);
await projectRouter.register(registerIdentityProjectRouter);
},
{ prefix: "/projects" }
);
await server.register(
async (pkiRouter) => {
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
@@ -11,58 +11,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:workspaceId/environments/:envId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Environments],
description: "Get Environment",
security: [
{
bearerAuth: []
}
],
params: z.object({
// NOTE(daniel): workspaceId isn't used, but we need to keep it for backwards compatibility. The endpoint defined below, uses no project ID, and is takes a pure environment ID.
workspaceId: z.string().trim().describe(ENVIRONMENTS.GET.workspaceId),
envId: z.string().trim().describe(ENVIRONMENTS.GET.id)
}),
response: {
200: z.object({
environment: ProjectEnvironmentsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const environment = await server.services.projectEnv.getEnvironmentById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.envId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.GET_ENVIRONMENT,
metadata: {
id: environment.id
}
}
});
return { environment };
}
});
server.route({
method: "GET",
url: "/environments/:envId",
url: "/:projectId/environments/:envId",
config: {
rateLimit: readLimit
},
@@ -76,7 +25,8 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
envId: z.string().trim().describe(ENVIRONMENTS.GET.id)
envId: z.string().trim().describe(ENVIRONMENTS.GET.id),
projectId: z.string().trim().describe(ENVIRONMENTS.GET.projectId)
}),
response: {
200: z.object({
@@ -111,7 +61,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:workspaceId/environments",
url: "/:projectId/environments",
config: {
rateLimit: writeLimit
},
@@ -125,7 +75,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim().describe(ENVIRONMENTS.CREATE.workspaceId)
projectId: z.string().trim().describe(ENVIRONMENTS.CREATE.projectId)
}),
body: z.object({
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
@@ -135,7 +85,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
projectId: z.string(),
environment: ProjectEnvironmentsSchema
})
}
@@ -147,7 +97,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectId: req.params.workspaceId,
projectId: req.params.projectId,
...req.body
});
@@ -164,7 +114,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
});
return {
message: "Successfully created new environment",
workspace: req.params.workspaceId,
projectId: req.params.projectId,
environment
};
}
@@ -172,7 +122,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
server.route({
method: "PATCH",
url: "/:workspaceId/environments/:id",
url: "/:projectId/environments/:id",
config: {
rateLimit: writeLimit
},
@@ -186,7 +136,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim().describe(ENVIRONMENTS.UPDATE.workspaceId),
projectId: z.string().trim().describe(ENVIRONMENTS.UPDATE.projectId),
id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id)
}),
body: z.object({
@@ -197,7 +147,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
projectId: z.string(),
environment: ProjectEnvironmentsSchema
})
}
@@ -209,7 +159,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
projectId: req.params.projectId,
id: req.params.id,
...req.body
});
@@ -232,7 +182,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
return {
message: "Successfully updated environment",
workspace: req.params.workspaceId,
projectId: req.params.projectId,
environment
};
}
@@ -240,7 +190,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
server.route({
method: "DELETE",
url: "/:workspaceId/environments/:id",
url: "/:projectId/environments/:id",
config: {
rateLimit: writeLimit
},
@@ -254,13 +204,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim().describe(ENVIRONMENTS.DELETE.workspaceId),
projectId: z.string().trim().describe(ENVIRONMENTS.DELETE.projectId),
id: z.string().trim().describe(ENVIRONMENTS.DELETE.id)
}),
response: {
200: z.object({
message: z.string(),
workspace: z.string(),
projectId: z.string(),
environment: ProjectEnvironmentsSchema
})
}
@@ -272,7 +222,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
projectId: req.params.projectId,
id: req.params.id
});
@@ -290,7 +240,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
return {
message: "Successfully deleted environment",
workspace: req.params.workspaceId,
projectId: req.params.projectId,
environment
};
}
@@ -1,7 +1,8 @@
import { z } from "zod";
import {
OrgMembershipsSchema,
OrgMembershipRole,
ProjectMembershipRole,
ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema,
UserEncryptionKeysSchema,
@@ -18,7 +19,7 @@ import { ProjectUserMembershipTemporaryMode } from "@app/services/project-member
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:workspaceId/memberships",
url: "/:projectId/memberships",
config: {
rateLimit: readLimit
},
@@ -32,7 +33,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.workspaceId)
projectId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.projectId)
}),
response: {
200: z.object({
@@ -71,7 +72,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
projectId: req.params.projectId
});
return { memberships };
}
@@ -79,7 +80,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
server.route({
method: "GET",
url: "/:workspaceId/memberships/:membershipId",
url: "/:projectId/memberships/:membershipId",
config: {
rateLimit: readLimit
},
@@ -91,7 +92,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.workspaceId),
projectId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.projectId),
membershipId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.membershipId)
}),
response: {
@@ -129,7 +130,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
projectId: req.params.projectId,
id: req.params.membershipId
});
return { membership };
@@ -138,7 +139,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
server.route({
method: "POST",
url: "/:workspaceId/memberships/details",
url: "/:projectId/memberships/details",
config: {
rateLimit: readLimit
},
@@ -152,7 +153,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.workspaceId)
projectId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.projectId)
}),
body: z.object({
username: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.username)
@@ -191,7 +192,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
projectId: req.params.projectId,
username: req.body.username
});
return { membership };
@@ -200,61 +201,83 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
server.route({
method: "POST",
url: "/:workspaceId/memberships",
url: "/:projectId/memberships",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectUsers],
description: "Invite members to project",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim()
projectId: z.string().describe(PROJECT_USERS.INVITE_MEMBER.projectId)
}),
body: z.object({
members: z
.object({
orgMembershipId: z.string().trim(),
workspaceEncryptedKey: z.string().trim(),
workspaceEncryptedNonce: z.string().trim()
})
emails: z
.string()
.email()
.array()
.min(1)
.default([])
.describe(PROJECT_USERS.INVITE_MEMBER.emails)
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
usernames: z
.string()
.array()
.default([])
.describe(PROJECT_USERS.INVITE_MEMBER.usernames)
.refine((val) => val.every((el) => el === el.toLowerCase()), "Username must be lowercase"),
roleSlugs: z.string().array().min(1).optional().describe(PROJECT_USERS.INVITE_MEMBER.roleSlugs)
}),
response: {
200: z.object({
success: z.boolean(),
data: OrgMembershipsSchema.array()
memberships: ProjectMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.projectMembership.addUsersToProject({
actorId: req.permission.id,
actor: req.permission.type,
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
members: req.body.members
actor: req.permission.type,
inviteeEmails: usernamesAndEmails,
orgId: req.permission.orgId,
organizationRoleSlug: OrgMembershipRole.NoAccess,
projects: [
{
id: req.params.projectId,
projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member]
}
]
});
await server.services.auditLog.createAuditLog({
projectId: req.params.workspaceId,
projectId: req.params.projectId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
metadata: data.map(({ userId }) => ({
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: memberships.map(({ userId, id }) => ({
userId: userId || "",
membershipId: id,
email: ""
}))
}
});
return { data, success: true };
return { memberships };
}
});
server.route({
method: "PATCH",
url: "/:workspaceId/memberships/:membershipId",
url: "/:projectId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
@@ -268,7 +291,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.workspaceId),
projectId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.projectId),
membershipId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.membershipId)
}),
body: z.object({
@@ -305,31 +328,87 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
projectId: req.params.projectId,
membershipId: req.params.membershipId,
roles: req.body.roles
});
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId: req.params.workspaceId,
// event: {
// type: EventType.UPDATE_USER_WORKSPACE_ROLE,
// metadata: {
// userId: membership.userId,
// newRole: req.body.role,
// oldRole: membership.role,
// email: ""
// }
// }
// });
return { roles };
}
});
server.route({
method: "DELETE",
url: "/:workspaceId/memberships/:membershipId",
url: "/:projectId/memberships",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectUsers],
description: "Remove members from project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().describe(PROJECT_USERS.REMOVE_MEMBER.projectId)
}),
body: z.object({
emails: z
.string()
.email()
.array()
.default([])
.describe(PROJECT_USERS.REMOVE_MEMBER.emails)
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
usernames: z
.string()
.array()
.default([])
.describe(PROJECT_USERS.REMOVE_MEMBER.usernames)
.refine((val) => val.every((el) => el === el.toLowerCase()), "Username must be lowercase")
}),
response: {
200: z.object({
memberships: ProjectMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.deleteProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
emails: req.body.emails,
usernames: req.body.usernames
});
for (const membership of memberships) {
// eslint-disable-next-line no-await-in-loop
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.projectId,
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
email: ""
}
}
});
}
return { memberships };
}
});
server.route({
method: "DELETE",
url: "/:projectId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
@@ -341,7 +420,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().trim(),
projectId: z.string().trim(),
membershipId: z.string().trim()
}),
response: {
@@ -357,15 +436,15 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
projectId: req.params.projectId,
membershipId: req.params.membershipId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
projectId: req.params.projectId,
event: {
type: EventType.REMOVE_WORKSPACE_MEMBER,
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
email: ""
@@ -378,13 +457,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
server.route({
method: "DELETE",
url: "/:workspaceId/leave",
url: "/:projectId/leave",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
projectId: z.string().trim()
}),
response: {
200: z.object({
@@ -398,7 +477,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
const membership = await server.services.projectMembership.leaveProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId
projectId: req.params.projectId
});
return { membership };
}
File diff suppressed because it is too large Load Diff
@@ -22,20 +22,20 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
workspaceTags: SecretTagsSchema.array()
tags: SecretTagsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTags = await server.services.secretTag.getProjectTags({
const tags = await server.services.secretTag.getProjectTags({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
});
return { workspaceTags };
return { tags };
}
});
@@ -55,20 +55,20 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
// akhilmhdh: for terraform backward compatiability
workspaceTag: SecretTagsSchema.extend({ name: z.string() })
tag: SecretTagsSchema.extend({ name: z.string() })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.getTagById({
const tag = await server.services.secretTag.getTagById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.tagId
});
return { workspaceTag };
return { tag };
}
});
@@ -88,13 +88,13 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
// akhilmhdh: for terraform backward compatiability
workspaceTag: SecretTagsSchema.extend({ name: z.string() })
tag: SecretTagsSchema.extend({ name: z.string() })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.getTagBySlug({
const tag = await server.services.secretTag.getTagBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
@@ -102,7 +102,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
slug: req.params.tagSlug,
projectId: req.params.projectId
});
return { workspaceTag };
return { tag };
}
});
@@ -124,13 +124,13 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
tag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.createTag({
const tag = await server.services.secretTag.createTag({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
@@ -138,7 +138,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
projectId: req.params.projectId,
...req.body
});
return { workspaceTag };
return { tag };
}
});
@@ -161,13 +161,13 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
tag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.updateTag({
const tag = await server.services.secretTag.updateTag({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
@@ -175,7 +175,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
...req.body,
id: req.params.tagId
});
return { workspaceTag };
return { tag };
}
});
@@ -194,20 +194,20 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
workspaceTag: SecretTagsSchema
tag: SecretTagsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaceTag = await server.services.secretTag.deleteTag({
const tag = await server.services.secretTag.deleteTag({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.tagId
});
return { workspaceTag };
return { tag };
}
});
};
@@ -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 };
}
@@ -0,0 +1,363 @@
import { z } from "zod";
import {
GroupProjectMembershipsSchema,
GroupsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema,
UsersSchema
} from "@app/db/schemas";
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/groups/:groupIdOrName",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Add group to project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.projectId),
groupIdOrName: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.groupIdOrName)
}),
body: z
.object({
role: z
.string()
.trim()
.min(1)
.default(ProjectMembershipRole.NoAccess)
.describe(PROJECTS.ADD_GROUP_TO_PROJECT.role),
roles: z
.array(
z.union([
z.object({
role: z.string(),
isTemporary: z.literal(false).default(false)
}),
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
])
)
.optional()
})
.refine((data) => data.role || data.roles, {
message: "Either role or roles must be present",
path: ["role", "roles"]
}),
response: {
200: z.object({
groupMembership: GroupProjectMembershipsSchema
})
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.addGroupToProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
roles: req.body.roles || [{ role: req.body.role }],
projectId: req.params.projectId,
groupIdOrName: req.params.groupIdOrName
});
return { groupMembership };
}
});
server.route({
method: "PATCH",
url: "/:projectId/groups/:groupId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Update group in project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.projectId),
groupId: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.groupId)
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string(),
isTemporary: z.literal(false).default(false)
}),
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
])
)
.min(1)
.describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.roles)
}),
response: {
200: z.object({
roles: ProjectUserMembershipRolesSchema.array()
})
}
},
handler: async (req) => {
const roles = await server.services.groupProject.updateGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
groupId: req.params.groupId,
roles: req.body.roles
});
return { roles };
}
});
server.route({
method: "DELETE",
url: "/:projectId/groups/:groupId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Remove group from project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.projectId),
groupId: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.groupId)
}),
response: {
200: z.object({
groupMembership: GroupProjectMembershipsSchema
})
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.removeGroupFromProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupId: req.params.groupId,
projectId: req.params.projectId
});
return { groupMembership };
}
});
server.route({
method: "GET",
url: "/:projectId/groups",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Return list of groups in project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_GROUPS_IN_PROJECT.projectId)
}),
response: {
200: z.object({
groupMemberships: z
.object({
id: z.string(),
groupId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
group: GroupsSchema.pick({ name: true, id: true, slug: true })
})
.array()
})
}
},
handler: async (req) => {
const groupMemberships = await server.services.groupProject.listGroupsInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
});
return { groupMemberships };
}
});
server.route({
method: "GET",
url: "/:projectId/groups/:groupId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Return project group",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim(),
groupId: z.string().trim()
}),
response: {
200: z.object({
groupMembership: z.object({
id: z.string(),
groupId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
group: GroupsSchema.pick({ name: true, id: true, slug: true })
})
})
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.getGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.params
});
return { groupMembership };
}
});
server.route({
method: "GET",
url: "/:projectId/groups/:groupId/users",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Return project group users",
params: z.object({
projectId: z.string().trim().describe(GROUPS.LIST_USERS.projectId),
groupId: z.string().trim().describe(GROUPS.LIST_USERS.id)
}),
querystring: z.object({
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username),
search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search),
filter: z.nativeEnum(EFilterReturnedUsers).optional().describe(GROUPS.LIST_USERS.filterUsers)
}),
response: {
200: z.object({
users: UsersSchema.pick({
email: true,
username: true,
firstName: true,
lastName: true,
id: true
})
.merge(
z.object({
isPartOfGroup: z.boolean(),
joinedGroupAt: z.date().nullable()
})
)
.array(),
totalCount: z.number()
})
}
},
handler: async (req) => {
const { users, totalCount } = await server.services.groupProject.listProjectGroupUsers({
id: req.params.groupId,
projectId: req.params.projectId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
});
return { users, totalCount };
}
});
};
@@ -0,0 +1,418 @@
import { z } from "zod";
import {
IdentitiesSchema,
IdentityProjectMembershipsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
} from "@app/db/schemas";
import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { OrderByDirection } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
import { SanitizedProjectSchema } from "../sanitizedSchemas";
export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Create project identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim(),
identityId: z.string().trim()
}),
body: z.object({
// @depreciated
role: z.string().trim().optional().default(ProjectMembershipRole.NoAccess),
roles: z
.array(
z.union([
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
}),
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
})
])
)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.description)
.optional()
}),
response: {
200: z.object({
identityMembership: IdentityProjectMembershipsSchema
})
}
},
handler: async (req) => {
const { role, roles } = req.body;
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
const identityMembership = await server.services.identityProject.createProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: roles || [{ role }]
});
return { identityMembership };
}
});
server.route({
method: "PATCH",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Update project identity memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.identityId)
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary)
}),
z.object({
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryRange),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime)
})
])
)
.min(1)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.description)
}),
response: {
200: z.object({
roles: ProjectUserMembershipRolesSchema.array()
})
}
},
handler: async (req) => {
const roles = await server.services.identityProject.updateProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: req.body.roles
});
return { roles };
}
});
server.route({
method: "DELETE",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Delete project identity memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId)
}),
response: {
200: z.object({
identityMembership: IdentityProjectMembershipsSchema
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.deleteProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId
});
return { identityMembership };
}
});
server.route({
method: "GET",
url: "/:projectId/identity-memberships",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Return project identity memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
}),
querystring: z.object({
offset: z.coerce
.number()
.min(0)
.default(0)
.describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.offset)
.optional(),
limit: z.coerce
.number()
.min(1)
.max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added
.default(100)
.describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.limit)
.optional(),
orderBy: z
.nativeEnum(ProjectIdentityOrderBy)
.default(ProjectIdentityOrderBy.Name)
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy)
.optional(),
orderDirection: z
.nativeEnum(OrderByDirection)
.default(OrderByDirection.ASC)
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection)
.optional(),
search: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.search).optional()
}),
response: {
200: z.object({
identityMemberships: z
.object({
id: z.string(),
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({
authMethods: z.array(z.string())
}),
project: SanitizedProjectSchema.pick({ name: true, id: true })
})
.array(),
totalCount: z.number()
})
}
},
handler: async (req) => {
const { identityMemberships, totalCount } = await server.services.identityProject.listProjectIdentities({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
limit: req.query.limit,
offset: req.query.offset,
orderBy: req.query.orderBy,
orderDirection: req.query.orderDirection,
search: req.query.search
});
return { identityMemberships, totalCount };
}
});
server.route({
method: "GET",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Return project identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId)
}),
response: {
200: z.object({
identityMembership: z.object({
id: z.string(),
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({
authMethods: z.array(z.string())
}),
project: SanitizedProjectSchema.pick({ name: true, id: true })
})
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.getProjectIdentityByIdentityId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
identityId: req.params.identityId
});
return { identityMembership };
}
});
server.route({
method: "GET",
url: "/identity-memberships/:identityMembershipId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
params: z.object({
identityMembershipId: z.string().trim()
}),
response: {
200: z.object({
identityMembership: z.object({
id: z.string(),
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({
authMethods: z.array(z.string())
}),
project: SanitizedProjectSchema.pick({ name: true, id: true })
})
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.getProjectIdentityByMembershipId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityMembershipId: req.params.identityMembershipId
});
return { identityMembership };
}
});
};
@@ -7,7 +7,7 @@ import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/memberships",
@@ -71,7 +71,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
projectId: req.params.projectId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: memberships.map(({ userId, id }) => ({
userId: userId || "",
membershipId: id,
@@ -141,7 +141,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
...req.auditLogInfo,
projectId: req.params.projectId,
event: {
type: EventType.REMOVE_WORKSPACE_MEMBER,
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
email: ""
@@ -35,7 +35,8 @@ const projectWithEnv = SanitizedProjectSchema.extend({
kmsSecretManagerKeyId: z.string().nullable().optional()
});
export const registerProjectRouter = async (server: FastifyZodProvider) => {
export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider) => {
// depreciated
/* Get project key */
server.route({
method: "GET",
@@ -46,7 +47,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
schema: {
description: "Return encrypted project key",
params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.GET_KEY.workspaceId)
workspaceId: z.string().trim().describe(PROJECTS.GET_KEY.projectId)
}),
response: {
200: ProjectKeysSchema.merge(
@@ -72,7 +73,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.GET_WORKSPACE_KEY,
type: EventType.GET_PROJECT_KEY,
metadata: {
keyId: key?.id as string
}
@@ -83,68 +84,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
});
/* Start upgrade of a project */
server.route({
method: "POST",
url: "/:projectId/upgrade",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
body: z.object({
userPrivateKey: z.string().trim()
}),
response: {
200: z.void()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.project.upgradeProject({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
projectId: req.params.projectId,
userPrivateKey: req.body.userPrivateKey
});
}
});
/* Get upgrade status of project */
server.route({
url: "/:projectId/upgrade/status",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
status: z.string().nullable()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const status = await server.services.project.getProjectUpgradeStatus({
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
actor: req.permission.type,
actorId: req.permission.id
});
return { status };
}
});
/* Create new project */
server.route({
method: "POST",
@@ -186,8 +125,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
workspaceName: req.body.projectName,
workspaceDescription: req.body.projectDescription,
projectName: req.body.projectName,
projectDescription: req.body.projectDescription,
slug: req.body.slug,
kmsKeyId: req.body.kmsKeyId,
template: req.body.template,
@@ -224,6 +163,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
/* Delete a project by slug */
// moved to DELETE /v1/projects/slug/:slug
server.route({
method: "DELETE",
url: "/:slug",
@@ -276,6 +216,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
/* Get a project by slug */
// moved to GET /v1/projects/slug/:slug
server.route({
method: "GET",
url: "/:slug",
@@ -337,7 +278,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
200: SanitizedProjectSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const project = await server.services.project.updateProject({
+15 -8
View File
@@ -1,13 +1,15 @@
import { registerCaRouter } from "./certificate-authority-router";
import { registerGroupProjectRouter } from "./group-project-router";
import { registerDeprecatedGroupProjectRouter } from "./deprecated-group-project-router";
import { registerDeprecatedIdentityProjectRouter } from "./deprecated-identity-project-router";
import { registerDeprecatedProjectMembershipRouter } from "./deprecated-project-membership-router";
import { registerDeprecatedProjectRouter } from "./deprecated-project-router";
import { registerIdentityOrgRouter } from "./identity-org-router";
import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerMfaRouter } from "./mfa-router";
import { registerOrgRouter } from "./organization-router";
import { registerPasswordRouter } from "./password-router";
import { registerPkiTemplatesRouter } from "./pki-templates-router";
import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router";
import { registerSecretFolderRouter } from "./secret-folder-router";
import { registerSecretImportRouter } from "./secret-import-router";
import { registerServiceTokenRouter } from "./service-token-router";
import { registerUserRouter } from "./user-router";
@@ -32,12 +34,17 @@ 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) => {
await projectServer.register(registerProjectRouter);
await projectServer.register(registerIdentityProjectRouter);
await projectServer.register(registerGroupProjectRouter);
await projectServer.register(registerProjectMembershipRouter);
await projectServer.register(registerDeprecatedProjectRouter);
await projectServer.register(registerDeprecatedIdentityProjectRouter);
await projectServer.register(registerDeprecatedGroupProjectRouter);
await projectServer.register(registerDeprecatedProjectMembershipRouter);
},
{ prefix: "/workspace" }
);
@@ -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 };
}
@@ -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 };
}
@@ -39,7 +39,7 @@ const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReference
children: z.lazy(() => SecretReferenceNodeTree.array())
});
export const registerSecretRouter = async (server: FastifyZodProvider) => {
export const registerDeprecatedSecretRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/tags/:secretName",
@@ -230,7 +230,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
})
.describe(RAW_SECRETS.LIST.metadataFilter),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.projectId),
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),
@@ -298,7 +298,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
workspaceId = projectId;
}
if (!workspaceId || !environment) throw new BadRequestError({ message: "Missing workspace id or environment" });
if (!workspaceId || !environment) throw new BadRequestError({ message: "Missing project id or environment" });
const { secrets, imports } = await server.services.secret.getSecretsRaw({
actorId: req.permission.id,
@@ -336,7 +336,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId,
projectId: workspaceId,
environment,
secretPath: req.query.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -405,7 +405,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretName: z.string().trim().describe(RAW_SECRETS.GET.secretName)
}),
querystring: z.object({
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceId),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.projectId),
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceSlug),
environment: z.string().trim().optional().describe(RAW_SECRETS.GET.environment),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
@@ -495,7 +495,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
distinctId: getTelemetryDistinctId(req),
properties: {
numberOfSecrets: 1,
workspaceId: secret.workspace,
projectId: secret.workspace,
environment,
secretPath: req.query.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -526,7 +526,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretName: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.CREATE.workspaceId),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.CREATE.projectId),
projectSlug: z.string().trim().optional().describe(RAW_SECRETS.CREATE.projectSlug),
environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment),
secretPath: z
@@ -638,7 +638,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: 1,
workspaceId: projectId,
projectId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -669,7 +669,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretName: BaseSecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.workspaceId),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectId),
projectSlug: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectSlug),
environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment),
secretValue: z
@@ -791,7 +791,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: 1,
workspaceId: projectId,
projectId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -821,7 +821,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretName: z.string().min(1).describe(RAW_SECRETS.DELETE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.workspaceId),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectId),
projectSlug: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectSlug),
environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment),
secretPath: z
@@ -909,7 +909,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: 1,
workspaceId: projectId,
projectId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1017,7 +1017,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId: req.query.workspaceId,
projectId: req.query.workspaceId,
environment: req.query.environment,
secretPath: req.query.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1097,7 +1097,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: 1,
workspaceId: req.query.workspaceId,
projectId: req.query.workspaceId,
environment: req.query.environment,
secretPath: req.query.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1272,7 +1272,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: 1,
workspaceId: req.body.workspaceId,
projectId: req.body.workspaceId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1466,7 +1466,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: 1,
workspaceId: req.body.workspaceId,
projectId: req.body.workspaceId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1594,7 +1594,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: 1,
workspaceId: req.body.workspaceId,
projectId: req.body.workspaceId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1781,7 +1781,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId: req.body.workspaceId,
projectId: req.body.workspaceId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1914,7 +1914,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId: req.body.workspaceId,
projectId: req.body.workspaceId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -2039,7 +2039,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId: req.body.workspaceId,
projectId: req.body.workspaceId,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -2067,7 +2067,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
],
body: z.object({
projectSlug: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectSlug),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.workspaceId),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectId),
environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment),
secretPath: z
.string()
@@ -2166,7 +2166,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId: secrets[0].workspace,
projectId: secrets[0].workspace,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -2194,7 +2194,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
],
body: z.object({
projectSlug: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectSlug),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.workspaceId),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectId),
environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment),
secretPath: z
.string()
@@ -2341,7 +2341,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId: secrets[0].workspace,
projectId: secrets[0].workspace,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -2369,7 +2369,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
],
body: z.object({
projectSlug: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectSlug),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.workspaceId),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectId),
environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment),
secretPath: z
.string()
@@ -2459,7 +2459,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secrets.length,
workspaceId: secrets[0].workspace,
projectId: secrets[0].workspace,
environment: req.body.environment,
secretPath: req.body.secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -2469,95 +2469,4 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
return { secrets };
}
});
server.route({
method: "GET",
url: "/raw/:secretName/secret-reference-tree",
config: {
rateLimit: secretsLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Secrets],
description: "Get secret reference tree",
security: [
{
bearerAuth: []
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.secretName)
}),
querystring: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.workspaceId),
environment: z.string().trim().describe(RAW_SECRETS.GET_REFERENCE_TREE.environment),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.GET_REFERENCE_TREE.secretPath)
}),
response: {
200: z.object({
tree: SecretReferenceNodeTree,
value: z.string().optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { secretName } = req.params;
const { secretPath, environment, workspaceId } = req.query;
const { tree, value } = await server.services.secret.getSecretReferenceTree({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: workspaceId,
secretName,
secretPath,
environment
});
return { tree, value };
}
});
server.route({
method: "POST",
url: "/backfill-secret-references",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Backfill secret references",
security: [
{
bearerAuth: []
}
],
body: z.object({
projectId: z.string().trim().min(1)
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { projectId } = req.body;
const message = await server.services.secret.backfillSecretReferences({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId
});
return message;
}
});
};
+2 -4
View File
@@ -1,7 +1,6 @@
import { registerDeprecatedSecretRouter } from "./deprecated-secret-router";
import { registerExternalMigrationRouter } from "./external-migration-router";
import { registerLoginRouter } from "./login-router";
import { registerSecretBlindIndexRouter } from "./secret-blind-index-router";
import { registerSecretRouter } from "./secret-router";
import { registerSignupRouter } from "./signup-router";
import { registerUserRouter } from "./user-router";
@@ -9,7 +8,6 @@ export const registerV3Routes = async (server: FastifyZodProvider) => {
await server.register(registerSignupRouter, { prefix: "/signup" });
await server.register(registerLoginRouter, { prefix: "/auth" });
await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerSecretRouter, { prefix: "/secrets" });
await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" });
await server.register(registerDeprecatedSecretRouter, { prefix: "/secrets" });
await server.register(registerExternalMigrationRouter, { prefix: "/external-migration" });
};
@@ -1,109 +0,0 @@
import { z } from "zod";
import { SecretsSchema } from "@app/db/schemas";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:projectId/secrets/blind-index-status",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.boolean()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const count = await server.services.secretBlindIndex.getSecretBlindIndexStatus({
projectId: req.params.projectId,
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
return count === 0;
}
});
server.route({
method: "GET",
url: "/:projectId/secrets",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
secrets: SecretsSchema.omit({ secretBlindIndex: true })
.merge(
z.object({
environment: z.string(),
workspace: z.string()
})
)
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const secrets = await server.services.secretBlindIndex.getProjectSecrets({
projectId: req.params.projectId,
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
return { secrets };
}
});
server.route({
method: "POST",
url: "/:projectId/secrets/names",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
body: z.object({
secretsToUpdate: z
.object({
secretName: z.string().trim(),
secretId: z.string().trim()
})
.array()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.secretBlindIndex.updateProjectSecretName({
projectId: req.params.projectId,
secretsToUpdate: req.body.secretsToUpdate,
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
return { message: "Successfully named workspace secrets" };
}
});
};
+5
View File
@@ -0,0 +1,5 @@
import { registerSecretRouter } from "./secret-router";
export const registerV4Routes = async (server: FastifyZodProvider) => {
await server.register(registerSecretRouter, { prefix: "/secrets" });
};
File diff suppressed because it is too large Load Diff