Merge branch 'heads/main' into ENG-2785

This commit is contained in:
Daniel Hougaard
2025-09-24 04:24:02 +04:00
955 changed files with 30612 additions and 7796 deletions
+3 -5
View File
@@ -43,8 +43,6 @@ export const GenericResourceNameSchema = z
export const BaseSecretNameSchema = z.string().trim().min(1);
export const SecretNameSchema = BaseSecretNameSchema.refine(
(el) => !el.includes(" "),
"Secret name cannot contain spaces."
)
.refine((el) => !el.includes(":"), "Secret name cannot contain colon.")
.refine((el) => !el.includes("/"), "Secret name cannot contain forward slash.");
(el) => !el.includes(":"),
"Secret name cannot contain colon."
).refine((el) => !el.includes("/"), "Secret name cannot contain forward slash.");
+146 -83
View File
@@ -248,6 +248,10 @@ import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-co
import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
import { pkiSubscriberQueueServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-queue";
import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
import { pkiSyncCleanupQueueServiceFactory } from "@app/services/pki-sync/pki-sync-cleanup-queue";
import { pkiSyncDALFactory } from "@app/services/pki-sync/pki-sync-dal";
import { pkiSyncQueueFactory } from "@app/services/pki-sync/pki-sync-queue";
import { pkiSyncServiceFactory } from "@app/services/pki-sync/pki-sync-service";
import { pkiTemplatesDALFactory } from "@app/services/pki-templates/pki-templates-dal";
import { pkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service";
import { projectDALFactory } from "@app/services/project/project-dal";
@@ -329,6 +333,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();
@@ -775,7 +780,8 @@ export const registerRoutes = async (
orgDAL,
totpService,
orgMembershipDAL,
auditLogService
auditLogService,
notificationService
});
const passwordService = authPaswordServiceFactory({
tokenService,
@@ -889,7 +895,8 @@ export const registerRoutes = async (
projectDAL,
permissionService,
projectUserMembershipRoleDAL,
projectMembershipDAL
projectMembershipDAL,
notificationService
});
const rateLimitService = rateLimitServiceFactory({
@@ -928,7 +935,8 @@ export const registerRoutes = async (
projectRoleDAL,
groupProjectDAL,
secretReminderRecipientsDAL,
licenseService
licenseService,
notificationService
});
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
permissionService,
@@ -978,6 +986,7 @@ export const registerRoutes = async (
const pkiCollectionDAL = pkiCollectionDALFactory(db);
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
const pkiSyncDAL = pkiSyncDALFactory(db);
const pkiTemplatesDAL = pkiTemplatesDALFactory(db);
const instanceRelayConfigDAL = instanceRelayConfigDalFactory(db);
@@ -987,21 +996,6 @@ export const registerRoutes = async (
const orgGatewayConfigV2DAL = orgGatewayConfigV2DalFactory(db);
const certificateService = certificateServiceFactory({
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthorityCrlDAL,
certificateAuthoritySecretDAL,
projectDAL,
kmsService,
permissionService,
pkiCollectionDAL,
pkiCollectionItemDAL
});
const sshCertificateAuthorityService = sshCertificateAuthorityServiceFactory({
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
@@ -1109,7 +1103,9 @@ export const registerRoutes = async (
instanceRelayConfigDAL,
orgRelayConfigDAL,
relayDAL,
kmsService
kmsService,
licenseService,
permissionService
});
const gatewayV2Service = gatewayV2ServiceFactory({
@@ -1147,7 +1143,8 @@ export const registerRoutes = async (
appConnectionDAL,
licenseService,
gatewayService,
gatewayV2Service
gatewayV2Service,
notificationService
});
const secretQueueService = secretQueueFactory({
@@ -1231,7 +1228,8 @@ export const registerRoutes = async (
projectTemplateService,
groupProjectDAL,
smtpService,
reminderService
reminderService,
notificationService
});
const projectEnvService = projectEnvServiceFactory({
@@ -1365,7 +1363,8 @@ export const registerRoutes = async (
resourceMetadataDAL,
projectMicrosoftTeamsConfigDAL,
microsoftTeamsService,
folderCommitService
folderCommitService,
notificationService
});
const secretService = secretServiceFactory({
@@ -1683,7 +1682,8 @@ export const registerRoutes = async (
identityOrgMembershipDAL,
licenseService,
identityDAL,
identityAuthTemplateDAL
identityAuthTemplateDAL,
keyStore
});
const dynamicSecretProviders = buildDynamicSecretProviders({
@@ -1815,7 +1815,8 @@ export const registerRoutes = async (
secretV2BridgeService,
resourceMetadataDAL,
folderCommitService,
folderVersionDAL
folderVersionDAL,
notificationService
});
const migrationService = externalMigrationServiceFactory({
@@ -1840,7 +1841,8 @@ export const registerRoutes = async (
gatewayService,
gatewayV2Service,
gatewayDAL,
gatewayV2DAL
gatewayV2DAL,
projectDAL
});
const secretSyncService = secretSyncServiceFactory({
@@ -1855,52 +1857,6 @@ export const registerRoutes = async (
licenseService
});
const certificateAuthorityQueue = certificateAuthorityQueueFactory({
certificateAuthorityCrlDAL,
certificateAuthorityDAL,
certificateAuthoritySecretDAL,
certificateDAL,
projectDAL,
kmsService,
queueService,
pkiSubscriberDAL,
certificateBodyDAL,
certificateSecretDAL,
externalCertificateAuthorityDAL,
keyStore,
appConnectionDAL,
appConnectionService
});
const internalCertificateAuthorityService = internalCertificateAuthorityServiceFactory({
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL,
certificateTemplateDAL,
certificateAuthorityQueue,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
pkiCollectionDAL,
pkiCollectionItemDAL,
projectDAL,
internalCertificateAuthorityDAL,
kmsService,
permissionService
});
const certificateEstService = certificateEstServiceFactory({
internalCertificateAuthorityService,
certificateTemplateService,
certificateTemplateDAL,
certificateAuthorityCertDAL,
certificateAuthorityDAL,
projectDAL,
kmsService,
licenseService
});
const kmipService = kmipServiceFactory({
kmipClientDAL,
permissionService,
@@ -1943,6 +1899,79 @@ export const registerRoutes = async (
gatewayV2Service
});
const pkiSyncQueue = pkiSyncQueueFactory({
queueService,
kmsService,
appConnectionDAL,
keyStore,
pkiSyncDAL,
auditLogService,
projectDAL,
licenseService,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL
});
const pkiSyncCleanup = pkiSyncCleanupQueueServiceFactory({
queueService,
pkiSyncDAL,
pkiSyncQueue
});
const internalCaFns = InternalCertificateAuthorityFns({
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
projectDAL,
kmsService,
pkiSyncDAL,
pkiSyncQueue
});
const certificateAuthorityQueue = certificateAuthorityQueueFactory({
certificateAuthorityCrlDAL,
certificateAuthorityDAL,
certificateAuthoritySecretDAL,
certificateDAL,
projectDAL,
kmsService,
queueService,
pkiSubscriberDAL,
certificateBodyDAL,
certificateSecretDAL,
externalCertificateAuthorityDAL,
keyStore,
appConnectionDAL,
appConnectionService,
pkiSyncDAL,
pkiSyncQueue
});
const internalCertificateAuthorityService = internalCertificateAuthorityServiceFactory({
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL,
certificateTemplateDAL,
certificateAuthorityQueue,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
pkiCollectionDAL,
pkiCollectionItemDAL,
projectDAL,
internalCertificateAuthorityDAL,
kmsService,
permissionService
});
const certificateAuthorityService = certificateAuthorityServiceFactory({
certificateAuthorityDAL,
permissionService,
@@ -1955,19 +1984,20 @@ export const registerRoutes = async (
certificateSecretDAL,
kmsService,
pkiSubscriberDAL,
projectDAL
projectDAL,
pkiSyncDAL,
pkiSyncQueue
});
const internalCaFns = InternalCertificateAuthorityFns({
certificateAuthorityDAL,
const certificateEstService = certificateEstServiceFactory({
internalCertificateAuthorityService,
certificateTemplateService,
certificateTemplateDAL,
certificateAuthorityCertDAL,
certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
certificateAuthorityDAL,
projectDAL,
kmsService
kmsService,
licenseService
});
const pkiSubscriberQueue = pkiSubscriberQueueServiceFactory({
@@ -1980,6 +2010,23 @@ export const registerRoutes = async (
internalCaFns
});
const certificateService = certificateServiceFactory({
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthorityCrlDAL,
certificateAuthoritySecretDAL,
projectDAL,
kmsService,
permissionService,
pkiCollectionDAL,
pkiCollectionItemDAL,
pkiSyncDAL,
pkiSyncQueue
});
const pkiSubscriberService = pkiSubscriberServiceFactory({
pkiSubscriberDAL,
certificateAuthorityDAL,
@@ -1993,7 +2040,18 @@ export const registerRoutes = async (
kmsService,
permissionService,
certificateAuthorityQueue,
internalCaFns
internalCaFns,
pkiSyncDAL,
pkiSyncQueue
});
const pkiSyncService = pkiSyncServiceFactory({
pkiSyncDAL,
pkiSubscriberDAL,
appConnectionService,
permissionService,
licenseService,
pkiSyncQueue
});
const pkiTemplateService = pkiTemplatesServiceFactory({
@@ -2017,7 +2075,8 @@ export const registerRoutes = async (
queueService,
projectDAL,
projectMembershipDAL,
smtpService
smtpService,
notificationService
});
const secretScanningV2Queue = await secretScanningV2QueueServiceFactory({
@@ -2029,7 +2088,8 @@ export const registerRoutes = async (
smtpService,
kmsService,
keyStore,
appConnectionDAL
appConnectionDAL,
notificationService
});
const secretScanningV2Service = secretScanningV2ServiceFactory({
@@ -2056,6 +2116,7 @@ export const registerRoutes = async (
await telemetryQueue.startTelemetryCheck();
await telemetryQueue.startAggregatedEventsJob();
await dailyResourceCleanUp.init();
await pkiSyncCleanup.init();
await dailyReminderQueueService.startDailyRemindersJob();
await dailyReminderQueueService.startSecretReminderMigrationJob();
await dailyExpiringPkiItemAlert.startSendingAlerts();
@@ -2139,6 +2200,7 @@ export const registerRoutes = async (
pkiAlert: pkiAlertService,
pkiCollection: pkiCollectionService,
pkiSubscriber: pkiSubscriberService,
pkiSync: pkiSyncService,
pkiTemplate: pkiTemplateService,
secretScanning: secretScanningService,
license: licenseService,
@@ -2298,6 +2360,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: {
+364 -62
View File
@@ -1,16 +1,16 @@
import { ForbiddenError } from "@casl/ability";
import { z } from "zod";
import { SecretFoldersSchema, SecretImportsSchema, UsersSchema } from "@app/db/schemas";
import { SecretFoldersSchema, SecretImportsSchema, SecretType, UsersSchema } from "@app/db/schemas";
import { RemindersSchema } from "@app/db/schemas/reminders";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
import { DASHBOARD } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { OrderByDirection } from "@app/lib/types";
import { secretsLimit } from "@app/server/config/rateLimiter";
import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -111,6 +111,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
SecretRotationV2Schema,
z.object({
secrets: secretRawSchema
.omit({ secretValue: true })
.extend({
secretValueHidden: z.boolean(),
secretPath: z.string().optional(),
@@ -124,7 +125,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
.array()
.optional(),
secrets: secretRawSchema
.omit({ secretValue: true })
.extend({
isEmpty: z.boolean(),
secretValueHidden: z.boolean(),
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
@@ -207,7 +210,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);
@@ -219,7 +222,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
let imports: Awaited<ReturnType<typeof server.services.secretImport.getImportsMultiEnv>> | undefined;
let folders: Awaited<ReturnType<typeof server.services.folder.getFoldersMultiEnv>> | undefined;
let secrets: Awaited<ReturnType<typeof server.services.secret.getSecretsRawMultiEnv>> | undefined;
let secrets:
| (Awaited<ReturnType<typeof server.services.secret.getSecretsRawMultiEnv>>[number] & { isEmpty: boolean })[]
| undefined;
let dynamicSecrets:
| Awaited<ReturnType<typeof server.services.dynamicSecret.listDynamicSecretsByEnvs>>
| undefined;
@@ -426,43 +431,51 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
});
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
secrets = await server.services.secret.getSecretsRawMultiEnv({
viewSecretValue: true,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
environments,
actorAuthMethod: req.permission.authMethod,
projectId,
path: secretPath,
orderBy,
orderDirection,
search,
limit: remainingLimit,
offset: adjustedOffset,
isInternal: true
});
secrets = (
await server.services.secret.getSecretsRawMultiEnv({
viewSecretValue: true,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
environments,
actorAuthMethod: req.permission.authMethod,
projectId,
path: secretPath,
orderBy,
orderDirection,
search,
limit: remainingLimit,
offset: adjustedOffset,
isInternal: true
})
).map((secret) => ({ ...secret, isEmpty: !secret.secretValue }));
}
}
if (secrets?.length || secretRotations?.length) {
for await (const environment of environments) {
const secretCountFromEnv =
(secrets?.filter((secret) => secret.environment === environment).length ?? 0) +
(secretRotations
?.filter((rotation) => rotation.environment.slug === environment)
.flatMap((rotation) => rotation.secrets.filter((secret) => Boolean(secret))).length ?? 0);
const secretIds = [
...new Set(
[
...(secrets?.filter((secret) => secret.environment === environment) ?? []),
...(secretRotations
?.filter((rotation) => rotation.environment.slug === environment)
.flatMap((rotation) => rotation.secrets.filter((secret) => Boolean(secret))) ?? [])
].map((secret) => secret.id)
)
];
if (secretCountFromEnv) {
if (secretIds) {
await server.services.auditLog.createAuditLog({
projectId,
...req.auditLogInfo,
event: {
type: EventType.GET_SECRETS,
type: EventType.DASHBOARD_LIST_SECRETS,
metadata: {
environment,
secretPath,
numberOfSecrets: secretCountFromEnv
numberOfSecrets: secretIds.length,
secretIds
}
}
});
@@ -473,8 +486,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
distinctId: getTelemetryDistinctId(req),
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secretCountFromEnv,
workspaceId: projectId,
numberOfSecrets: secretIds.length,
projectId,
environment,
secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -584,7 +597,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
.optional(),
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
viewSecretValue: booleanSchema.default(true),
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
@@ -606,7 +618,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
SecretRotationV2Schema,
z.object({
secrets: secretRawSchema
.omit({ secretValue: true })
.extend({
isEmpty: z.boolean(),
secretValueHidden: z.boolean(),
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
@@ -619,7 +633,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
.array()
.optional(),
secrets: secretRawSchema
.omit({ secretValue: true })
.extend({
isEmpty: z.boolean(),
secretReminderRecipients: z
.object({
user: UsersSchema.pick({ id: true, email: true, username: true }),
@@ -696,7 +712,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);
@@ -715,12 +731,21 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
let folders: Awaited<ReturnType<typeof server.services.folder.getFolders>> | undefined;
let secrets:
| (Awaited<ReturnType<typeof server.services.secret.getSecretsRaw>>["secrets"][number] & {
isEmpty: boolean;
reminder: Awaited<ReturnType<typeof server.services.reminder.getRemindersForDashboard>>[string] | null;
})[]
| undefined;
let dynamicSecrets: Awaited<ReturnType<typeof server.services.dynamicSecret.listDynamicSecretsByEnv>> | undefined;
let secretRotations:
| Awaited<ReturnType<typeof server.services.secretRotationV2.getDashboardSecretRotations>>
| (Awaited<ReturnType<typeof server.services.secretRotationV2.getDashboardSecretRotations>>[number] & {
secrets: (NonNullable<
Awaited<
ReturnType<typeof server.services.secretRotationV2.getDashboardSecretRotations>
>[number]["secrets"][number] & {
isEmpty: boolean;
}
> | null)[];
})[]
| undefined;
let totalImportCount: number | undefined;
@@ -822,19 +847,31 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
);
if (remainingLimit > 0 && totalSecretRotationCount > adjustedOffset) {
secretRotations = await server.services.secretRotationV2.getDashboardSecretRotations(
{
projectId,
search,
orderBy,
orderDirection,
environments: [environment],
secretPath,
limit: remainingLimit,
offset: adjustedOffset
},
req.permission
);
secretRotations = (
await server.services.secretRotationV2.getDashboardSecretRotations(
{
projectId,
search,
orderBy,
orderDirection,
environments: [environment],
secretPath,
limit: remainingLimit,
offset: adjustedOffset
},
req.permission
)
).map((rotation) => ({
...rotation,
secrets: rotation.secrets.map((secret) =>
secret
? {
...secret,
isEmpty: !secret.secretValue
}
: secret
)
}));
await server.services.auditLog.createAuditLog({
projectId,
@@ -919,7 +956,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
await server.services.secret.getSecretsRaw({
actorId: req.permission.id,
actor: req.permission.type,
viewSecretValue: req.query.viewSecretValue,
viewSecretValue: true,
throwOnMissingReadValuePermission: false,
actorOrgId: req.permission.orgId,
environment,
@@ -943,6 +980,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets = rawSecrets.map((secret) => ({
...secret,
isEmpty: !secret.secretValue,
reminder: reminders[secret.id] ?? null
}));
}
@@ -977,19 +1015,25 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
}));
if (secrets?.length || secretRotations?.length) {
const secretCount =
(secrets?.length ?? 0) +
(secretRotations?.flatMap((rotation) => rotation.secrets.filter((secret) => Boolean(secret))).length ?? 0);
const secretIds = [
...new Set(
[
...(secrets ?? []),
...(secretRotations?.flatMap((rotation) => rotation.secrets.filter((secret) => Boolean(secret))) ?? [])
].map((secret) => secret.id)
)
];
await server.services.auditLog.createAuditLog({
projectId,
...req.auditLogInfo,
event: {
type: EventType.GET_SECRETS,
type: EventType.DASHBOARD_LIST_SECRETS,
metadata: {
environment,
secretPath,
numberOfSecrets: secretCount
numberOfSecrets: secretIds.length,
secretIds
}
}
});
@@ -1000,8 +1044,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
distinctId: getTelemetryDistinctId(req),
organizationId: req.permission.orgId,
properties: {
numberOfSecrets: secretCount,
workspaceId: projectId,
numberOfSecrets: secretIds.length,
projectId,
environment,
secretPath,
channel: getUserAgentType(req.headers["user-agent"]),
@@ -1060,6 +1104,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
.array()
.optional(),
secrets: secretRawSchema
.omit({ secretValue: true })
.extend({
secretValueHidden: z.boolean(),
secretPath: z.string().optional(),
@@ -1145,18 +1190,20 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
);
for await (const environment of environments) {
const secretCountForEnv = secrets.filter((secret) => secret.environment === environment).length;
const envSecrets = secrets.filter((secret) => secret.environment === environment);
const secretCountForEnv = envSecrets.length;
if (secretCountForEnv) {
await server.services.auditLog.createAuditLog({
projectId,
...req.auditLogInfo,
event: {
type: EventType.GET_SECRETS,
type: EventType.DASHBOARD_LIST_SECRETS,
metadata: {
environment,
secretPath,
numberOfSecrets: secretCountForEnv
numberOfSecrets: secretCountForEnv,
secretIds: envSecrets.map((secret) => secret.id)
}
}
});
@@ -1168,7 +1215,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"]),
@@ -1259,6 +1306,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
// TODO(scott): omit secretValue here, but requires refactor of uploading env/copy from board
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
@@ -1310,6 +1358,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
// TODO(scott): omit secretValue here, but requires refactor of uploading env/copy from board
secrets: secretRawSchema
.extend({
secretValueHidden: z.boolean(),
@@ -1345,11 +1394,12 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
projectId,
...req.auditLogInfo,
event: {
type: EventType.GET_SECRETS,
type: EventType.DASHBOARD_LIST_SECRETS,
metadata: {
environment,
secretPath,
numberOfSecrets: secrets.length
numberOfSecrets: secrets.length,
secretIds: secrets.map((secret) => secret.id)
}
}
});
@@ -1361,7 +1411,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"]),
@@ -1373,4 +1423,256 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
return { secrets };
}
});
server.route({
method: "GET",
url: "/secret-value",
config: {
rateLimit: secretsLimit
},
schema: {
security: [
{
bearerAuth: []
}
],
querystring: z.object({
projectId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secretKey: z.string().trim(),
isOverride: z
.enum(["true", "false"])
.transform((value) => value === "true")
.optional()
}),
response: {
200: z.object({
valueOverride: z.string().optional(),
value: z.string().optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { secretPath, projectId, environment, secretKey, isOverride } = req.query;
// TODO (scott): just get the secret instead of searching for it in list
const { secrets } = await server.services.secret.getSecretsRaw({
actorId: req.permission.id,
actor: req.permission.type,
viewSecretValue: true,
throwOnMissingReadValuePermission: false,
actorOrgId: req.permission.orgId,
environment,
actorAuthMethod: req.permission.authMethod,
projectId,
path: secretPath,
search: secretKey,
includeTagsInSearch: true,
includeMetadataInSearch: true
});
if (isOverride) {
const personalSecret = secrets.find(
(secret) => secret.type === SecretType.Personal && secret.secretKey === secretKey
);
if (!personalSecret)
throw new BadRequestError({
message: `Could not find personal secret with key "${secretKey}" at secret path "${secretPath}" in environment "${environment}" for project with ID "${projectId}"`
});
if (personalSecret)
return {
valueOverride: personalSecret.secretValue
};
}
const sharedSecret = secrets.find(
(secret) => secret.type === SecretType.Shared && secret.secretKey === secretKey
);
if (!sharedSecret)
throw new BadRequestError({
message: `Could not find secret with key "${secretKey}" at secret path "${secretPath}" in environment "${environment}" for project with ID "${projectId}"`
});
// only audit if not personal
await server.services.auditLog.createAuditLog({
projectId,
...req.auditLogInfo,
event: {
type: EventType.DASHBOARD_GET_SECRET_VALUE,
metadata: {
environment: req.query.environment,
secretPath: req.query.secretPath,
secretKey,
secretId: sharedSecret.id
}
}
});
return { value: sharedSecret.secretValue };
}
});
server.route({
url: "/secret-imports",
method: "GET",
config: {
rateLimit: secretsLimit
},
schema: {
querystring: z.object({
projectId: 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.omit({ secretValue: true }).extend({ isEmpty: z.boolean() }).array()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
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
});
await server.services.auditLog.createAuditLog({
projectId: req.query.projectId,
...req.auditLogInfo,
event: {
type: EventType.DASHBOARD_LIST_SECRETS,
metadata: {
environment: req.query.environment,
secretPath: req.query.path,
numberOfSecrets: importedSecrets.length,
secretIds: importedSecrets.map((secret) => secret.id)
}
}
});
return {
secrets: importedSecrets.map((importData) => ({
...importData,
secrets: importData.secrets.map((secret) => ({
...secret,
isEmpty: !secret.secretValue
}))
}))
};
}
});
server.route({
method: "GET",
url: "/secret-versions/:secretId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
secretId: z.string()
}),
querystring: z.object({
offset: z.coerce.number(),
limit: z.coerce.number()
}),
response: {
200: z.object({
secretVersions: secretRawSchema
.omit({ secretValue: true })
.extend({
secretValueHidden: z.boolean()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const secretVersions = await server.services.secret.getSecretVersions({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
limit: req.query.limit,
offset: req.query.offset,
secretId: req.params.secretId
});
return { secretVersions };
}
});
server.route({
method: "GET",
url: "/secret-versions/:secretId/value/:version",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
secretId: z.string(),
version: z.string()
}),
response: {
200: z.object({
value: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { version, secretId } = req.params;
const [secretVersion] = await server.services.secret.getSecretVersions({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
secretId,
secretVersions: [version]
});
if (!secretVersion)
throw new NotFoundError({
message: `Could not find secret version "${version}" for secret with ID "${secretId}`
});
await server.services.auditLog.createAuditLog({
projectId: secretVersion.workspace,
...req.auditLogInfo,
event: {
type: EventType.DASHBOARD_GET_SECRET_VERSION_VALUE,
metadata: {
secretId,
version
}
}
});
return { value: secretVersion.secretValue };
}
});
};
@@ -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 };
}
});
};
@@ -8,7 +8,7 @@
import { Authenticator } from "@fastify/passport";
import fastifySession from "@fastify/session";
import { FastifyRequest } from "fastify";
import { FastifyReply, FastifyRequest } from "fastify";
import { IncomingMessage } from "http";
import LdapStrategy from "passport-ldapauth";
import { z } from "zod";
@@ -135,19 +135,26 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
})
}
},
preValidation: passport.authenticate("ldapauth", {
failWithError: true,
session: false
}) as any,
preValidation: [
(req, res) => {
const passportAuth = (request: FastifyRequest, reply: FastifyReply) =>
(
passport.authenticate("ldapauth", {
failWithError: true,
session: false
}) as any
)(request, reply);
errorHandler: (error) => {
if (error.name === "AuthenticationError") {
throw new UnauthorizedError({ message: "Invalid credentials" });
const { identityId, username } = req.body;
return server.services.identityLdapAuth.withLdapLockout(
{
identityId,
username
},
() => passportAuth(req, res)
);
}
throw error;
},
],
handler: async (req) => {
if (!req.passportMachineIdentity?.identityId) {
throw new UnauthorizedError({ message: "Invalid request. Missing identity ID or LDAP entry details." });
@@ -241,7 +248,21 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
.int()
.min(0)
.default(0)
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit),
lockoutEnabled: z.boolean().default(true).describe(LDAP_AUTH.ATTACH.lockoutEnabled),
lockoutThreshold: z.number().min(1).max(30).default(3).describe(LDAP_AUTH.ATTACH.lockoutThreshold),
lockoutDurationSeconds: z
.number()
.min(30)
.max(86400)
.default(300)
.describe(LDAP_AUTH.ATTACH.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number()
.min(5)
.max(3600)
.default(30)
.describe(LDAP_AUTH.ATTACH.lockoutCounterResetSeconds)
})
.refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
@@ -291,7 +312,21 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
.int()
.min(0)
.default(0)
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit),
lockoutEnabled: z.boolean().default(true).describe(LDAP_AUTH.ATTACH.lockoutEnabled),
lockoutThreshold: z.number().min(1).max(30).default(3).describe(LDAP_AUTH.ATTACH.lockoutThreshold),
lockoutDurationSeconds: z
.number()
.min(30)
.max(86400)
.default(300)
.describe(LDAP_AUTH.ATTACH.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number()
.min(5)
.max(3600)
.default(30)
.describe(LDAP_AUTH.ATTACH.lockoutCounterResetSeconds)
})
.refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
@@ -331,7 +366,11 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
accessTokenTTL: identityLdapAuth.accessTokenTTL,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
allowedFields: req.body.allowedFields,
templateId: identityLdapAuth.templateId
templateId: identityLdapAuth.templateId,
lockoutEnabled: identityLdapAuth.lockoutEnabled,
lockoutThreshold: identityLdapAuth.lockoutThreshold,
lockoutDurationSeconds: identityLdapAuth.lockoutDurationSeconds,
lockoutCounterResetSeconds: identityLdapAuth.lockoutCounterResetSeconds
}
}
});
@@ -395,7 +434,21 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
.max(315360000)
.min(0)
.optional()
.describe(LDAP_AUTH.UPDATE.accessTokenMaxTTL)
.describe(LDAP_AUTH.UPDATE.accessTokenMaxTTL),
lockoutEnabled: z.boolean().optional().describe(LDAP_AUTH.UPDATE.lockoutEnabled),
lockoutThreshold: z.number().min(1).max(30).optional().describe(LDAP_AUTH.UPDATE.lockoutThreshold),
lockoutDurationSeconds: z
.number()
.min(30)
.max(86400)
.optional()
.describe(LDAP_AUTH.UPDATE.lockoutDurationSeconds),
lockoutCounterResetSeconds: z
.number()
.min(5)
.max(3600)
.optional()
.describe(LDAP_AUTH.UPDATE.lockoutCounterResetSeconds)
})
.refine(
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
@@ -434,7 +487,11 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
accessTokenTrustedIps: identityLdapAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
allowedFields: req.body.allowedFields,
templateId: identityLdapAuth.templateId
templateId: identityLdapAuth.templateId,
lockoutEnabled: identityLdapAuth.lockoutEnabled,
lockoutThreshold: identityLdapAuth.lockoutThreshold,
lockoutDurationSeconds: identityLdapAuth.lockoutDurationSeconds,
lockoutCounterResetSeconds: identityLdapAuth.lockoutCounterResetSeconds
}
}
});
@@ -553,4 +610,53 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
return { identityLdapAuth };
}
});
server.route({
method: "POST",
url: "/ldap-auth/identities/:identityId/clear-lockouts",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Clear LDAP Auth Lockouts for identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(LDAP_AUTH.CLEAR_CLIENT_LOCKOUTS.identityId)
}),
response: {
200: z.object({
deleted: z.number()
})
}
},
handler: async (req) => {
const clearLockoutsData = await server.services.identityLdapAuth.clearLdapAuthLockouts({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: clearLockoutsData.orgId,
event: {
type: EventType.CLEAR_IDENTITY_LDAP_AUTH_LOCKOUTS,
metadata: {
identityId: clearLockoutsData.identityId
}
}
});
return clearLockoutsData;
}
});
};
+37 -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";
@@ -40,13 +48,12 @@ import { registerPasswordRouter } from "./password-router";
import { registerPkiAlertRouter } from "./pki-alert-router";
import { registerPkiCollectionRouter } from "./pki-collection-router";
import { registerPkiSubscriberRouter } from "./pki-subscriber-router";
import { PKI_SYNC_REGISTER_ROUTER_MAP, registerPkiSyncRouter } from "./pki-sync-routers";
import { registerProjectEnvRouter } from "./project-env-router";
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";
@@ -88,8 +95,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) => {
@@ -102,15 +109,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" });
@@ -129,6 +149,15 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
await pkiRouter.register(registerPkiSubscriberRouter, { prefix: "/subscribers" });
await pkiRouter.register(
async (pkiSyncRouter) => {
await pkiSyncRouter.register(registerPkiSyncRouter);
for await (const [destination, router] of Object.entries(PKI_SYNC_REGISTER_ROUTER_MAP)) {
await pkiSyncRouter.register(router, { prefix: `/${destination}` });
}
},
{ prefix: "/syncs" }
);
},
{ prefix: "/pki" }
);
@@ -3,8 +3,10 @@ import { z } from "zod";
import { UserNotificationsSchema } from "@app/db/schemas/user-notifications";
import { UnauthorizedError } from "@app/lib/errors";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerNotificationRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -97,6 +99,16 @@ export const registerNotificationRouter = async (server: FastifyZodProvider) =>
...req.body
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.NotificationUpdated,
distinctId: getTelemetryDistinctId(req),
organizationId: req.permission.orgId,
properties: {
notificationId: req.params.notificationId,
...req.body
}
});
return { notification };
}
});
@@ -0,0 +1,22 @@
import {
AZURE_KEY_VAULT_PKI_SYNC_LIST_OPTION,
AzureKeyVaultPkiSyncSchema,
CreateAzureKeyVaultPkiSyncSchema,
UpdateAzureKeyVaultPkiSyncSchema
} from "@app/services/pki-sync/azure-key-vault";
import { PkiSync } from "@app/services/pki-sync/pki-sync-enums";
import { registerSyncPkiEndpoints } from "./pki-sync-endpoints";
export const registerAzureKeyVaultPkiSyncRouter = async (server: FastifyZodProvider) =>
registerSyncPkiEndpoints({
destination: PkiSync.AzureKeyVault,
server,
responseSchema: AzureKeyVaultPkiSyncSchema,
createSchema: CreateAzureKeyVaultPkiSyncSchema,
updateSchema: UpdateAzureKeyVaultPkiSyncSchema,
syncOptions: {
canImportCertificates: AZURE_KEY_VAULT_PKI_SYNC_LIST_OPTION.canImportCertificates,
canRemoveCertificates: AZURE_KEY_VAULT_PKI_SYNC_LIST_OPTION.canRemoveCertificates
}
});
@@ -0,0 +1,9 @@
import { PkiSync } from "@app/services/pki-sync/pki-sync-enums";
import { registerAzureKeyVaultPkiSyncRouter } from "./azure-key-vault-pki-sync-router";
export * from "./pki-sync-router";
export const PKI_SYNC_REGISTER_ROUTER_MAP: Record<PkiSync, (server: FastifyZodProvider) => Promise<void>> = {
[PkiSync.AzureKeyVault]: registerAzureKeyVaultPkiSyncRouter
};
@@ -0,0 +1,341 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
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 { PkiSync } from "@app/services/pki-sync/pki-sync-enums";
import { PKI_SYNC_NAME_MAP } from "@app/services/pki-sync/pki-sync-maps";
export const registerSyncPkiEndpoints = ({
server,
destination,
createSchema,
updateSchema,
responseSchema,
syncOptions
}: {
destination: PkiSync;
server: FastifyZodProvider;
createSchema: z.ZodType<{
name: string;
projectId: string;
connectionId: string;
destinationConfig: Record<string, unknown>;
syncOptions?: Record<string, unknown>;
description?: string;
isAutoSyncEnabled?: boolean;
subscriberId?: string;
}>;
updateSchema: z.ZodType<{
connectionId?: string;
name?: string;
destinationConfig?: Record<string, unknown>;
syncOptions?: Record<string, unknown>;
description?: string;
isAutoSyncEnabled?: boolean;
subscriberId?: string;
}>;
responseSchema: z.ZodTypeAny;
syncOptions: {
canImportCertificates: boolean;
canRemoveCertificates: boolean;
};
}) => {
const destinationName = PKI_SYNC_NAME_MAP[destination];
server.route({
method: "GET",
url: `/`,
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: `List the ${destinationName} PKI Syncs for the specified project.`,
querystring: z.object({
projectId: z.string().trim().min(1, "Project ID required")
}),
response: {
200: z.object({ pkiSyncs: responseSchema.array() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
query: { projectId }
} = req;
const pkiSyncs = await server.services.pkiSync.listPkiSyncsByProjectId({ projectId }, req.permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.GET_PKI_SYNCS,
metadata: {
projectId
}
}
});
return { pkiSyncs };
}
});
server.route({
method: "GET",
url: "/:pkiSyncId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: `Get the specified ${destinationName} PKI Sync by ID.`,
params: z.object({
pkiSyncId: z.string()
}),
response: {
200: responseSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const pkiSync = await server.services.pkiSync.findPkiSyncById({ id: pkiSyncId }, req.permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: pkiSync.projectId,
event: {
type: EventType.GET_PKI_SYNC,
metadata: {
syncId: pkiSyncId,
destination
}
}
});
return pkiSync;
}
});
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: `Create a ${destinationName} PKI Sync for the specified project.`,
body: createSchema,
response: {
200: responseSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const pkiSync = await server.services.pkiSync.createPkiSync({ ...req.body, destination }, req.permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: pkiSync.projectId,
event: {
type: EventType.CREATE_PKI_SYNC,
metadata: {
pkiSyncId: pkiSync.id,
name: pkiSync.name,
destination
}
}
});
return pkiSync;
}
});
server.route({
method: "PATCH",
url: "/:pkiSyncId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: `Update the specified ${destinationName} PKI Sync.`,
params: z.object({
pkiSyncId: z.string()
}),
body: updateSchema,
response: {
200: responseSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const pkiSync = await server.services.pkiSync.updatePkiSync({ ...req.body, id: pkiSyncId }, req.permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: pkiSync.projectId,
event: {
type: EventType.UPDATE_PKI_SYNC,
metadata: {
pkiSyncId,
name: pkiSync.name
}
}
});
return pkiSync;
}
});
server.route({
method: "DELETE",
url: `/:pkiSyncId`,
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: `Delete the specified ${destinationName} PKI Sync.`,
params: z.object({
pkiSyncId: z.string()
}),
response: {
200: responseSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const pkiSync = await server.services.pkiSync.deletePkiSync({ id: pkiSyncId }, req.permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: pkiSync.projectId,
event: {
type: EventType.DELETE_PKI_SYNC,
metadata: {
pkiSyncId,
name: pkiSync.name,
destination: pkiSync.destination
}
}
});
return pkiSync;
}
});
server.route({
method: "POST",
url: "/:pkiSyncId/sync",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: `Trigger a sync for the specified ${destinationName} PKI Sync.`,
params: z.object({
pkiSyncId: z.string()
}),
response: {
200: z.object({ message: z.string() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const result = await server.services.pkiSync.triggerPkiSyncSyncCertificatesById(
{
id: pkiSyncId
},
req.permission
);
return result;
}
});
// Only register import route if the destination supports it
if (syncOptions.canImportCertificates) {
server.route({
method: "POST",
url: "/:pkiSyncId/import",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: `Import certificates from the specified ${destinationName} PKI Sync destination.`,
params: z.object({
pkiSyncId: z.string()
}),
response: {
200: z.object({ message: z.string() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const result = await server.services.pkiSync.triggerPkiSyncImportCertificatesById(
{
id: pkiSyncId
},
req.permission
);
return result;
}
});
}
server.route({
method: "POST",
url: "/:pkiSyncId/remove-certificates",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: `Remove certificates from the specified ${destinationName} PKI Sync destination.`,
params: z.object({
pkiSyncId: z.string()
}),
response: {
200: z.object({ message: z.string() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const result = await server.services.pkiSync.triggerPkiSyncRemoveCertificatesById(
{
id: pkiSyncId
},
req.permission
);
return result;
}
});
};
@@ -0,0 +1,182 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { AuthMode } from "@app/services/auth/auth-type";
import { PkiSync } from "@app/services/pki-sync/pki-sync-enums";
const PkiSyncSchema = z.object({
id: z.string().uuid(),
name: z.string(),
description: z.string().nullable().optional(),
destination: z.nativeEnum(PkiSync),
isAutoSyncEnabled: z.boolean(),
destinationConfig: z.record(z.unknown()),
syncOptions: z.record(z.unknown()),
projectId: z.string().uuid(),
subscriberId: z.string().uuid().nullable().optional(),
connectionId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
// Sync status fields
syncStatus: z.string().nullable().optional(),
lastSyncJobId: z.string().nullable().optional(),
lastSyncMessage: z.string().nullable().optional(),
lastSyncedAt: z.date().nullable().optional(),
// Import status fields
importStatus: z.string().nullable().optional(),
lastImportJobId: z.string().nullable().optional(),
lastImportMessage: z.string().nullable().optional(),
lastImportedAt: z.date().nullable().optional(),
// Remove status fields
removeStatus: z.string().nullable().optional(),
lastRemoveJobId: z.string().nullable().optional(),
lastRemoveMessage: z.string().nullable().optional(),
lastRemovedAt: z.date().nullable().optional(),
// App connection info
appConnectionName: z.string(),
appConnectionApp: z.string(),
connection: z.object({
id: z.string(),
name: z.string(),
app: z.string(),
encryptedCredentials: z.unknown().nullable(),
orgId: z.string().uuid(),
projectId: z.string().uuid().nullable().optional(),
method: z.string(),
description: z.string().nullable().optional(),
version: z.number(),
gatewayId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
isPlatformManagedCredentials: z.boolean().nullable().optional()
}),
subscriber: z
.object({
id: z.string(),
name: z.string()
})
.nullable()
.optional()
});
const PkiSyncOptionsSchema = z.object({
name: z.string(),
connection: z.nativeEnum(AppConnection),
destination: z.nativeEnum(PkiSync),
canImportCertificates: z.boolean(),
canRemoveCertificates: z.boolean(),
defaultCertificateNameSchema: z.string().optional(),
forbiddenCharacters: z.string().optional(),
allowedCharacterPattern: z.string().optional(),
maxCertificateNameLength: z.number().optional(),
minCertificateNameLength: z.number().optional()
});
export const registerPkiSyncRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/options",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: "List the available PKI Sync Options.",
response: {
200: z.object({
pkiSyncOptions: PkiSyncOptionsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: () => {
const pkiSyncOptions = server.services.pkiSync.getPkiSyncOptions();
return { pkiSyncOptions };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: "List all the PKI Syncs for the specified project.",
querystring: z.object({
projectId: z.string().trim().min(1)
}),
response: {
200: z.object({ pkiSyncs: PkiSyncSchema.array() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
query: { projectId },
permission
} = req;
const pkiSyncs = await server.services.pkiSync.listPkiSyncsByProjectId({ projectId }, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.GET_PKI_SYNCS,
metadata: {
projectId
}
}
});
return { pkiSyncs };
}
});
server.route({
method: "GET",
url: "/:pkiSyncId",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: "Get a PKI Sync by ID.",
params: z.object({
pkiSyncId: z.string()
}),
response: {
200: PkiSyncSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const pkiSync = await server.services.pkiSync.findPkiSyncById({ id: pkiSyncId }, req.permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: pkiSync.projectId,
event: {
type: EventType.GET_PKI_SYNC,
metadata: {
syncId: pkiSyncId,
destination: pkiSync.destination
}
}
});
return pkiSync;
}
});
};
@@ -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",
@@ -240,7 +180,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
slug: slugSchema({ min: 5, max: 36 }).describe("The slug of the project to delete.")
slug: slugSchema({ min: 5, max: 64 }).describe("The slug of the project to delete.")
}),
response: {
200: SanitizedProjectSchema
@@ -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",
@@ -292,7 +233,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
slug: slugSchema({ max: 36 }).describe("The slug of the project to get.")
slug: slugSchema({ max: 64 }).describe("The slug of the project to get.")
}),
response: {
200: projectWithEnv
@@ -325,7 +266,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
},
schema: {
params: z.object({
slug: slugSchema({ min: 5, max: 36 }).describe("The slug of the project to update.")
slug: slugSchema({ min: 5, max: 64 }).describe("The slug of the project to update.")
}),
body: z.object({
name: z.string().trim().optional().describe(PROJECTS.UPDATE.name),
@@ -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({
@@ -382,7 +322,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
params: z.object({
slug: slugSchema({ min: 5, max: 36 }).describe(PROJECTS.LIST_CAS.slug)
slug: slugSchema({ min: 5, max: 64 }).describe(PROJECTS.LIST_CAS.slug)
}),
querystring: z.object({
status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional().describe(PROJECTS.LIST_CAS.status),
@@ -425,7 +365,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
hide: false,
tags: [ApiDocsTags.PkiCertificates],
params: z.object({
slug: slugSchema({ min: 5, max: 36 }).describe(PROJECTS.LIST_CERTIFICATES.slug)
slug: slugSchema({ min: 5, max: 64 }).describe(PROJECTS.LIST_CERTIFICATES.slug)
}),
querystring: z.object({
friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName),
+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
@@ -627,7 +627,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretId: secret.id,
secretKey: req.params.secretName,
secretVersion: secret.version,
secretMetadata: req.body.secretMetadata
secretMetadata: req.body.secretMetadata,
secretTags: secret.tags?.map((tag) => tag.name)
}
}
});
@@ -638,7 +639,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 +670,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
@@ -780,7 +781,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretId: secret.id,
secretKey: req.params.secretName,
secretVersion: secret.version,
secretMetadata: req.body.secretMetadata
secretMetadata: req.body.secretMetadata,
secretTags: secret.tags?.map((tag) => tag.name)
}
}
});
@@ -791,7 +793,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 +823,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 +911,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 +1019,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 +1099,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 +1274,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 +1468,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 +1596,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 +1783,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 +1916,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 +2041,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 +2069,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()
@@ -2154,7 +2156,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretId: secret.id,
secretKey: secret.secretKey,
secretVersion: secret.version,
secretMetadata: secretMetadataMap.get(secret.secretKey)
secretMetadata: secretMetadataMap.get(secret.secretKey),
secretTags: secret.tags?.map((tag) => tag.name)
}))
}
}
@@ -2166,7 +2169,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 +2197,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()
@@ -2288,7 +2291,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
return { approval: secretOperation.approval };
}
const { secrets } = secretOperation;
const secretMetadataMap = new Map(
inputSecrets.map(({ secretKey, secretMetadata }) => [secretKey, secretMetadata])
);
@@ -2308,7 +2310,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretPath: secret.secretPath,
secretKey: secret.secretKey,
secretVersion: secret.version,
secretMetadata: secretMetadataMap.get(secret.secretKey)
secretMetadata: secretMetadataMap.get(secret.secretKey),
secretTags: secret.tags?.map((tag) => tag.name)
}))
}
}
@@ -2328,7 +2331,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretPath: secret.secretPath,
secretKey: secret.secretKey,
secretVersion: secret.version,
secretMetadata: secretMetadataMap.get(secret.secretKey)
secretMetadata: secretMetadataMap.get(secret.secretKey),
secretTags: secret.tags?.map((tag) => tag.name)
}))
}
}
@@ -2341,7 +2345,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 +2373,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 +2463,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 +2473,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