Merge branch 'heads/main' into daniel/vault-import

This commit is contained in:
Daniel Hougaard
2025-07-24 17:29:55 +04:00
697 changed files with 18099 additions and 7050 deletions
+1
View File
@@ -24,6 +24,7 @@ export const mockQueue = (): TQueueServiceFactory => {
events[name] = event; events[name] = event;
}, },
getRepeatableJobs: async () => [], getRepeatableJobs: async () => [],
getDelayedJobs: async () => [],
clearQueue: async () => {}, clearQueue: async () => {},
stopJobById: async () => {}, stopJobById: async () => {},
stopJobByIdPg: async () => {}, stopJobByIdPg: async () => {},
+2
View File
@@ -93,6 +93,7 @@ import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env
import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service"; import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service";
import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { TReminderServiceFactory } from "@app/services/reminder/reminder-types";
import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service";
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
@@ -285,6 +286,7 @@ declare module "fastify" {
secretScanningV2: TSecretScanningV2ServiceFactory; secretScanningV2: TSecretScanningV2ServiceFactory;
internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory; internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory;
pkiTemplate: TPkiTemplatesServiceFactory; pkiTemplate: TPkiTemplatesServiceFactory;
reminder: TReminderServiceFactory;
}; };
// this is exclusive use for middlewares in which we need to inject data // this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer // everywhere else access using service layer
+12
View File
@@ -504,6 +504,12 @@ import {
TProjectMicrosoftTeamsConfigsInsert, TProjectMicrosoftTeamsConfigsInsert,
TProjectMicrosoftTeamsConfigsUpdate TProjectMicrosoftTeamsConfigsUpdate
} from "@app/db/schemas/project-microsoft-teams-configs"; } from "@app/db/schemas/project-microsoft-teams-configs";
import { TReminders, TRemindersInsert, TRemindersUpdate } from "@app/db/schemas/reminders";
import {
TRemindersRecipients,
TRemindersRecipientsInsert,
TRemindersRecipientsUpdate
} from "@app/db/schemas/reminders-recipients";
import { import {
TSecretReminderRecipients, TSecretReminderRecipients,
TSecretReminderRecipientsInsert, TSecretReminderRecipientsInsert,
@@ -1211,5 +1217,11 @@ declare module "knex/types/tables" {
TSecretScanningConfigsInsert, TSecretScanningConfigsInsert,
TSecretScanningConfigsUpdate TSecretScanningConfigsUpdate
>; >;
[TableName.Reminder]: KnexOriginal.CompositeTableType<TReminders, TRemindersInsert, TRemindersUpdate>;
[TableName.ReminderRecipient]: KnexOriginal.CompositeTableType<
TRemindersRecipients,
TRemindersRecipientsInsert,
TRemindersRecipientsUpdate
>;
} }
} }
@@ -0,0 +1,43 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Reminder))) {
await knex.schema.createTable(TableName.Reminder, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("secretId").nullable();
t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
t.string("message", 1024).nullable();
t.integer("repeatDays").checkPositive().nullable();
t.timestamp("nextReminderDate").notNullable();
t.timestamps(true, true, true);
t.unique("secretId");
});
}
if (!(await knex.schema.hasTable(TableName.ReminderRecipient))) {
await knex.schema.createTable(TableName.ReminderRecipient, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("reminderId").notNullable();
t.foreign("reminderId").references("id").inTable(TableName.Reminder).onDelete("CASCADE");
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.timestamps(true, true, true);
t.index("reminderId");
t.index("userId");
t.unique(["reminderId", "userId"]);
});
}
await createOnUpdateTrigger(knex, TableName.Reminder);
await createOnUpdateTrigger(knex, TableName.ReminderRecipient);
}
export async function down(knex: Knex): Promise<void> {
await dropOnUpdateTrigger(knex, TableName.Reminder);
await dropOnUpdateTrigger(knex, TableName.ReminderRecipient);
await knex.schema.dropTableIfExists(TableName.ReminderRecipient);
await knex.schema.dropTableIfExists(TableName.Reminder);
}
@@ -0,0 +1,432 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { v4 as uuidV4 } from "uuid";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ProjectType, TableName } from "../schemas";
/* eslint-disable no-await-in-loop,@typescript-eslint/ban-ts-comment */
// Single query to get all projects that need any kind of kickout
const getProjectsNeedingKickouts = async (
knex: Knex
): Promise<
Array<{
id: string;
defaultProduct: string;
needsSecretManager: boolean;
needsCertManager: boolean;
needsSecretScanning: boolean;
needsKms: boolean;
needsSsh: boolean;
}>
> => {
const result = await knex.raw(
`
SELECT DISTINCT
p.id,
p."defaultProduct",
-- Use CASE with direct joins instead of EXISTS subqueries
CASE WHEN p."defaultProduct" != 'secret-manager' AND s.secret_exists IS NOT NULL THEN true ELSE false END AS "needsSecretManager",
CASE WHEN p."defaultProduct" != 'cert-manager' AND ca.ca_exists IS NOT NULL THEN true ELSE false END AS "needsCertManager",
CASE WHEN p."defaultProduct" != 'secret-scanning' AND ssds.ssds_exists IS NOT NULL THEN true ELSE false END AS "needsSecretScanning",
CASE WHEN p."defaultProduct" != 'kms' AND kk.kms_exists IS NOT NULL THEN true ELSE false END AS "needsKms",
CASE WHEN p."defaultProduct" != 'ssh' AND sc.ssh_exists IS NOT NULL THEN true ELSE false END AS "needsSsh"
FROM projects p
LEFT JOIN (
SELECT DISTINCT e."projectId", 1 as secret_exists
FROM secrets_v2 s
JOIN secret_folders sf ON sf.id = s."folderId"
JOIN project_environments e ON e.id = sf."envId"
) s ON s."projectId" = p.id AND p."defaultProduct" != 'secret-manager'
LEFT JOIN (
SELECT DISTINCT "projectId", 1 as ca_exists
FROM certificate_authorities
) ca ON ca."projectId" = p.id AND p."defaultProduct" != 'cert-manager'
LEFT JOIN (
SELECT DISTINCT "projectId", 1 as ssds_exists
FROM secret_scanning_data_sources
) ssds ON ssds."projectId" = p.id AND p."defaultProduct" != 'secret-scanning'
LEFT JOIN (
SELECT DISTINCT "projectId", 1 as kms_exists
FROM kms_keys
WHERE "isReserved" = false
) kk ON kk."projectId" = p.id AND p."defaultProduct" != 'kms'
LEFT JOIN (
SELECT DISTINCT sca."projectId", 1 as ssh_exists
FROM ssh_certificates sc
JOIN ssh_certificate_authorities sca ON sca.id = sc."sshCaId"
) sc ON sc."projectId" = p.id AND p."defaultProduct" != 'ssh'
WHERE p."defaultProduct" IS NOT NULL
AND (
(p."defaultProduct" != 'secret-manager' AND s.secret_exists IS NOT NULL) OR
(p."defaultProduct" != 'cert-manager' AND ca.ca_exists IS NOT NULL) OR
(p."defaultProduct" != 'secret-scanning' AND ssds.ssds_exists IS NOT NULL) OR
(p."defaultProduct" != 'kms' AND kk.kms_exists IS NOT NULL) OR
(p."defaultProduct" != 'ssh' AND sc.ssh_exists IS NOT NULL)
)
`
);
return result.rows;
};
const newProject = async (knex: Knex, projectId: string, projectType: ProjectType) => {
const newProjectId = uuidV4();
const project = await knex(TableName.Project).where("id", projectId).first();
await knex(TableName.Project).insert({
...project,
type: projectType,
defaultProduct: null,
// @ts-ignore id is required
id: newProjectId,
slug: slugify(`${project?.name}-${alphaNumericNanoId(8)}`)
});
const customRoleMapping: Record<string, string> = {};
const projectCustomRoles = await knex(TableName.ProjectRoles).where("projectId", projectId);
if (projectCustomRoles.length) {
await knex.batchInsert(
TableName.ProjectRoles,
projectCustomRoles.map((el) => {
const id = uuidV4();
customRoleMapping[el.id] = id;
return {
...el,
id,
projectId: newProjectId,
permissions: el.permissions ? JSON.stringify(el.permissions) : el.permissions
};
})
);
}
const groupMembershipMapping: Record<string, string> = {};
const groupMemberships = await knex(TableName.GroupProjectMembership).where("projectId", projectId);
if (groupMemberships.length) {
await knex.batchInsert(
TableName.GroupProjectMembership,
groupMemberships.map((el) => {
const id = uuidV4();
groupMembershipMapping[el.id] = id;
return { ...el, id, projectId: newProjectId };
})
);
}
const groupMembershipRoles = await knex(TableName.GroupProjectMembershipRole).whereIn(
"projectMembershipId",
groupMemberships.map((el) => el.id)
);
if (groupMembershipRoles.length) {
await knex.batchInsert(
TableName.GroupProjectMembershipRole,
groupMembershipRoles.map((el) => {
const id = uuidV4();
const projectMembershipId = groupMembershipMapping[el.projectMembershipId];
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return { ...el, id, projectMembershipId, customRoleId };
})
);
}
const identityProjectMembershipMapping: Record<string, string> = {};
const identities = await knex(TableName.IdentityProjectMembership).where("projectId", projectId);
if (identities.length) {
await knex.batchInsert(
TableName.IdentityProjectMembership,
identities.map((el) => {
const id = uuidV4();
identityProjectMembershipMapping[el.id] = id;
return { ...el, id, projectId: newProjectId };
})
);
}
const identitiesRoles = await knex(TableName.IdentityProjectMembershipRole).whereIn(
"projectMembershipId",
identities.map((el) => el.id)
);
if (identitiesRoles.length) {
await knex.batchInsert(
TableName.IdentityProjectMembershipRole,
identitiesRoles.map((el) => {
const id = uuidV4();
const projectMembershipId = identityProjectMembershipMapping[el.projectMembershipId];
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return { ...el, id, projectMembershipId, customRoleId };
})
);
}
const projectMembershipMapping: Record<string, string> = {};
const projectUserMembers = await knex(TableName.ProjectMembership).where("projectId", projectId);
if (projectUserMembers.length) {
await knex.batchInsert(
TableName.ProjectMembership,
projectUserMembers.map((el) => {
const id = uuidV4();
projectMembershipMapping[el.id] = id;
return { ...el, id, projectId: newProjectId };
})
);
}
const membershipRoles = await knex(TableName.ProjectUserMembershipRole).whereIn(
"projectMembershipId",
projectUserMembers.map((el) => el.id)
);
if (membershipRoles.length) {
await knex.batchInsert(
TableName.ProjectUserMembershipRole,
membershipRoles.map((el) => {
const id = uuidV4();
const projectMembershipId = projectMembershipMapping[el.projectMembershipId];
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return { ...el, id, projectMembershipId, customRoleId };
})
);
}
const kmsKeys = await knex(TableName.KmsKey).where("projectId", projectId).andWhere("isReserved", true);
if (kmsKeys.length) {
await knex.batchInsert(
TableName.KmsKey,
kmsKeys.map((el) => {
const id = uuidV4();
const slug = slugify(alphaNumericNanoId(8).toLowerCase());
return { ...el, id, slug, projectId: newProjectId };
})
);
}
const projectBot = await knex(TableName.ProjectBot).where("projectId", projectId).first();
if (projectBot) {
const newProjectBot = { ...projectBot, id: uuidV4(), projectId: newProjectId };
await knex(TableName.ProjectBot).insert(newProjectBot);
}
const projectKeys = await knex(TableName.ProjectKeys).where("projectId", projectId);
if (projectKeys.length) {
await knex.batchInsert(
TableName.ProjectKeys,
projectKeys.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
const projectGateways = await knex(TableName.ProjectGateway).where("projectId", projectId);
if (projectGateways.length) {
await knex.batchInsert(
TableName.ProjectGateway,
projectGateways.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
const projectSlackConfigs = await knex(TableName.ProjectSlackConfigs).where("projectId", projectId);
if (projectSlackConfigs.length) {
await knex.batchInsert(
TableName.ProjectSlackConfigs,
projectSlackConfigs.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
const projectMicrosoftTeamsConfigs = await knex(TableName.ProjectMicrosoftTeamsConfigs).where("projectId", projectId);
if (projectMicrosoftTeamsConfigs.length) {
await knex.batchInsert(
TableName.ProjectMicrosoftTeamsConfigs,
projectMicrosoftTeamsConfigs.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
const trustedIps = await knex(TableName.TrustedIps).where("projectId", projectId);
if (trustedIps.length) {
await knex.batchInsert(
TableName.TrustedIps,
trustedIps.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
return newProjectId;
};
const kickOutSecretManagerProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SecretManager);
await knex(TableName.IntegrationAuth).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.Environment).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretBlindIndex).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretSync).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretTag).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretReminderRecipients).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.ServiceToken).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const kickOutCertManagerProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.CertificateManager);
await knex(TableName.CertificateAuthority).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.Certificate).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.PkiSubscriber).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.PkiCollection).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.PkiAlert).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const kickOutSecretScanningProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SecretScanning);
await knex(TableName.SecretScanningConfig).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretScanningDataSource).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretScanningFinding).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const kickOutKmsProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.KMS);
await knex(TableName.KmsKey)
.where("projectId", oldProjectId)
.andWhere("isReserved", false)
.update("projectId", newProjectId);
await knex(TableName.KmipClient).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const kickOutSshProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SSH);
await knex(TableName.SshHost).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.ProjectSshConfig).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SshCertificateAuthority).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SshHostGroup).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const BATCH_SIZE = 1000;
const MIGRATION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
export async function up(knex: Knex): Promise<void> {
const result = await knex.raw("SHOW statement_timeout");
const originalTimeout = result.rows[0].statement_timeout;
try {
await knex.raw(`SET statement_timeout = ${MIGRATION_TIMEOUT}`);
const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type");
if (hasTemplateTypeColumn) {
await knex(TableName.ProjectTemplates).whereNull("type").update({
type: ProjectType.SecretManager
});
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
t.string("type").notNullable().defaultTo(ProjectType.SecretManager).alter();
});
}
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct");
if (hasTypeColumn && hasDefaultTypeColumn) {
await knex(TableName.Project).update({
// eslint-disable-next-line
// @ts-ignore this is because this field is created later
type: knex.raw(`"defaultProduct"`)
});
await knex.schema.alterTable(TableName.Project, (t) => {
t.string("type").notNullable().alter();
t.string("defaultProduct").nullable().alter();
});
// Get all projects that need kickouts in a single query
const projectsNeedingKickouts = await getProjectsNeedingKickouts(knex);
// Process projects in batches to avoid overwhelming the database
for (let i = 0; i < projectsNeedingKickouts.length; i += projectsNeedingKickouts.length) {
const batch = projectsNeedingKickouts.slice(i, i + BATCH_SIZE);
const processedIds: string[] = [];
for (const project of batch) {
const kickoutPromises: Promise<void>[] = [];
// Only add kickouts that are actually needed (flags are pre-computed)
if (project.needsSecretManager) {
kickoutPromises.push(kickOutSecretManagerProject(knex, project.id));
}
if (project.needsCertManager) {
kickoutPromises.push(kickOutCertManagerProject(knex, project.id));
}
if (project.needsKms) {
kickoutPromises.push(kickOutKmsProject(knex, project.id));
}
if (project.needsSsh) {
kickoutPromises.push(kickOutSshProject(knex, project.id));
}
if (project.needsSecretScanning) {
kickoutPromises.push(kickOutSecretScanningProject(knex, project.id));
}
// Execute all kickouts in parallel and handle any failures gracefully
if (kickoutPromises.length > 0) {
const results = await Promise.allSettled(kickoutPromises);
// Log any failures for debugging
results.forEach((res) => {
if (res.status === "rejected") {
throw new Error(`Migration failed for project ${project.id}: ${res.reason}`);
}
});
}
processedIds.push(project.id);
}
// Clear defaultProduct for the processed batch
if (processedIds.length > 0) {
await knex(TableName.Project).whereIn("id", processedIds).update("defaultProduct", null);
}
}
}
} finally {
await knex.raw(`SET statement_timeout = '${originalTimeout}'`);
}
}
export async function down(knex: Knex): Promise<void> {
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct");
if (hasTypeColumn && hasDefaultTypeColumn) {
await knex(TableName.Project).update({
// eslint-disable-next-line
// @ts-ignore this is because this field is created later
defaultProduct: knex.raw(`
CASE
WHEN "type" IS NULL OR "type" = '' THEN 'secret-manager'
ELSE "type"
END
`)
});
await knex.schema.alterTable(TableName.Project, (t) => {
t.string("type").nullable().alter();
t.string("defaultProduct").notNullable().alter();
});
}
const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type");
if (hasTemplateTypeColumn) {
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
t.string("type").nullable().alter();
});
}
}
@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.IdentityAwsAuth, "allowedPrincipalArns");
if (hasColumn) {
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
t.string("allowedPrincipalArns", 4096).notNullable().alter();
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.IdentityAwsAuth, "allowedPrincipalArns");
if (hasColumn) {
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
t.string("allowedPrincipalArns", 2048).notNullable().alter();
});
}
}
+15 -2
View File
@@ -160,7 +160,7 @@ export enum TableName {
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings", SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
MicrosoftTeamsIntegrations = "microsoft_teams_integrations", MicrosoftTeamsIntegrations = "microsoft_teams_integrations",
ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs", ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs",
SecretReminderRecipients = "secret_reminder_recipients", SecretReminderRecipients = "secret_reminder_recipients", // TODO(Carlos): Remove this in the future after migrating to the new reminder recipients table
GithubOrgSyncConfig = "github_org_sync_configs", GithubOrgSyncConfig = "github_org_sync_configs",
FolderCommit = "folder_commits", FolderCommit = "folder_commits",
FolderCommitChanges = "folder_commit_changes", FolderCommitChanges = "folder_commit_changes",
@@ -172,7 +172,10 @@ export enum TableName {
SecretScanningResource = "secret_scanning_resources", SecretScanningResource = "secret_scanning_resources",
SecretScanningScan = "secret_scanning_scans", SecretScanningScan = "secret_scanning_scans",
SecretScanningFinding = "secret_scanning_findings", SecretScanningFinding = "secret_scanning_findings",
SecretScanningConfig = "secret_scanning_configs" SecretScanningConfig = "secret_scanning_configs",
// reminders
Reminder = "reminders",
ReminderRecipient = "reminders_recipients"
} }
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId"; export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
@@ -267,6 +270,16 @@ export enum ProjectType {
SecretScanning = "secret-scanning" SecretScanning = "secret-scanning"
} }
export enum ActionProjectType {
SecretManager = ProjectType.SecretManager,
CertificateManager = ProjectType.CertificateManager,
KMS = ProjectType.KMS,
SSH = ProjectType.SSH,
SecretScanning = ProjectType.SecretScanning,
// project operations that happen on all types
Any = "any"
}
export enum SortDirection { export enum SortDirection {
ASC = "asc", ASC = "asc",
DESC = "desc" DESC = "desc"
+1 -1
View File
@@ -16,7 +16,7 @@ export const ProjectTemplatesSchema = z.object({
orgId: z.string().uuid(), orgId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
type: z.string().nullable().optional() type: z.string().default("secret-manager")
}); });
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>; export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;
+2 -2
View File
@@ -25,12 +25,12 @@ export const ProjectsSchema = z.object({
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(), kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(), kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
description: z.string().nullable().optional(), description: z.string().nullable().optional(),
type: z.string().nullable().optional(), type: z.string(),
enforceCapitalization: z.boolean().default(false), enforceCapitalization: z.boolean().default(false),
hasDeleteProtection: z.boolean().default(false).nullable().optional(), hasDeleteProtection: z.boolean().default(false).nullable().optional(),
secretSharing: z.boolean().default(true), secretSharing: z.boolean().default(true),
showSnapshotsLegacy: z.boolean().default(false), showSnapshotsLegacy: z.boolean().default(false),
defaultProduct: z.string().default("secret-manager") defaultProduct: z.string().nullable().optional()
}); });
export type TProjects = z.infer<typeof ProjectsSchema>; export type TProjects = z.infer<typeof ProjectsSchema>;
@@ -0,0 +1,20 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const RemindersRecipientsSchema = z.object({
id: z.string().uuid(),
reminderId: z.string().uuid(),
userId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TRemindersRecipients = z.infer<typeof RemindersRecipientsSchema>;
export type TRemindersRecipientsInsert = Omit<z.input<typeof RemindersRecipientsSchema>, TImmutableDBKeys>;
export type TRemindersRecipientsUpdate = Partial<Omit<z.input<typeof RemindersRecipientsSchema>, TImmutableDBKeys>>;
+22
View File
@@ -0,0 +1,22 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const RemindersSchema = z.object({
id: z.string().uuid(),
secretId: z.string().uuid().nullable().optional(),
message: z.string().nullable().optional(),
repeatDays: z.number().nullable().optional(),
nextReminderDate: z.date(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TReminders = z.infer<typeof RemindersSchema>;
export type TRemindersInsert = Omit<z.input<typeof RemindersSchema>, TImmutableDBKeys>;
export type TRemindersUpdate = Partial<Omit<z.input<typeof RemindersSchema>, TImmutableDBKeys>>;
+166 -1
View File
@@ -3,11 +3,14 @@ import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { readLimit } from "@app/server/config/rateLimiter"; import { isValidFolderName } from "@app/lib/validator";
import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
import { SecretNameSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { booleanSchema } from "@app/server/routes/sanitizedSchemas"; import { booleanSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { commitChangesResponseSchema, resourceChangeSchema } from "@app/services/folder-commit/folder-commit-schemas"; import { commitChangesResponseSchema, resourceChangeSchema } from "@app/services/folder-commit/folder-commit-schemas";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
const commitHistoryItemSchema = z.object({ const commitHistoryItemSchema = z.object({
id: z.string(), id: z.string(),
@@ -413,4 +416,166 @@ export const registerPITRouter = async (server: FastifyZodProvider) => {
return result; return result;
} }
}); });
server.route({
method: "POST",
url: "/batch/commit",
config: {
rateLimit: secretsLimit
},
schema: {
hide: true,
description: "Commit changes",
security: [
{
bearerAuth: []
}
],
body: z.object({
projectId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
message: z
.string()
.trim()
.min(1)
.max(255)
.refine((message) => message.trim() !== "", {
message: "Commit message cannot be empty"
}),
changes: z.object({
secrets: z.object({
create: z
.array(
z.object({
secretKey: SecretNameSchema,
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
secretComment: z.string().trim().optional().default(""),
skipMultilineEncoding: z.boolean().optional(),
metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional()
})
)
.optional(),
update: z
.array(
z.object({
secretKey: SecretNameSchema,
newSecretName: SecretNameSchema.optional(),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.optional(),
secretComment: z.string().trim().optional().default(""),
skipMultilineEncoding: z.boolean().optional(),
metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional()
})
)
.optional(),
delete: z
.array(
z.object({
secretKey: SecretNameSchema
})
)
.optional()
}),
folders: z.object({
create: z
.array(
z.object({
folderName: z
.string()
.trim()
.refine((name) => isValidFolderName(name), {
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
}),
description: z.string().optional()
})
)
.optional(),
update: z
.array(
z.object({
folderName: z
.string()
.trim()
.refine((name) => isValidFolderName(name), {
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
}),
description: z.string().nullable().optional(),
id: z.string()
})
)
.optional(),
delete: z
.array(
z.object({
folderName: z
.string()
.trim()
.refine((name) => isValidFolderName(name), {
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
}),
id: z.string()
})
)
.optional()
})
})
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const result = await server.services.pit.processNewCommitRaw({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectId: req.body.projectId,
environment: req.body.environment,
secretPath: req.body.secretPath,
message: req.body.message,
changes: {
secrets: req.body.changes.secrets,
folders: req.body.changes.folders
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.PIT_PROCESS_NEW_COMMIT_RAW,
metadata: {
commitId: result.commitId,
approvalId: result.approvalId,
projectId: req.body.projectId,
environment: req.body.environment,
secretPath: req.body.secretPath,
message: req.body.message
}
}
});
for await (const event of result.secretMutationEvents) {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: req.body.projectId,
event
});
}
return { message: "success" };
}
});
}; };
@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas"; import { ProjectMembershipRole, ProjectTemplatesSchema, ProjectType } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns"; import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
@@ -104,6 +104,9 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
hide: false, hide: false,
tags: [ApiDocsTags.ProjectTemplates], tags: [ApiDocsTags.ProjectTemplates],
description: "List project templates for the current organization.", description: "List project templates for the current organization.",
querystring: z.object({
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
}),
response: { response: {
200: z.object({ 200: z.object({
projectTemplates: SanitizedProjectTemplateSchema.array() projectTemplates: SanitizedProjectTemplateSchema.array()
@@ -112,7 +115,10 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission); const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(
req.permission,
req.query.type
);
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name)); const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
@@ -191,6 +197,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
.describe(ProjectTemplates.CREATE.name), .describe(ProjectTemplates.CREATE.name),
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description), description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles), roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional() environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
}), }),
response: { response: {
@@ -6,6 +6,7 @@ import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-r
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router"; import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router"; import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router"; import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
import { registerOktaClientSecretRotationRouter } from "./okta-client-secret-rotation-router";
import { registerOracleDBCredentialsRotationRouter } from "./oracledb-credentials-rotation-router"; import { registerOracleDBCredentialsRotationRouter } from "./oracledb-credentials-rotation-router";
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router"; import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
@@ -22,5 +23,6 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter, [SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter, [SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter, [SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter [SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter,
[SecretRotation.OktaClientSecret]: registerOktaClientSecretRotationRouter
}; };
@@ -0,0 +1,19 @@
import {
CreateOktaClientSecretRotationSchema,
OktaClientSecretRotationGeneratedCredentialsSchema,
OktaClientSecretRotationSchema,
UpdateOktaClientSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/okta-client-secret";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
export const registerOktaClientSecretRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.OktaClientSecret,
server,
responseSchema: OktaClientSecretRotationSchema,
createSchema: CreateOktaClientSecretRotationSchema,
updateSchema: UpdateOktaClientSecretRotationSchema,
generatedCredentialsSchema: OktaClientSecretRotationGeneratedCredentialsSchema
});
@@ -315,10 +315,12 @@ export const registerSecretRotationEndpoints = <
querystring: z.object({ querystring: z.object({
deleteSecrets: z deleteSecrets: z
.enum(["true", "false"]) .enum(["true", "false"])
.optional()
.transform((value) => value === "true") .transform((value) => value === "true")
.describe(SecretRotations.DELETE(type).deleteSecrets), .describe(SecretRotations.DELETE(type).deleteSecrets),
revokeGeneratedCredentials: z revokeGeneratedCredentials: z
.enum(["true", "false"]) .enum(["true", "false"])
.optional()
.transform((value) => value === "true") .transform((value) => value === "true")
.describe(SecretRotations.DELETE(type).revokeGeneratedCredentials) .describe(SecretRotations.DELETE(type).revokeGeneratedCredentials)
}), }),
@@ -7,6 +7,7 @@ import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password"; import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials"; import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials"; import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
import { OktaClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/okta-client-secret";
import { OracleDBCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials"; import { OracleDBCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials"; import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema"; import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
@@ -23,7 +24,8 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
Auth0ClientSecretRotationListItemSchema, Auth0ClientSecretRotationListItemSchema,
AzureClientSecretRotationListItemSchema, AzureClientSecretRotationListItemSchema,
AwsIamUserSecretRotationListItemSchema, AwsIamUserSecretRotationListItemSchema,
LdapPasswordRotationListItemSchema LdapPasswordRotationListItemSchema,
OktaClientSecretRotationListItemSchema
]); ]);
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => { export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -116,7 +117,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -272,7 +274,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null }); const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
@@ -337,7 +340,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: accessApprovalPolicy.projectId, projectId: accessApprovalPolicy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
@@ -533,7 +537,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: policy.projectId, projectId: policy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
@@ -583,7 +588,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" }); throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@@ -622,7 +628,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: policy.projectId, projectId: policy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
@@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import msFn from "ms"; import msFn from "ms";
import { ProjectMembershipRole } from "@app/db/schemas"; import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
@@ -107,7 +107,8 @@ export const accessApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" }); throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@@ -216,7 +217,7 @@ export const accessApprovalRequestServiceFactory = ({
); );
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`; const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
const approvalUrl = `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`; const approvalUrl = `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`;
await triggerWorkflowIntegrationNotification({ await triggerWorkflowIntegrationNotification({
input: { input: {
@@ -289,7 +290,8 @@ export const accessApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" }); throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@@ -335,7 +337,8 @@ export const accessApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: accessApprovalRequest.projectId, projectId: accessApprovalRequest.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
@@ -551,7 +554,7 @@ export const accessApprovalRequestServiceFactory = ({
bypassReason: bypassReason || "No reason provided", bypassReason: bypassReason || "No reason provided",
secretPath: policy.secretPath || "/", secretPath: policy.secretPath || "/",
environment, environment,
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`, approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`,
requestType: "access" requestType: "access"
}, },
template: SmtpTemplates.AccessSecretRequestBypassed template: SmtpTemplates.AccessSecretRequestBypassed
@@ -582,7 +585,8 @@ export const accessApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" }); throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -37,7 +38,8 @@ export const assumePrivilegeServiceFactory = ({
actorId: actorPermissionDetails.id, actorId: actorPermissionDetails.id,
projectId, projectId,
actorAuthMethod: actorPermissionDetails.authMethod, actorAuthMethod: actorPermissionDetails.authMethod,
actorOrgId: actorPermissionDetails.orgId actorOrgId: actorPermissionDetails.orgId,
actionProjectType: ActionProjectType.Any
}); });
if (targetActorType === ActorType.USER) { if (targetActorType === ActorType.USER) {
@@ -58,7 +60,8 @@ export const assumePrivilegeServiceFactory = ({
actorId: targetActorId, actorId: targetActorId,
projectId, projectId,
actorAuthMethod: actorPermissionDetails.authMethod, actorAuthMethod: actorPermissionDetails.authMethod,
actorOrgId: actorPermissionDetails.orgId actorOrgId: actorPermissionDetails.orgId,
actionProjectType: ActionProjectType.Any
}); });
const appCfg = getConfig(); const appCfg = getConfig();
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { requestContext } from "@fastify/request-context"; import { requestContext } from "@fastify/request-context";
import { ActionProjectType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
@@ -37,7 +38,8 @@ export const auditLogServiceFactory = ({
actorId, actorId,
projectId: filter.projectId, projectId: filter.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
} else { } else {
@@ -449,6 +449,7 @@ export enum EventType {
PIT_REVERT_COMMIT = "pit-revert-commit", PIT_REVERT_COMMIT = "pit-revert-commit",
PIT_GET_FOLDER_STATE = "pit-get-folder-state", PIT_GET_FOLDER_STATE = "pit-get-folder-state",
PIT_COMPARE_FOLDER_STATES = "pit-compare-folder-states", PIT_COMPARE_FOLDER_STATES = "pit-compare-folder-states",
PIT_PROCESS_NEW_COMMIT_RAW = "pit-process-new-commit-raw",
SECRET_SCANNING_DATA_SOURCE_LIST = "secret-scanning-data-source-list", SECRET_SCANNING_DATA_SOURCE_LIST = "secret-scanning-data-source-list",
SECRET_SCANNING_DATA_SOURCE_CREATE = "secret-scanning-data-source-create", SECRET_SCANNING_DATA_SOURCE_CREATE = "secret-scanning-data-source-create",
SECRET_SCANNING_DATA_SOURCE_UPDATE = "secret-scanning-data-source-update", SECRET_SCANNING_DATA_SOURCE_UPDATE = "secret-scanning-data-source-update",
@@ -467,7 +468,11 @@ export enum EventType {
CREATE_PROJECT = "create-project", CREATE_PROJECT = "create-project",
UPDATE_PROJECT = "update-project", UPDATE_PROJECT = "update-project",
DELETE_PROJECT = "delete-project" DELETE_PROJECT = "delete-project",
CREATE_SECRET_REMINDER = "create-secret-reminder",
GET_SECRET_REMINDER = "get-secret-reminder",
DELETE_SECRET_REMINDER = "delete-secret-reminder"
} }
export const filterableSecretEvents: EventType[] = [ export const filterableSecretEvents: EventType[] = [
@@ -1546,8 +1551,9 @@ interface UpdateFolderEvent {
metadata: { metadata: {
environment: string; environment: string;
folderId: string; folderId: string;
oldFolderName: string; oldFolderName?: string;
newFolderName: string; newFolderName: string;
newFolderDescription?: string;
folderPath: string; folderPath: string;
}; };
} }
@@ -3222,6 +3228,18 @@ interface PitCompareFolderStatesEvent {
}; };
} }
interface PitProcessNewCommitRawEvent {
type: EventType.PIT_PROCESS_NEW_COMMIT_RAW;
metadata: {
projectId: string;
environment: string;
secretPath: string;
message: string;
approvalId?: string;
commitId?: string;
};
}
interface SecretScanningDataSourceListEvent { interface SecretScanningDataSourceListEvent {
type: EventType.SECRET_SCANNING_DATA_SOURCE_LIST; type: EventType.SECRET_SCANNING_DATA_SOURCE_LIST;
metadata: { metadata: {
@@ -3312,6 +3330,31 @@ interface SecretScanningConfigUpdateEvent {
}; };
} }
interface SecretReminderCreateEvent {
type: EventType.CREATE_SECRET_REMINDER;
metadata: {
secretId: string;
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: string | null;
recipients?: string[] | null;
};
}
interface SecretReminderGetEvent {
type: EventType.GET_SECRET_REMINDER;
metadata: {
secretId: string;
};
}
interface SecretReminderDeleteEvent {
type: EventType.DELETE_SECRET_REMINDER;
metadata: {
secretId: string;
};
}
interface SecretScanningConfigReadEvent { interface SecretScanningConfigReadEvent {
type: EventType.SECRET_SCANNING_CONFIG_GET; type: EventType.SECRET_SCANNING_CONFIG_GET;
metadata?: Record<string, never>; // not needed, based off projectId metadata?: Record<string, never>; // not needed, based off projectId
@@ -3658,6 +3701,7 @@ export type Event =
| PitRevertCommitEvent | PitRevertCommitEvent
| PitCompareFolderStatesEvent | PitCompareFolderStatesEvent
| PitGetFolderStateEvent | PitGetFolderStateEvent
| PitProcessNewCommitRawEvent
| SecretScanningDataSourceListEvent | SecretScanningDataSourceListEvent
| SecretScanningDataSourceGetEvent | SecretScanningDataSourceGetEvent
| SecretScanningDataSourceCreateEvent | SecretScanningDataSourceCreateEvent
@@ -3674,4 +3718,7 @@ export type Event =
| OrgUpdateEvent | OrgUpdateEvent
| ProjectCreateEvent | ProjectCreateEvent
| ProjectUpdateEvent | ProjectUpdateEvent
| ProjectDeleteEvent; | ProjectDeleteEvent
| SecretReminderCreateEvent
| SecretReminderGetEvent
| SecretReminderDeleteEvent;
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@@ -77,7 +78,8 @@ export const certificateAuthorityCrlServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -1,6 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import RE2 from "re2"; import RE2 from "re2";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { import {
@@ -84,7 +85,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -200,7 +202,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
@@ -297,7 +300,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
@@ -385,7 +389,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -432,7 +437,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -1,5 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { import {
@@ -78,7 +79,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -207,7 +209,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -358,7 +361,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -423,7 +427,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -487,7 +492,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
// verify user has access to each env in request // verify user has access to each env in request
@@ -530,7 +536,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionDynamicSecretActions.ReadRootCredential,
@@ -578,7 +585,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -615,7 +623,8 @@ export const dynamicSecretServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId, projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) => const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
@@ -659,7 +668,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path); const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
@@ -566,6 +566,14 @@ export const gatewayServiceFactory = ({
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` }); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
const orgGatewayConfig = await orgGatewayConfigDAL.findById(gateway.orgGatewayRootCaId); const orgGatewayConfig = await orgGatewayConfigDAL.findById(gateway.orgGatewayRootCaId);
const orgLicensePlan = await licenseService.getPlan(orgGatewayConfig.orgId);
if (!orgLicensePlan.gateway) {
throw new BadRequestError({
message: "Please upgrade your instance to Infisical's Enterprise plan to use gateways."
});
}
const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization, type: KmsDataKey.Organization,
orgId: orgGatewayConfig.orgId orgId: orgGatewayConfig.orgId
@@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; import { packRules } from "@casl/ability/extra";
import { TableName } from "@app/db/schemas"; import { ActionProjectType, TableName } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -61,7 +61,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit, ProjectPermissionIdentityActions.Edit,
@@ -72,7 +73,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId: identityId, actorId: identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -158,7 +160,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit, ProjectPermissionIdentityActions.Edit,
@@ -169,7 +172,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId: identityProjectMembership.identityId, actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -256,7 +260,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit, ProjectPermissionIdentityActions.Edit,
@@ -267,7 +272,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId: identityProjectMembership.identityId, actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem, membership.shouldUseNewPrivilegeSystem,
@@ -315,7 +321,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read, ProjectPermissionIdentityActions.Read,
@@ -349,7 +356,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read, ProjectPermissionIdentityActions.Read,
@@ -384,7 +392,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read, ProjectPermissionIdentityActions.Read,
@@ -1,6 +1,7 @@
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"; import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -72,7 +73,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -85,7 +87,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId: identityId, actorId: identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -172,7 +175,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -185,7 +189,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId: identityProjectMembership.identityId, actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -288,7 +293,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit, ProjectPermissionIdentityActions.Edit,
@@ -300,7 +306,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId: identityProjectMembership.identityId, actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem, membership.shouldUseNewPrivilegeSystem,
@@ -359,7 +366,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read, ProjectPermissionIdentityActions.Read,
@@ -401,7 +409,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
+13 -6
View File
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { isValidIp } from "@app/lib/ip"; import { isValidIp } from "@app/lib/ip";
@@ -78,7 +79,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -131,7 +133,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId: kmipClient.projectId, projectId: kmipClient.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -162,7 +165,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId: kmipClient.projectId, projectId: kmipClient.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -195,7 +199,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId: kmipClient.projectId, projectId: kmipClient.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
@@ -216,7 +221,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
@@ -252,7 +258,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId: kmipClient.projectId, projectId: kmipClient.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -1,6 +1,7 @@
import { MongoAbility, RawRuleOf } from "@casl/ability"; import { MongoAbility, RawRuleOf } from "@casl/ability";
import { MongoQuery } from "@ucast/mongo2js"; import { MongoQuery } from "@ucast/mongo2js";
import { ActionProjectType } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { OrgPermissionSet } from "./org-permission"; import { OrgPermissionSet } from "./org-permission";
@@ -20,6 +21,7 @@ export type TGetUserProjectPermissionArg = {
userId: string; userId: string;
projectId: string; projectId: string;
authMethod: ActorAuthMethod; authMethod: ActorAuthMethod;
actionProjectType: ActionProjectType;
userOrgId?: string; userOrgId?: string;
}; };
@@ -27,12 +29,14 @@ export type TGetIdentityProjectPermissionArg = {
identityId: string; identityId: string;
projectId: string; projectId: string;
identityOrgId?: string; identityOrgId?: string;
actionProjectType: ActionProjectType;
}; };
export type TGetServiceTokenProjectPermissionArg = { export type TGetServiceTokenProjectPermissionArg = {
serviceTokenId: string; serviceTokenId: string;
projectId: string; projectId: string;
actorOrgId?: string; actorOrgId?: string;
actionProjectType: ActionProjectType;
}; };
export type TGetProjectPermissionArg = { export type TGetProjectPermissionArg = {
@@ -41,6 +45,7 @@ export type TGetProjectPermissionArg = {
projectId: string; projectId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId?: string; actorOrgId?: string;
actionProjectType: ActionProjectType;
}; };
export type TPermissionServiceFactory = { export type TPermissionServiceFactory = {
@@ -138,7 +143,13 @@ export type TPermissionServiceFactory = {
}; };
} }
>; >;
getUserProjectPermission: ({ userId, projectId, authMethod, userOrgId }: TGetUserProjectPermissionArg) => Promise<{ getUserProjectPermission: ({
userId,
projectId,
authMethod,
userOrgId,
actionProjectType
}: TGetUserProjectPermissionArg) => Promise<{
permission: MongoAbility<ProjectPermissionSet, MongoQuery>; permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: { membership: {
id: string; id: string;
@@ -5,6 +5,7 @@ import { MongoQuery } from "@ucast/mongo2js";
import handlebars from "handlebars"; import handlebars from "handlebars";
import { import {
ActionProjectType,
OrgMembershipRole, OrgMembershipRole,
ProjectMembershipRole, ProjectMembershipRole,
ServiceTokenScopes, ServiceTokenScopes,
@@ -213,7 +214,8 @@ export const permissionServiceFactory = ({
userId, userId,
projectId, projectId,
authMethod, authMethod,
userOrgId userOrgId,
actionProjectType
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => { }: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId); const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" }); if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
@@ -240,6 +242,12 @@ export const permissionServiceFactory = ({
userProjectPermission.orgRole userProjectPermission.orgRole
); );
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
});
}
// join two permissions and pass to build the final permission set // join two permissions and pass to build the final permission set
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges = const additionalPrivileges =
@@ -287,7 +295,8 @@ export const permissionServiceFactory = ({
const getIdentityProjectPermission = async ({ const getIdentityProjectPermission = async ({
identityId, identityId,
projectId, projectId,
identityOrgId identityOrgId,
actionProjectType
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => { }: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId); const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
if (!identityProjectPermission) if (!identityProjectPermission)
@@ -307,6 +316,12 @@ export const permissionServiceFactory = ({
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" }); throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
} }
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
});
}
const rolePermissions = const rolePermissions =
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges = const additionalPrivileges =
@@ -361,7 +376,8 @@ export const permissionServiceFactory = ({
const getServiceTokenProjectPermission = async ({ const getServiceTokenProjectPermission = async ({
serviceTokenId, serviceTokenId,
projectId, projectId,
actorOrgId actorOrgId,
actionProjectType
}: TGetServiceTokenProjectPermissionArg) => { }: TGetServiceTokenProjectPermissionArg) => {
const serviceToken = await serviceTokenDAL.findById(serviceTokenId); const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` }); if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
@@ -386,6 +402,12 @@ export const permissionServiceFactory = ({
}); });
} }
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
throw new BadRequestError({
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
});
}
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []); const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
return { return {
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions), permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
@@ -537,7 +559,8 @@ export const permissionServiceFactory = ({
actorId: inputActorId, actorId: inputActorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => { }: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
let actor = inputActor; let actor = inputActor;
let actorId = inputActorId; let actorId = inputActorId;
@@ -558,19 +581,22 @@ export const permissionServiceFactory = ({
userId: actorId, userId: actorId,
projectId, projectId,
authMethod: actorAuthMethod, authMethod: actorAuthMethod,
userOrgId: actorOrgId userOrgId: actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>; }) as Promise<TProjectPermissionRT<T>>;
case ActorType.SERVICE: case ActorType.SERVICE:
return getServiceTokenProjectPermission({ return getServiceTokenProjectPermission({
serviceTokenId: actorId, serviceTokenId: actorId,
projectId, projectId,
actorOrgId actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>; }) as Promise<TProjectPermissionRT<T>>;
case ActorType.IDENTITY: case ActorType.IDENTITY:
return getIdentityProjectPermission({ return getIdentityProjectPermission({
identityId: actorId, identityId: actorId,
projectId, projectId,
identityOrgId: actorOrgId identityOrgId: actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>; }) as Promise<TProjectPermissionRT<T>>;
default: default:
throw new BadRequestError({ throw new BadRequestError({
+379 -7
View File
@@ -1,29 +1,53 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { Event, EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { ResourceType, TFolderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service"; import { TFolderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal";
import {
ResourceType,
TCommitResourceChangeDTO,
TFolderCommitServiceFactory
} from "@app/services/folder-commit/folder-commit-service";
import { import {
isFolderCommitChange, isFolderCommitChange,
isSecretCommitChange isSecretCommitChange
} from "@app/services/folder-commit-changes/folder-commit-changes-dal"; } from "@app/services/folder-commit-changes/folder-commit-changes-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TProcessNewCommitRawDTO } from "@app/services/secret/secret-types";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
import { TSecretV2BridgeServiceFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-service";
import { SecretOperations, SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
import { TPermissionServiceFactory } from "../permission/permission-service-types"; import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TSecretApprovalPolicyServiceFactory } from "../secret-approval-policy/secret-approval-policy-service";
import { TSecretApprovalRequestServiceFactory } from "../secret-approval-request/secret-approval-request-service";
type TPitServiceFactoryDep = { type TPitServiceFactoryDep = {
folderCommitService: TFolderCommitServiceFactory; folderCommitService: TFolderCommitServiceFactory;
secretService: Pick<TSecretServiceFactory, "getSecretVersionsV2ByIds" | "getChangeVersions">; secretService: Pick<TSecretServiceFactory, "getSecretVersionsV2ByIds" | "getChangeVersions">;
folderService: Pick<TSecretFolderServiceFactory, "getFolderById" | "getFolderVersions">; folderService: Pick<
TSecretFolderServiceFactory,
"getFolderById" | "getFolderVersions" | "createManyFolders" | "updateManyFolders" | "deleteManyFolders"
>;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds">; folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds" | "findBySecretPath">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">; projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
secretApprovalRequestService: Pick<
TSecretApprovalRequestServiceFactory,
"generateSecretApprovalRequest" | "generateSecretApprovalRequestV2Bridge"
>;
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findProjectBySlug" | "findById">;
secretV2BridgeService: TSecretV2BridgeServiceFactory;
folderCommitDAL: Pick<TFolderCommitDALFactory, "transaction">;
}; };
export type TPitServiceFactory = ReturnType<typeof pitServiceFactory>; export type TPitServiceFactory = ReturnType<typeof pitServiceFactory>;
@@ -34,7 +58,12 @@ export const pitServiceFactory = ({
folderService, folderService,
permissionService, permissionService,
folderDAL, folderDAL,
projectEnvDAL projectEnvDAL,
secretApprovalRequestService,
secretApprovalPolicyService,
projectDAL,
secretV2BridgeService,
folderCommitDAL
}: TPitServiceFactoryDep) => { }: TPitServiceFactoryDep) => {
const getCommitsCount = async ({ const getCommitsCount = async ({
actor, actor,
@@ -320,7 +349,8 @@ export const pitServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(userPermission).throwUnlessCan( ForbiddenError.from(userPermission).throwUnlessCan(
@@ -471,6 +501,347 @@ export const pitServiceFactory = ({
}); });
}; };
const processNewCommitRaw = async ({
actorId,
projectId,
environment,
actor,
actorOrgId,
actorAuthMethod,
secretPath,
message,
changes = {
secrets: {
create: [],
update: [],
delete: []
},
folders: {
create: [],
update: [],
delete: []
}
}
}: {
actorId: string;
projectId: string;
environment: string;
actor: ActorType;
actorOrgId: string;
actorAuthMethod: ActorAuthMethod;
secretPath: string;
message: string;
changes: TProcessNewCommitRawDTO;
}) => {
const policy =
actor === ActorType.USER
? await secretApprovalPolicyService.getSecretApprovalPolicy(projectId, environment, secretPath)
: undefined;
const secretMutationEvents: Event[] = [];
const project = await projectDAL.findById(projectId);
if (project.enforceCapitalization) {
const caseViolatingSecretKeys = [
// Check create operations
...(changes.secrets?.create
?.filter((sec) => sec.secretKey !== sec.secretKey.toUpperCase())
.map((sec) => sec.secretKey) ?? []),
// Check update operations
...(changes.secrets?.update
?.filter((sec) => sec.newSecretName && sec.newSecretName !== sec.newSecretName.toUpperCase())
.map((sec) => sec.secretKey) ?? [])
];
if (caseViolatingSecretKeys.length) {
throw new BadRequestError({
message: `Secret names must be in UPPERCASE per project requirements: ${caseViolatingSecretKeys.join(
", "
)}. You can disable this requirement in project settings`
});
}
}
const response = await folderCommitDAL.transaction(async (trx) => {
const targetFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath, trx);
if (!targetFolder)
throw new NotFoundError({
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`,
name: "CreateManySecret"
});
const commitChanges: TCommitResourceChangeDTO[] = [];
const folderChanges: { create: string[]; update: string[]; delete: string[] } = {
create: [],
update: [],
delete: []
};
if ((changes.folders?.create?.length ?? 0) > 0) {
const createdFolders = await folderService.createManyFolders({
projectId,
actor,
actorId,
actorOrgId,
actorAuthMethod,
folders:
changes.folders?.create?.map((folder) => ({
name: folder.folderName,
environment,
path: secretPath,
description: folder.description
})) ?? [],
tx: trx,
commitChanges
});
const newFolderEvents = createdFolders.folders.map(
(folder) =>
({
type: EventType.CREATE_FOLDER,
metadata: {
environment,
folderId: folder.id,
folderName: folder.name,
folderPath: secretPath,
...(folder.description ? { description: folder.description } : {})
}
}) as Event
);
secretMutationEvents.push(...newFolderEvents);
folderChanges.create.push(...createdFolders.folders.map((folder) => folder.id));
}
if ((changes.folders?.update?.length ?? 0) > 0) {
const updatedFolders = await folderService.updateManyFolders({
projectId,
actor,
actorId,
actorOrgId,
actorAuthMethod,
folders:
changes.folders?.update?.map((folder) => ({
environment,
path: secretPath,
id: folder.id,
name: folder.folderName,
description: folder.description
})) ?? [],
tx: trx,
commitChanges
});
const updatedFolderEvents = updatedFolders.newFolders.map(
(folder) =>
({
type: EventType.UPDATE_FOLDER,
metadata: {
environment,
folderId: folder.id,
folderPath: secretPath,
newFolderName: folder.name,
newFolderDescription: folder.description
}
}) as Event
);
secretMutationEvents.push(...updatedFolderEvents);
folderChanges.update.push(...updatedFolders.newFolders.map((folder) => folder.id));
}
if ((changes.folders?.delete?.length ?? 0) > 0) {
const deletedFolders = await folderService.deleteManyFolders({
projectId,
actor,
actorId,
actorOrgId,
actorAuthMethod,
folders:
changes.folders?.delete?.map((folder) => ({
environment,
path: secretPath,
idOrName: folder.id
})) ?? [],
tx: trx,
commitChanges
});
const deletedFolderEvents = deletedFolders.folders.map(
(folder) =>
({
type: EventType.DELETE_FOLDER,
metadata: {
environment,
folderId: folder.id,
folderPath: secretPath,
folderName: folder.name
}
}) as Event
);
secretMutationEvents.push(...deletedFolderEvents);
folderChanges.delete.push(...deletedFolders.folders.map((folder) => folder.id));
}
if (policy) {
if (
(changes.secrets?.create?.length ?? 0) > 0 ||
(changes.secrets?.update?.length ?? 0) > 0 ||
(changes.secrets?.delete?.length ?? 0) > 0
) {
const approval = await secretApprovalRequestService.generateSecretApprovalRequestV2Bridge({
policy,
secretPath,
environment,
projectId,
actor,
actorId,
actorOrgId,
actorAuthMethod,
data: {
[SecretOperations.Create]:
changes.secrets?.create?.map((el) => ({
tagIds: el.tagIds,
secretValue: el.secretValue,
secretComment: el.secretComment,
metadata: el.metadata,
skipMultilineEncoding: el.skipMultilineEncoding,
secretKey: el.secretKey,
secretMetadata: el.secretMetadata
})) ?? [],
[SecretOperations.Update]:
changes.secrets?.update?.map((el) => ({
tagIds: el.tagIds,
newSecretName: el.newSecretName,
secretValue: el.secretValue,
secretComment: el.secretComment,
metadata: el.metadata,
skipMultilineEncoding: el.skipMultilineEncoding,
secretKey: el.secretKey,
secretMetadata: el.secretMetadata
})) ?? [],
[SecretOperations.Delete]:
changes.secrets?.delete?.map((el) => ({
secretKey: el.secretKey
})) ?? []
}
});
return {
approvalId: approval.id,
folderChanges,
secretMutationEvents
};
}
return {
folderChanges,
secretMutationEvents
};
}
if ((changes.secrets?.create?.length ?? 0) > 0) {
const newSecrets = await secretV2BridgeService.createManySecret({
secretPath,
environment,
projectId,
actorAuthMethod,
actorOrgId,
actor,
actorId,
secrets: changes.secrets?.create ?? [],
tx: trx,
commitChanges
});
secretMutationEvents.push({
type: EventType.CREATE_SECRETS,
metadata: {
environment,
secretPath,
secrets: newSecrets.map((secret) => ({
secretId: secret.id,
secretKey: secret.secretKey,
secretVersion: secret.version
}))
}
});
}
if ((changes.secrets?.update?.length ?? 0) > 0) {
const updatedSecrets = await secretV2BridgeService.updateManySecret({
secretPath,
environment,
projectId,
actorAuthMethod,
actorOrgId,
actor,
actorId,
secrets: changes.secrets?.update ?? [],
mode: SecretUpdateMode.FailOnNotFound,
tx: trx,
commitChanges
});
secretMutationEvents.push({
type: EventType.UPDATE_SECRETS,
metadata: {
environment,
secretPath,
secrets: updatedSecrets.map((secret) => ({
secretId: secret.id,
secretKey: secret.secretKey,
secretVersion: secret.version
}))
}
});
}
if ((changes.secrets?.delete?.length ?? 0) > 0) {
const deletedSecrets = await secretV2BridgeService.deleteManySecret({
secretPath,
environment,
projectId,
actorAuthMethod,
actorOrgId,
actor,
actorId,
secrets: changes.secrets?.delete ?? [],
tx: trx,
commitChanges
});
secretMutationEvents.push({
type: EventType.DELETE_SECRETS,
metadata: {
environment,
secretPath,
secrets: deletedSecrets.map((secret) => ({
secretId: secret.id,
secretKey: secret.secretKey,
secretVersion: secret.version
}))
}
});
}
if (commitChanges?.length > 0) {
const commit = await folderCommitService.createCommit(
{
actor: {
type: actor || ActorType.PLATFORM,
metadata: {
id: actorId
}
},
message,
folderId: targetFolder.id,
changes: commitChanges
},
trx
);
return {
folderChanges,
commitId: commit?.id,
secretMutationEvents
};
}
return {
folderChanges,
secretMutationEvents
};
});
return response;
};
return { return {
getCommitsCount, getCommitsCount,
getCommitsForFolder, getCommitsForFolder,
@@ -478,6 +849,7 @@ export const pitServiceFactory = ({
compareCommitChanges, compareCommitChanges,
rollbackToCommit, rollbackToCommit,
revertCommit, revertCommit,
getFolderStateAtCommit getFolderStateAtCommit,
processNewCommitRaw
}; };
}; };
@@ -1,3 +1,4 @@
import { ProjectType } from "@app/db/schemas";
import { import {
InfisicalProjectTemplate, InfisicalProjectTemplate,
TUnpackedPermission TUnpackedPermission
@@ -6,18 +7,21 @@ import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"
import { ProjectTemplateDefaultEnvironments } from "./project-template-constants"; import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
export const getDefaultProjectTemplate = (orgId: string) => ({ export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
type,
name: InfisicalProjectTemplate.Default, name: InfisicalProjectTemplate.Default,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
description: `Infisical's default project template`, description: `Infisical's ${type} default project template`,
environments: ProjectTemplateDefaultEnvironments, environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
roles: getPredefinedRoles({ projectId: "project-template" }) as Array<{ roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
name: string; ({ name, slug, permissions }) => ({
slug: string; name,
permissions: TUnpackedPermission[]; slug,
}>, permissions: permissions as TUnpackedPermission[]
})
),
orgId orgId
}); });
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; import { packRules } from "@casl/ability/extra";
import { TProjectTemplates } from "@app/db/schemas"; import { ProjectType, TProjectTemplates } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -29,11 +29,13 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
...rest, ...rest,
environments: environments as TProjectTemplateEnvironment[], environments: environments as TProjectTemplateEnvironment[],
roles: [ roles: [
...getPredefinedRoles({ projectId: "project-template" }).map(({ name, slug, permissions }) => ({ ...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
name, ({ name, slug, permissions }) => ({
slug, name,
permissions: permissions as TUnpackedPermission[] slug,
})), permissions: permissions as TUnpackedPermission[]
})
),
...(roles as TProjectTemplateRole[]).map((role) => ({ ...(roles as TProjectTemplateRole[]).map((role) => ({
...role, ...role,
permissions: unpackPermissions(role.permissions) permissions: unpackPermissions(role.permissions)
@@ -46,7 +48,10 @@ export const projectTemplateServiceFactory = ({
permissionService, permissionService,
projectTemplateDAL projectTemplateDAL
}: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => { }: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => {
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (actor) => { const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (
actor,
type
) => {
const plan = await licenseService.getPlan(actor.orgId); const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates) if (!plan.projectTemplates)
@@ -65,11 +70,14 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
const projectTemplates = await projectTemplateDAL.find({ const projectTemplates = await projectTemplateDAL.find({
orgId: actor.orgId orgId: actor.orgId,
...(type ? { type } : {})
}); });
return [ return [
getDefaultProjectTemplate(actor.orgId), ...(type
? [getDefaultProjectTemplate(actor.orgId, type)]
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
...projectTemplates.map((template) => $unpackProjectTemplate(template)) ...projectTemplates.map((template) => $unpackProjectTemplate(template))
]; ];
}; };
@@ -134,7 +142,7 @@ export const projectTemplateServiceFactory = ({
}; };
const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async ( const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async (
{ roles, environments, ...params }, { roles, environments, type, ...params },
actor actor
) => { ) => {
const plan = await licenseService.getPlan(actor.orgId); const plan = await licenseService.getPlan(actor.orgId);
@@ -154,6 +162,10 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
if (environments && type !== ProjectType.SecretManager) {
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
}
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) { if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({ throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -176,8 +188,10 @@ export const projectTemplateServiceFactory = ({
const projectTemplate = await projectTemplateDAL.create({ const projectTemplate = await projectTemplateDAL.create({
...params, ...params,
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))), roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
environments: environments ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null, environments:
orgId: actor.orgId type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
orgId: actor.orgId,
type
}); });
return $unpackProjectTemplate(projectTemplate); return $unpackProjectTemplate(projectTemplate);
@@ -208,6 +222,11 @@ export const projectTemplateServiceFactory = ({
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
if (projectTemplate.type !== ProjectType.SecretManager && environments)
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
if (projectTemplate.type === ProjectType.SecretManager && environments === null)
throw new BadRequestError({ message: "Environments cannot be removed for SecretManager project templates" });
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) { if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({ throw new BadRequestError({
@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { ProjectMembershipRole, TProjectEnvironments } from "@app/db/schemas"; import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
@@ -15,6 +15,7 @@ export type TProjectTemplateRole = {
export type TCreateProjectTemplateDTO = { export type TCreateProjectTemplateDTO = {
name: string; name: string;
type: ProjectType;
description?: string; description?: string;
roles: TProjectTemplateRole[]; roles: TProjectTemplateRole[];
environments?: TProjectTemplateEnvironment[] | null; environments?: TProjectTemplateEnvironment[] | null;
@@ -29,11 +30,15 @@ export enum InfisicalProjectTemplate {
} }
export type TProjectTemplateServiceFactory = { export type TProjectTemplateServiceFactory = {
listProjectTemplatesByOrg: (actor: OrgServiceActor) => Promise< listProjectTemplatesByOrg: (
actor: OrgServiceActor,
type?: ProjectType
) => Promise<
( (
| { | {
id: string; id: string;
name: InfisicalProjectTemplate; name: InfisicalProjectTemplate;
type: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
description: string; description: string;
@@ -58,6 +63,7 @@ export type TProjectTemplateServiceFactory = {
} }
| { | {
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
type: string;
roles: { roles: {
permissions: { permissions: {
action: string[]; action: string[];
@@ -94,6 +100,7 @@ export type TProjectTemplateServiceFactory = {
}[]; }[];
name: string; name: string;
orgId: string; orgId: string;
type: string;
id: string; id: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
@@ -118,6 +125,7 @@ export type TProjectTemplateServiceFactory = {
name: string; name: string;
orgId: string; orgId: string;
id: string; id: string;
type: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
description?: string | null | undefined; description?: string | null | undefined;
@@ -140,6 +148,7 @@ export type TProjectTemplateServiceFactory = {
name: string; name: string;
orgId: string; orgId: string;
id: string; id: string;
type: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
description?: string | null | undefined; description?: string | null | undefined;
@@ -162,6 +171,7 @@ export type TProjectTemplateServiceFactory = {
}[]; }[];
name: string; name: string;
orgId: string; orgId: string;
type: string;
id: string; id: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
@@ -184,6 +194,7 @@ export type TProjectTemplateServiceFactory = {
name: string; name: string;
}[]; }[];
name: string; name: string;
type: string;
orgId: string; orgId: string;
id: string; id: string;
createdAt: Date; createdAt: Date;
@@ -1,7 +1,7 @@
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability"; import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { TableName } from "@app/db/schemas"; import { ActionProjectType, TableName } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -61,7 +61,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({ const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
@@ -69,7 +70,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId: projectMembership.userId, actorId: projectMembership.userId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -164,7 +166,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission } = await permissionService.getProjectPermission({ const { permission: targetUserPermission } = await permissionService.getProjectPermission({
@@ -172,7 +175,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId: projectMembership.userId, actorId: projectMembership.userId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -272,7 +276,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
@@ -317,7 +322,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
@@ -343,7 +349,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import picomatch from "picomatch"; import picomatch from "picomatch";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -110,7 +111,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
@@ -304,7 +306,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: secretApprovalPolicy.projectId, projectId: secretApprovalPolicy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
@@ -459,7 +462,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: sapPolicy.projectId, projectId: sapPolicy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
@@ -498,7 +502,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
@@ -542,7 +547,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
return getSecretApprovalPolicy(projectId, environment, secretPath); return getSecretApprovalPolicy(projectId, environment, secretPath);
@@ -568,7 +574,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: sapPolicy.projectId, projectId: sapPolicy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
@@ -36,7 +36,7 @@ export const sendApprovalEmailsFn = async ({
firstName: reviewerUser.firstName, firstName: reviewerUser.firstName,
projectName: project.name, projectName: project.name,
organizationName: project.organization.name, organizationName: project.organization.name,
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval?requestId=${secretApprovalRequest.id}` approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval?requestId=${secretApprovalRequest.id}`
}, },
template: SmtpTemplates.SecretApprovalRequestNeedsReview template: SmtpTemplates.SecretApprovalRequestNeedsReview
}); });
@@ -1,7 +1,9 @@
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { Knex } from "knex";
import { import {
ActionProjectType,
ProjectMembershipRole, ProjectMembershipRole,
SecretEncryptionAlgo, SecretEncryptionAlgo,
SecretKeyEncoding, SecretKeyEncoding,
@@ -183,7 +185,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId); const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId);
@@ -210,7 +213,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
@@ -262,7 +266,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if ( if (
!hasRole(ProjectMembershipRole.Admin) && !hasRole(ProjectMembershipRole.Admin) &&
@@ -411,7 +416,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: secretApprovalRequest.projectId, projectId: secretApprovalRequest.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if ( if (
!hasRole(ProjectMembershipRole.Admin) && !hasRole(ProjectMembershipRole.Admin) &&
@@ -480,7 +486,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: secretApprovalRequest.projectId, projectId: secretApprovalRequest.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if ( if (
!hasRole(ProjectMembershipRole.Admin) && !hasRole(ProjectMembershipRole.Admin) &&
@@ -536,7 +543,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if ( if (
@@ -954,7 +962,7 @@ export const secretApprovalRequestServiceFactory = ({
bypassReason, bypassReason,
secretPath: policy.secretPath, secretPath: policy.secretPath,
environment: env.name, environment: env.name,
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval` approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`
}, },
template: SmtpTemplates.AccessSecretRequestBypassed template: SmtpTemplates.AccessSecretRequestBypassed
}); });
@@ -1088,7 +1096,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
@@ -1368,8 +1377,9 @@ export const secretApprovalRequestServiceFactory = ({
policy, policy,
projectId, projectId,
secretPath, secretPath,
environment environment,
}: TGenerateSecretApprovalRequestV2BridgeDTO) => { trx: providedTx
}: TGenerateSecretApprovalRequestV2BridgeDTO & { trx?: Knex }) => {
if (actor === ActorType.SERVICE || actor === ActorType.Machine) if (actor === ActorType.SERVICE || actor === ActorType.Machine)
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" }); throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
@@ -1378,7 +1388,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) if (!folder)
@@ -1595,7 +1606,7 @@ export const secretApprovalRequestServiceFactory = ({
); );
}); });
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => { const executeApprovalRequestCreation = async (tx: Knex) => {
const doc = await secretApprovalRequestDAL.create( const doc = await secretApprovalRequestDAL.create(
{ {
folderId, folderId,
@@ -1657,7 +1668,11 @@ export const secretApprovalRequestServiceFactory = ({
} }
return { ...doc, commits: approvalCommits }; return { ...doc, commits: approvalCommits };
}); };
const secretApprovalRequest = providedTx
? await executeApprovalRequestCreation(providedTx)
: await secretApprovalRequestDAL.transaction(executeApprovalRequestCreation);
const user = await userDAL.findById(actorId); const user = await userDAL.findById(actorId);
const env = await projectEnvDAL.findOne({ id: policy.envId }); const env = await projectEnvDAL.findOne({ id: policy.envId });
@@ -0,0 +1,3 @@
export * from "./okta-client-secret-rotation-constants";
export * from "./okta-client-secret-rotation-schemas";
export * from "./okta-client-secret-rotation-types";
@@ -0,0 +1,15 @@
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
export const OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
name: "Okta Client Secret",
type: SecretRotation.OktaClientSecret,
connection: AppConnection.Okta,
template: {
secretsMapping: {
clientId: "OKTA_CLIENT_ID",
clientSecret: "OKTA_CLIENT_SECRET"
}
}
};
@@ -0,0 +1,273 @@
/* eslint-disable no-await-in-loop */
import { AxiosError } from "axios";
import {
TRotationFactory,
TRotationFactoryGetSecretsPayload,
TRotationFactoryIssueCredentials,
TRotationFactoryRevokeCredentials,
TRotationFactoryRotateCredentials
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { request } from "@app/lib/config/request";
import { delay as delayMs } from "@app/lib/delay";
import { BadRequestError } from "@app/lib/errors";
import { getOktaInstanceUrl } from "@app/services/app-connection/okta";
import {
TOktaClientSecret,
TOktaClientSecretRotationGeneratedCredentials,
TOktaClientSecretRotationWithConnection
} from "./okta-client-secret-rotation-types";
type OktaErrorResponse = { errorCode: string; errorSummary: string; errorCauses?: { errorSummary: string }[] };
const isOktaErrorResponse = (data: unknown): data is OktaErrorResponse => {
return (
typeof data === "object" &&
data !== null &&
"errorSummary" in data &&
typeof (data as OktaErrorResponse).errorSummary === "string"
);
};
const createErrorMessage = (error: unknown) => {
if (error instanceof AxiosError) {
if (error.response?.data && isOktaErrorResponse(error.response.data)) {
const oktaError = error.response.data;
if (oktaError.errorCauses && oktaError.errorCauses.length > 0) {
return oktaError.errorCauses[0].errorSummary;
}
return oktaError.errorSummary;
}
if (error.message) {
return error.message;
}
}
return "Unknown error";
};
// Delay between each revocation call in revokeCredentials
const DELAY_MS = 1000;
export const oktaClientSecretRotationFactory: TRotationFactory<
TOktaClientSecretRotationWithConnection,
TOktaClientSecretRotationGeneratedCredentials
> = (secretRotation) => {
const {
connection,
parameters: { clientId },
secretsMapping
} = secretRotation;
/**
* Creates a new client secret for the Okta app.
*/
const $rotateClientSecret = async () => {
const instanceUrl = await getOktaInstanceUrl(connection);
try {
const { data } = await request.post<TOktaClientSecret>(
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets`,
{},
{
headers: {
Accept: "application/json",
Authorization: `SSWS ${connection.credentials.apiToken}`
}
}
);
if (!data.client_secret || !data.id) {
throw new Error("Invalid response from Okta: missing 'client_secret' or secret 'id'.");
}
return {
clientSecret: data.client_secret,
secretId: data.id,
clientId
};
} catch (error: unknown) {
if (
error instanceof AxiosError &&
error.response?.data &&
isOktaErrorResponse(error.response.data) &&
error.response.data.errorCode === "E0000001"
) {
// Okta has a maximum of 2 secrets per app, thus we must warn the users in case they already have 2
throw new BadRequestError({
message: `Failed to add client secret to Okta app ${clientId}: You must have only a single secret for the Okta app prior to creating this secret rotation.`
});
}
throw new BadRequestError({
message: `Failed to add client secret to Okta app ${clientId}: ${createErrorMessage(error)}`
});
}
};
/**
* List client secrets.
*/
const $listClientSecrets = async () => {
const instanceUrl = await getOktaInstanceUrl(connection);
try {
const { data } = await request.get<TOktaClientSecret[]>(
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets`,
{
headers: {
Accept: "application/json",
Authorization: `SSWS ${connection.credentials.apiToken}`
}
}
);
return data;
} catch (error: unknown) {
throw new BadRequestError({
message: `Failed to list client secrets for Okta app ${clientId}: ${createErrorMessage(error)}`
});
}
};
/**
* Checks if a credential with the given secretId exists.
*/
const credentialExists = async (secretId: string): Promise<boolean> => {
const instanceUrl = await getOktaInstanceUrl(connection);
try {
const { data } = await request.get<TOktaClientSecret>(
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}`,
{
headers: {
Accept: "application/json",
Authorization: `SSWS ${connection.credentials.apiToken}`
}
}
);
return data.id === secretId;
} catch (_) {
return false;
}
};
/**
* Revokes a client secret from the Okta app using its secretId.
* First checks if the credential exists before attempting revocation.
*/
const revokeCredential = async (secretId: string) => {
// Check if credential exists before attempting revocation
const exists = await credentialExists(secretId);
if (!exists) {
return; // Credential doesn't exist, nothing to revoke
}
const instanceUrl = await getOktaInstanceUrl(connection);
try {
// First deactivate the secret
await request.post(
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}/lifecycle/deactivate`,
undefined,
{
headers: {
Authorization: `SSWS ${connection.credentials.apiToken}`
}
}
);
// Then delete it
await request.delete(`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}`, {
headers: {
Authorization: `SSWS ${connection.credentials.apiToken}`
}
});
} catch (error: unknown) {
if (
error instanceof AxiosError &&
error.response?.data &&
isOktaErrorResponse(error.response.data) &&
error.response.data.errorCode === "E0000001"
) {
// If this is the last secret, we cannot revoke it
return;
}
throw new BadRequestError({
message: `Failed to remove client secret with secretId ${secretId} from app ${clientId}: ${createErrorMessage(error)}`
});
}
};
/**
* Issues a new set of credentials.
*/
const issueCredentials: TRotationFactoryIssueCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
callback
) => {
const credentials = await $rotateClientSecret();
return callback(credentials);
};
/**
* Revokes a list of credentials.
*/
const revokeCredentials: TRotationFactoryRevokeCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
credentials,
callback
) => {
if (!credentials?.length) return callback();
for (const { secretId } of credentials) {
await revokeCredential(secretId);
await delayMs(DELAY_MS);
}
return callback();
};
/**
* Rotates credentials by issuing new ones and revoking the old.
*/
const rotateCredentials: TRotationFactoryRotateCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
oldCredentials,
callback,
activeCredentials
) => {
// Since in Okta you can only have a maximum of 2 secrets at a time, we must delete any other secret besides the current one PRIOR to generating the second secret
if (oldCredentials?.secretId) {
await revokeCredential(oldCredentials.secretId);
} else if (activeCredentials) {
// On the first rotation oldCredentials won't be set so we must find the second secret manually
const secrets = await $listClientSecrets();
if (secrets.length > 1) {
const nonActiveSecret = secrets.find((secret) => secret.id !== activeCredentials.secretId);
if (nonActiveSecret) {
await revokeCredential(nonActiveSecret.id);
}
}
}
const newCredentials = await $rotateClientSecret();
return callback(newCredentials);
};
/**
* Maps the generated credentials into the secret payload format.
*/
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TOktaClientSecretRotationGeneratedCredentials> = ({
clientSecret
}) => [
{ key: secretsMapping.clientId, value: clientId },
{ key: secretsMapping.clientSecret, value: clientSecret }
];
return {
issueCredentials,
revokeCredentials,
rotateCredentials,
getSecretsPayload
};
};
@@ -0,0 +1,68 @@
import { z } from "zod";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import {
BaseCreateSecretRotationSchema,
BaseSecretRotationSchema,
BaseUpdateSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
import { SecretRotations } from "@app/lib/api-docs";
import { SecretNameSchema } from "@app/server/lib/schemas";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
export const OktaClientSecretRotationGeneratedCredentialsSchema = z
.object({
clientId: z.string(),
clientSecret: z.string(),
secretId: z.string()
})
.array()
.min(1)
.max(2);
const OktaClientSecretRotationParametersSchema = z.object({
clientId: z
.string()
.trim()
.min(1, "Client ID Required")
.describe(SecretRotations.PARAMETERS.OKTA_CLIENT_SECRET.clientId)
});
const OktaClientSecretRotationSecretsMappingSchema = z.object({
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.OKTA_CLIENT_SECRET.clientId),
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.OKTA_CLIENT_SECRET.clientSecret)
});
export const OktaClientSecretRotationTemplateSchema = z.object({
secretsMapping: z.object({
clientId: z.string(),
clientSecret: z.string()
})
});
export const OktaClientSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.OktaClientSecret).extend({
type: z.literal(SecretRotation.OktaClientSecret),
parameters: OktaClientSecretRotationParametersSchema,
secretsMapping: OktaClientSecretRotationSecretsMappingSchema
});
export const CreateOktaClientSecretRotationSchema = BaseCreateSecretRotationSchema(
SecretRotation.OktaClientSecret
).extend({
parameters: OktaClientSecretRotationParametersSchema,
secretsMapping: OktaClientSecretRotationSecretsMappingSchema
});
export const UpdateOktaClientSecretRotationSchema = BaseUpdateSecretRotationSchema(
SecretRotation.OktaClientSecret
).extend({
parameters: OktaClientSecretRotationParametersSchema.optional(),
secretsMapping: OktaClientSecretRotationSecretsMappingSchema.optional()
});
export const OktaClientSecretRotationListItemSchema = z.object({
name: z.literal("Okta Client Secret"),
connection: z.literal(AppConnection.Okta),
type: z.literal(SecretRotation.OktaClientSecret),
template: OktaClientSecretRotationTemplateSchema
});
@@ -0,0 +1,40 @@
import { z } from "zod";
import { TOktaConnection } from "@app/services/app-connection/okta";
import {
CreateOktaClientSecretRotationSchema,
OktaClientSecretRotationGeneratedCredentialsSchema,
OktaClientSecretRotationListItemSchema,
OktaClientSecretRotationSchema
} from "./okta-client-secret-rotation-schemas";
export type TOktaClientSecretRotation = z.infer<typeof OktaClientSecretRotationSchema>;
export type TOktaClientSecretRotationInput = z.infer<typeof CreateOktaClientSecretRotationSchema>;
export type TOktaClientSecretRotationListItem = z.infer<typeof OktaClientSecretRotationListItemSchema>;
export type TOktaClientSecretRotationWithConnection = TOktaClientSecretRotation & {
connection: TOktaConnection;
};
export type TOktaClientSecretRotationGeneratedCredentials = z.infer<
typeof OktaClientSecretRotationGeneratedCredentialsSchema
>;
export interface TOktaClientSecretRotationParameters {
clientId: string;
secretId: string;
}
export interface TOktaClientSecretRotationSecretsMapping {
clientId: string;
clientSecret: string;
secretId: string;
}
export interface TOktaClientSecret {
id: string;
client_secret: string;
}
@@ -6,7 +6,8 @@ export enum SecretRotation {
Auth0ClientSecret = "auth0-client-secret", Auth0ClientSecret = "auth0-client-secret",
AzureClientSecret = "azure-client-secret", AzureClientSecret = "azure-client-secret",
AwsIamUserSecret = "aws-iam-user-secret", AwsIamUserSecret = "aws-iam-user-secret",
LdapPassword = "ldap-password" LdapPassword = "ldap-password",
OktaClientSecret = "okta-client-secret"
} }
export enum SecretRotationStatus { export enum SecretRotationStatus {
@@ -10,6 +10,7 @@ import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret"
import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password"; import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password";
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials"; import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials"; import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials";
import { OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./okta-client-secret";
import { ORACLEDB_CREDENTIALS_ROTATION_LIST_OPTION } from "./oracledb-credentials"; import { ORACLEDB_CREDENTIALS_ROTATION_LIST_OPTION } from "./oracledb-credentials";
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials"; import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums"; import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
@@ -30,7 +31,8 @@ const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2List
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION, [SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION, [SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION, [SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION [SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION,
[SecretRotation.OktaClientSecret]: OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION
}; };
export const listSecretRotationOptions = () => { export const listSecretRotationOptions = () => {
@@ -9,7 +9,8 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret", [SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
[SecretRotation.AzureClientSecret]: "Azure Client Secret", [SecretRotation.AzureClientSecret]: "Azure Client Secret",
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret", [SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
[SecretRotation.LdapPassword]: "LDAP Password" [SecretRotation.LdapPassword]: "LDAP Password",
[SecretRotation.OktaClientSecret]: "Okta Client Secret"
}; };
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = { export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
@@ -20,5 +21,6 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0, [SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets, [SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS, [SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
[SecretRotation.LdapPassword]: AppConnection.LDAP [SecretRotation.LdapPassword]: AppConnection.LDAP,
[SecretRotation.OktaClientSecret]: AppConnection.Okta
}; };
@@ -167,7 +167,7 @@ export const secretRotationV2QueueServiceFactory = async ({
environment: environment.name, environment: environment.name,
projectName: project.name, projectName: project.name,
rotationUrl: encodeURI( rotationUrl: encodeURI(
`${appCfg.SITE_URL}/projects/${projectId}/secret-manager/secrets/${environment.slug}` `${appCfg.SITE_URL}/projects/secret-management/${projectId}/secrets/${environment.slug}`
) )
} }
}); });
@@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import { Knex } from "knex"; import { Knex } from "knex";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { SecretType, TableName } from "@app/db/schemas"; import { ActionProjectType, SecretType, TableName } from "@app/db/schemas";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -83,6 +83,7 @@ import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secre
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns"; import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns";
import { oktaClientSecretRotationFactory } from "./okta-client-secret/okta-client-secret-rotation-fns";
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal"; import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
export type TSecretRotationV2ServiceFactoryDep = { export type TSecretRotationV2ServiceFactoryDep = {
@@ -128,7 +129,8 @@ const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplem
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation, [SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation, [SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation, [SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation [SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation,
[SecretRotation.OktaClientSecret]: oktaClientSecretRotationFactory as TRotationFactoryImplementation
}; };
export const secretRotationV2ServiceFactory = ({ export const secretRotationV2ServiceFactory = ({
@@ -221,7 +223,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -272,7 +274,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -318,7 +320,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -383,7 +385,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -427,7 +429,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -629,7 +631,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -779,7 +781,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -1111,7 +1113,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -1158,7 +1160,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -1210,7 +1212,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -1326,7 +1328,8 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId, projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const permissiveFolderMappings = folderMappings.filter(({ path, environment }) => const permissiveFolderMappings = folderMappings.filter(({ path, environment }) =>
@@ -46,6 +46,13 @@ import {
TMySqlCredentialsRotationListItem, TMySqlCredentialsRotationListItem,
TMySqlCredentialsRotationWithConnection TMySqlCredentialsRotationWithConnection
} from "./mysql-credentials"; } from "./mysql-credentials";
import {
TOktaClientSecretRotation,
TOktaClientSecretRotationGeneratedCredentials,
TOktaClientSecretRotationInput,
TOktaClientSecretRotationListItem,
TOktaClientSecretRotationWithConnection
} from "./okta-client-secret";
import { import {
TOracleDBCredentialsRotation, TOracleDBCredentialsRotation,
TOracleDBCredentialsRotationInput, TOracleDBCredentialsRotationInput,
@@ -69,7 +76,8 @@ export type TSecretRotationV2 =
| TAuth0ClientSecretRotation | TAuth0ClientSecretRotation
| TAzureClientSecretRotation | TAzureClientSecretRotation
| TLdapPasswordRotation | TLdapPasswordRotation
| TAwsIamUserSecretRotation; | TAwsIamUserSecretRotation
| TOktaClientSecretRotation;
export type TSecretRotationV2WithConnection = export type TSecretRotationV2WithConnection =
| TPostgresCredentialsRotationWithConnection | TPostgresCredentialsRotationWithConnection
@@ -79,14 +87,16 @@ export type TSecretRotationV2WithConnection =
| TAuth0ClientSecretRotationWithConnection | TAuth0ClientSecretRotationWithConnection
| TAzureClientSecretRotationWithConnection | TAzureClientSecretRotationWithConnection
| TLdapPasswordRotationWithConnection | TLdapPasswordRotationWithConnection
| TAwsIamUserSecretRotationWithConnection; | TAwsIamUserSecretRotationWithConnection
| TOktaClientSecretRotationWithConnection;
export type TSecretRotationV2GeneratedCredentials = export type TSecretRotationV2GeneratedCredentials =
| TSqlCredentialsRotationGeneratedCredentials | TSqlCredentialsRotationGeneratedCredentials
| TAuth0ClientSecretRotationGeneratedCredentials | TAuth0ClientSecretRotationGeneratedCredentials
| TAzureClientSecretRotationGeneratedCredentials | TAzureClientSecretRotationGeneratedCredentials
| TLdapPasswordRotationGeneratedCredentials | TLdapPasswordRotationGeneratedCredentials
| TAwsIamUserSecretRotationGeneratedCredentials; | TAwsIamUserSecretRotationGeneratedCredentials
| TOktaClientSecretRotationGeneratedCredentials;
export type TSecretRotationV2Input = export type TSecretRotationV2Input =
| TPostgresCredentialsRotationInput | TPostgresCredentialsRotationInput
@@ -96,7 +106,8 @@ export type TSecretRotationV2Input =
| TAuth0ClientSecretRotationInput | TAuth0ClientSecretRotationInput
| TAzureClientSecretRotationInput | TAzureClientSecretRotationInput
| TLdapPasswordRotationInput | TLdapPasswordRotationInput
| TAwsIamUserSecretRotationInput; | TAwsIamUserSecretRotationInput
| TOktaClientSecretRotationInput;
export type TSecretRotationV2ListItem = export type TSecretRotationV2ListItem =
| TPostgresCredentialsRotationListItem | TPostgresCredentialsRotationListItem
@@ -106,7 +117,8 @@ export type TSecretRotationV2ListItem =
| TAuth0ClientSecretRotationListItem | TAuth0ClientSecretRotationListItem
| TAzureClientSecretRotationListItem | TAzureClientSecretRotationListItem
| TLdapPasswordRotationListItem | TLdapPasswordRotationListItem
| TAwsIamUserSecretRotationListItem; | TAwsIamUserSecretRotationListItem
| TOktaClientSecretRotationListItem;
export type TSecretRotationV2TemporaryParameters = TLdapPasswordRotationInput["temporaryParameters"] | undefined; export type TSecretRotationV2TemporaryParameters = TLdapPasswordRotationInput["temporaryParameters"] | undefined;
@@ -6,6 +6,7 @@ import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotatio
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password"; import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials"; import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { MySqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials"; import { MySqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
import { OktaClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/okta-client-secret";
import { OracleDBCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials"; import { OracleDBCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials"; import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
@@ -17,5 +18,6 @@ export const SecretRotationV2Schema = z.discriminatedUnion("type", [
Auth0ClientSecretRotationSchema, Auth0ClientSecretRotationSchema,
AzureClientSecretRotationSchema, AzureClientSecretRotationSchema,
LdapPasswordRotationSchema, LdapPasswordRotationSchema,
AwsIamUserSecretRotationSchema AwsIamUserSecretRotationSchema,
OktaClientSecretRotationSchema
]); ]);
@@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import Ajv from "ajv"; import Ajv from "ajv";
import { ProjectVersion, TableName } from "@app/db/schemas"; import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
@@ -66,7 +66,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Read, ProjectPermissionSecretRotationActions.Read,
@@ -97,7 +98,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Read, ProjectPermissionSecretRotationActions.Read,
@@ -213,7 +215,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Read, ProjectPermissionSecretRotationActions.Read,
@@ -263,7 +266,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Edit, ProjectPermissionSecretRotationActions.Edit,
@@ -283,7 +287,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId: doc.projectId, projectId: doc.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Delete, ProjectPermissionSecretRotationActions.Delete,
@@ -567,14 +567,18 @@ export const secretScanningV2QueueServiceFactory = async ({
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId); const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
const project = await projectDAL.findById(projectId); const project = await projectDAL.findById(projectId);
const projectAdmins = projectMembers.filter((member) => const recipients = projectMembers.filter((member) => {
member.roles.some((role) => role.role === ProjectMembershipRole.Admin) const isAdmin = member.roles.some((role) => role.role === ProjectMembershipRole.Admin);
); const isCompleted = payload.status === SecretScanningScanStatus.Completed;
// We assume that the committer is one of the project members
const isCommitter = isCompleted && payload.authorEmail === member.user.email;
return isAdmin || isCommitter;
});
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
await smtpService.sendMail({ await smtpService.sendMail({
recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean), recipients: recipients.map((member) => member.user.email!).filter(Boolean),
template: template:
payload.status === SecretScanningScanStatus.Completed payload.status === SecretScanningScanStatus.Completed
? SmtpTemplates.SecretScanningV2SecretsDetected ? SmtpTemplates.SecretScanningV2SecretsDetected
@@ -592,7 +596,7 @@ export const secretScanningV2QueueServiceFactory = async ({
numberOfSecrets: payload.numberOfSecrets, numberOfSecrets: payload.numberOfSecrets,
isDiffScan: payload.isDiffScan, isDiffScan: payload.isDiffScan,
url: encodeURI( url: encodeURI(
`${appCfg.SITE_URL}/projects/${projectId}/secret-scanning/findings?search=scanId:${payload.scanId}` `${appCfg.SITE_URL}/projects/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}`
), ),
timestamp timestamp
} }
@@ -603,7 +607,7 @@ export const secretScanningV2QueueServiceFactory = async ({
timestamp, timestamp,
errorMessage: payload.errorMessage, errorMessage: payload.errorMessage,
url: encodeURI( url: encodeURI(
`${appCfg.SITE_URL}/projects/${projectId}/secret-scanning/data-sources/${dataSource.type}/${dataSource.id}` `${appCfg.SITE_URL}/projects/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}`
) )
} }
}); });
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { join } from "path"; import { join } from "path";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { import {
@@ -94,7 +95,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -156,7 +157,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -201,7 +202,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -235,7 +236,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: payload.projectId projectId: payload.projectId
}); });
@@ -348,7 +349,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -401,6 +402,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -474,7 +476,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -538,7 +540,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -583,7 +585,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -626,7 +628,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -669,7 +671,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -702,7 +704,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -736,7 +738,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -776,7 +778,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: finding.projectId projectId: finding.projectId
}); });
@@ -807,7 +809,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -842,7 +844,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -2,7 +2,7 @@
// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error // akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas"; import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { InternalServerError, NotFoundError } from "@app/lib/errors"; import { InternalServerError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
@@ -103,7 +103,8 @@ export const secretSnapshotServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
@@ -139,7 +140,8 @@ export const secretSnapshotServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
@@ -167,7 +169,8 @@ export const secretSnapshotServiceFactory = ({
actorId, actorId,
projectId: snapshot.projectId, projectId: snapshot.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
@@ -391,7 +394,8 @@ export const secretSnapshotServiceFactory = ({
actorId, actorId,
projectId: snapshot.projectId, projectId: snapshot.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -58,7 +59,8 @@ export const sshCertificateTemplateServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -130,7 +132,8 @@ export const sshCertificateTemplateServiceFactory = ({
actorId, actorId,
projectId: certTemplate.projectId, projectId: certTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -198,7 +201,8 @@ export const sshCertificateTemplateServiceFactory = ({
actorId, actorId,
projectId: certificateTemplate.projectId, projectId: certificateTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -224,7 +228,8 @@ export const sshCertificateTemplateServiceFactory = ({
actorId, actorId,
projectId: certTemplate.projectId, projectId: certTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
@@ -79,7 +80,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.SshHostGroups);
@@ -171,7 +173,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
@@ -267,7 +270,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
@@ -290,7 +294,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.SshHostGroups);
@@ -316,7 +321,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
@@ -354,7 +360,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
@@ -393,7 +400,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
@@ -1,5 +1,6 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission"; import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission";
@@ -62,7 +63,8 @@ export const createSshLoginMappings = async ({
userId: user.id, userId: user.id,
projectId, projectId,
authMethod: actorAuthMethod, authMethod: actorAuthMethod,
userOrgId: actorOrgId userOrgId: actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
} }
@@ -1,5 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@@ -111,7 +112,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
const projectHosts = await sshHostDAL.findUserAccessibleSshHosts([project.id], actorId); const projectHosts = await sshHostDAL.findUserAccessibleSshHosts([project.id], actorId);
@@ -144,7 +146,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -273,7 +276,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -334,7 +338,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -362,7 +367,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -401,7 +407,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
const internalPrincipals = await convertActorToPrincipals({ const internalPrincipals = await convertActorToPrincipals({
@@ -520,7 +527,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
@@ -72,7 +73,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -107,7 +109,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -175,7 +178,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -213,7 +217,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -254,7 +259,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: sshCertificateTemplate.projectId, projectId: sshCertificateTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -375,7 +381,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: sshCertificateTemplate.projectId, projectId: sshCertificateTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -472,7 +479,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -35,7 +36,8 @@ export const trustedIpServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
const trustedIps = await trustedIpDAL.find({ const trustedIps = await trustedIpDAL.find({
@@ -59,7 +61,8 @@ export const trustedIpServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
@@ -104,7 +107,8 @@ export const trustedIpServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
@@ -149,7 +153,8 @@ export const trustedIpServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
+19
View File
@@ -2289,6 +2289,13 @@ export const AppConnections = {
SUPABASE: { SUPABASE: {
accessKey: "The Key used to access Supabase.", accessKey: "The Key used to access Supabase.",
instanceUrl: "The URL used to access Supabase." instanceUrl: "The URL used to access Supabase."
},
DIGITAL_OCEAN_APP_PLATFORM: {
apiToken: "The API token used to authenticate with Digital Ocean App Platform."
},
OKTA: {
instanceUrl: "The URL used to access your Okta organization.",
apiToken: "The API token used to authenticate with Okta."
} }
} }
}; };
@@ -2502,6 +2509,11 @@ export const SecretSyncs = {
SUPABASE: { SUPABASE: {
projectId: "The ID of the Supabase project to sync secrets to.", projectId: "The ID of the Supabase project to sync secrets to.",
projectName: "The name of the Supabase project to sync secrets to." projectName: "The name of the Supabase project to sync secrets to."
},
BITBUCKET: {
workspaceSlug: "The Bitbucket Workspace slug to sync secrets to.",
repositorySlug: "The Bitbucket Repository slug to sync secrets to.",
environmentId: "The Bitbucket Deployment Environment uuid to sync secrets to."
} }
} }
}; };
@@ -2594,6 +2606,9 @@ export const SecretRotations = {
AWS_IAM_USER_SECRET: { AWS_IAM_USER_SECRET: {
userName: "The name of the client to rotate credentials for.", userName: "The name of the client to rotate credentials for.",
region: "The AWS region the client is present in." region: "The AWS region the client is present in."
},
OKTA_CLIENT_SECRET: {
clientId: "The ID of the Okta Application to rotate the client secret for."
} }
}, },
SECRETS_MAPPING: { SECRETS_MAPPING: {
@@ -2616,6 +2631,10 @@ export const SecretRotations = {
AWS_IAM_USER_SECRET: { AWS_IAM_USER_SECRET: {
accessKeyId: "The name of the secret that the access key ID will be mapped to.", accessKeyId: "The name of the secret that the access key ID will be mapped to.",
secretAccessKey: "The name of the secret that the rotated secret access key will be mapped to." secretAccessKey: "The name of the secret that the rotated secret access key will be mapped to."
},
OKTA_CLIENT_SECRET: {
clientId: "The name of the secret that the client ID will be mapped to.",
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
} }
} }
}; };
+27 -2
View File
@@ -64,7 +64,9 @@ export enum QueueName {
FolderTreeCheckpoint = "folder-tree-checkpoint", FolderTreeCheckpoint = "folder-tree-checkpoint",
InvalidateCache = "invalidate-cache", InvalidateCache = "invalidate-cache",
SecretScanningV2 = "secret-scanning-v2", SecretScanningV2 = "secret-scanning-v2",
TelemetryAggregatedEvents = "telemetry-aggregated-events" TelemetryAggregatedEvents = "telemetry-aggregated-events",
DailyReminders = "daily-reminders",
SecretReminderMigration = "secret-reminder-migration"
} }
export enum QueueJobs { export enum QueueJobs {
@@ -104,7 +106,9 @@ export enum QueueJobs {
SecretScanningV2SendNotification = "secret-scanning-v2-notification", SecretScanningV2SendNotification = "secret-scanning-v2-notification",
CaOrderCertificateForSubscriber = "ca-order-certificate-for-subscriber", CaOrderCertificateForSubscriber = "ca-order-certificate-for-subscriber",
PkiSubscriberDailyAutoRenewal = "pki-subscriber-daily-auto-renewal", PkiSubscriberDailyAutoRenewal = "pki-subscriber-daily-auto-renewal",
TelemetryAggregatedEvents = "telemetry-aggregated-events" TelemetryAggregatedEvents = "telemetry-aggregated-events",
DailyReminders = "daily-reminders",
SecretReminderMigration = "secret-reminder-migration"
} }
export type TQueueJobTypes = { export type TQueueJobTypes = {
@@ -291,6 +295,14 @@ export type TQueueJobTypes = {
caType: CaType; caType: CaType;
}; };
}; };
[QueueName.DailyReminders]: {
name: QueueJobs.DailyReminders;
payload: undefined;
};
[QueueName.SecretReminderMigration]: {
name: QueueJobs.SecretReminderMigration;
payload: undefined;
};
[QueueName.PkiSubscriber]: { [QueueName.PkiSubscriber]: {
name: QueueJobs.PkiSubscriberDailyAutoRenewal; name: QueueJobs.PkiSubscriberDailyAutoRenewal;
payload: undefined; payload: undefined;
@@ -390,6 +402,11 @@ export type TQueueServiceFactory = {
startOffset?: number, startOffset?: number,
endOffset?: number endOffset?: number
) => Promise<{ key: string; name: string; id: string | null }[]>; ) => Promise<{ key: string; name: string; id: string | null }[]>;
getDelayedJobs: (
name: QueueName,
startOffset?: number,
endOffset?: number
) => Promise<{ delay: number; timestamp: number; repeatJobKey?: string; data?: unknown }[]>;
}; };
export const queueServiceFactory = ( export const queueServiceFactory = (
@@ -552,6 +569,13 @@ export const queueServiceFactory = (
return q.getRepeatableJobs(startOffset, endOffset); return q.getRepeatableJobs(startOffset, endOffset);
}; };
const getDelayedJobs: TQueueServiceFactory["getDelayedJobs"] = (name, startOffset, endOffset) => {
const q = queueContainer[name];
if (!q) throw new Error(`Queue '${name}' not initialized`);
return q.getDelayed(startOffset, endOffset);
};
const stopRepeatableJobByJobId: TQueueServiceFactory["stopRepeatableJobByJobId"] = async (name, jobId) => { const stopRepeatableJobByJobId: TQueueServiceFactory["stopRepeatableJobByJobId"] = async (name, jobId) => {
const q = queueContainer[name]; const q = queueContainer[name];
const job = await q.getJob(jobId); const job = await q.getJob(jobId);
@@ -598,6 +622,7 @@ export const queueServiceFactory = (
stopJobById, stopJobById,
stopJobByIdPg, stopJobByIdPg,
getRepeatableJobs, getRepeatableJobs,
getDelayedJobs,
startPg, startPg,
queuePg, queuePg,
schedulePg schedulePg
+42 -11
View File
@@ -246,6 +246,10 @@ import { projectMembershipServiceFactory } from "@app/services/project-membershi
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal"; import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { reminderDALFactory } from "@app/services/reminder/reminder-dal";
import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue";
import { reminderServiceFactory } from "@app/services/reminder/reminder-service";
import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretDALFactory } from "@app/services/secret/secret-dal";
@@ -371,6 +375,9 @@ export const registerRoutes = async (
const secretVersionV2BridgeDAL = secretVersionV2BridgeDALFactory(db); const secretVersionV2BridgeDAL = secretVersionV2BridgeDALFactory(db);
const secretVersionTagV2BridgeDAL = secretVersionV2TagBridgeDALFactory(db); const secretVersionTagV2BridgeDAL = secretVersionV2TagBridgeDALFactory(db);
const reminderDAL = reminderDALFactory(db);
const reminderRecipientDAL = reminderRecipientDALFactory(db);
const integrationDAL = integrationDALFactory(db); const integrationDAL = integrationDALFactory(db);
const integrationAuthDAL = integrationAuthDALFactory(db); const integrationAuthDAL = integrationAuthDALFactory(db);
const webhookDAL = webhookDALFactory(db); const webhookDAL = webhookDALFactory(db);
@@ -734,9 +741,17 @@ export const registerRoutes = async (
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL }); const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
const reminderService = reminderServiceFactory({
reminderDAL,
reminderRecipientDAL,
smtpService,
projectMembershipDAL,
permissionService,
secretV2BridgeDAL
});
const orgService = orgServiceFactory({ const orgService = orgServiceFactory({
userAliasDAL, userAliasDAL,
queueService,
identityMetadataDAL, identityMetadataDAL,
secretDAL, secretDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
@@ -762,7 +777,8 @@ export const registerRoutes = async (
orgBotDAL, orgBotDAL,
oidcConfigDAL, oidcConfigDAL,
loginService, loginService,
projectBotService projectBotService,
reminderService
}); });
const signupService = authSignupServiceFactory({ const signupService = authSignupServiceFactory({
tokenService, tokenService,
@@ -1060,7 +1076,6 @@ export const registerRoutes = async (
secretImportDAL, secretImportDAL,
projectEnvDAL, projectEnvDAL,
webhookDAL, webhookDAL,
orgDAL,
auditLogService, auditLogService,
userDAL, userDAL,
projectMembershipDAL, projectMembershipDAL,
@@ -1082,11 +1097,11 @@ export const registerRoutes = async (
secretApprovalRequestDAL, secretApprovalRequestDAL,
projectKeyDAL, projectKeyDAL,
projectUserMembershipRoleDAL, projectUserMembershipRoleDAL,
secretReminderRecipientsDAL,
orgService, orgService,
resourceMetadataDAL, resourceMetadataDAL,
folderCommitService, folderCommitService,
secretSyncQueue secretSyncQueue,
reminderService
}); });
const projectService = projectServiceFactory({ const projectService = projectServiceFactory({
@@ -1095,7 +1110,6 @@ export const registerRoutes = async (
projectSshConfigDAL, projectSshConfigDAL,
secretDAL, secretDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
queueService,
projectQueue: projectQueueService, projectQueue: projectQueueService,
projectBotService, projectBotService,
identityProjectDAL, identityProjectDAL,
@@ -1132,7 +1146,8 @@ export const registerRoutes = async (
microsoftTeamsIntegrationDAL, microsoftTeamsIntegrationDAL,
projectTemplateService, projectTemplateService,
groupProjectDAL, groupProjectDAL,
smtpService smtpService,
reminderService
}); });
const projectEnvService = projectEnvServiceFactory({ const projectEnvService = projectEnvServiceFactory({
@@ -1231,6 +1246,7 @@ export const registerRoutes = async (
kmsService, kmsService,
snapshotService, snapshotService,
resourceMetadataDAL, resourceMetadataDAL,
reminderService,
keyStore keyStore
}); });
@@ -1284,7 +1300,8 @@ export const registerRoutes = async (
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
secretV2BridgeService, secretV2BridgeService,
secretApprovalRequestService, secretApprovalRequestService,
licenseService licenseService,
reminderService
}); });
const secretSharingService = secretSharingServiceFactory({ const secretSharingService = secretSharingServiceFactory({
@@ -1538,7 +1555,12 @@ export const registerRoutes = async (
folderService, folderService,
permissionService, permissionService,
folderDAL, folderDAL,
projectEnvDAL projectEnvDAL,
secretApprovalRequestService,
secretApprovalPolicyService,
projectDAL,
secretV2BridgeService,
folderCommitDAL
}); });
const identityOidcAuthService = identityOidcAuthServiceFactory({ const identityOidcAuthService = identityOidcAuthServiceFactory({
@@ -1611,7 +1633,6 @@ export const registerRoutes = async (
auditLogDAL, auditLogDAL,
queueService, queueService,
secretVersionDAL, secretVersionDAL,
secretDAL,
secretFolderVersionDAL: folderVersionDAL, secretFolderVersionDAL: folderVersionDAL,
snapshotDAL, snapshotDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -1622,6 +1643,13 @@ export const registerRoutes = async (
orgService orgService
}); });
const dailyReminderQueueService = dailyReminderQueueServiceFactory({
reminderService,
queueService,
secretDAL: secretV2BridgeDAL,
secretReminderRecipientsDAL
});
const dailyExpiringPkiItemAlert = dailyExpiringPkiItemAlertQueueServiceFactory({ const dailyExpiringPkiItemAlert = dailyExpiringPkiItemAlertQueueServiceFactory({
queueService, queueService,
pkiAlertService pkiAlertService
@@ -1921,6 +1949,8 @@ export const registerRoutes = async (
await telemetryQueue.startTelemetryCheck(); await telemetryQueue.startTelemetryCheck();
await telemetryQueue.startAggregatedEventsJob(); await telemetryQueue.startAggregatedEventsJob();
await dailyResourceCleanUp.startCleanUp(); await dailyResourceCleanUp.startCleanUp();
await dailyReminderQueueService.startDailyRemindersJob();
await dailyReminderQueueService.startSecretReminderMigrationJob();
await dailyExpiringPkiItemAlert.startSendingAlerts(); await dailyExpiringPkiItemAlert.startSendingAlerts();
await pkiSubscriberQueue.startDailyAutoRenewalJob(); await pkiSubscriberQueue.startDailyAutoRenewalJob();
await kmsService.startService(); await kmsService.startService();
@@ -2031,7 +2061,8 @@ export const registerRoutes = async (
assumePrivileges: assumePrivilegeService, assumePrivileges: assumePrivilegeService,
githubOrgSync: githubOrgSyncConfigService, githubOrgSync: githubOrgSyncConfigService,
folderCommit: folderCommitService, folderCommit: folderCommitService,
secretScanningV2: secretScanningV2Service secretScanningV2: secretScanningV2Service,
reminder: reminderService
}); });
const cronJobs: CronJob[] = []; const cronJobs: CronJob[] = [];
@@ -51,6 +51,10 @@ import {
DatabricksConnectionListItemSchema, DatabricksConnectionListItemSchema,
SanitizedDatabricksConnectionSchema SanitizedDatabricksConnectionSchema
} from "@app/services/app-connection/databricks"; } from "@app/services/app-connection/databricks";
import {
DigitalOceanConnectionListItemSchema,
SanitizedDigitalOceanConnectionSchema
} from "@app/services/app-connection/digital-ocean";
import { FlyioConnectionListItemSchema, SanitizedFlyioConnectionSchema } from "@app/services/app-connection/flyio"; import { FlyioConnectionListItemSchema, SanitizedFlyioConnectionSchema } from "@app/services/app-connection/flyio";
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp"; import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github"; import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
@@ -71,6 +75,7 @@ import {
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap"; import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql"; import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql"; import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql";
import { OktaConnectionListItemSchema, SanitizedOktaConnectionSchema } from "@app/services/app-connection/okta";
import { import {
PostgresConnectionListItemSchema, PostgresConnectionListItemSchema,
SanitizedPostgresConnectionSchema SanitizedPostgresConnectionSchema
@@ -138,7 +143,9 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedZabbixConnectionSchema.options, ...SanitizedZabbixConnectionSchema.options,
...SanitizedRailwayConnectionSchema.options, ...SanitizedRailwayConnectionSchema.options,
...SanitizedChecklyConnectionSchema.options, ...SanitizedChecklyConnectionSchema.options,
...SanitizedSupabaseConnectionSchema.options ...SanitizedSupabaseConnectionSchema.options,
...SanitizedDigitalOceanConnectionSchema.options,
...SanitizedOktaConnectionSchema.options
]); ]);
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
@@ -175,7 +182,9 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
ZabbixConnectionListItemSchema, ZabbixConnectionListItemSchema,
RailwayConnectionListItemSchema, RailwayConnectionListItemSchema,
ChecklyConnectionListItemSchema, ChecklyConnectionListItemSchema,
SupabaseConnectionListItemSchema SupabaseConnectionListItemSchema,
DigitalOceanConnectionListItemSchema,
OktaConnectionListItemSchema
]); ]);
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
@@ -85,4 +85,40 @@ export const registerBitbucketConnectionRouter = async (server: FastifyZodProvid
return { repositories }; return { repositories };
} }
}); });
server.route({
method: "GET",
url: `/:connectionId/environments`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
querystring: z.object({
workspaceSlug: z.string().min(1).max(255),
repositorySlug: z.string().min(1).max(255)
}),
response: {
200: z.object({
environments: z.object({ slug: z.string(), name: z.string(), uuid: z.string() }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const {
params: { connectionId },
query: { workspaceSlug, repositorySlug }
} = req;
const environments = await server.services.appConnection.bitbucket.listEnvironments(
{ connectionId, workspaceSlug, repositorySlug },
req.permission
);
return { environments };
}
});
}; };
@@ -0,0 +1,57 @@
import { z } from "zod";
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 {
CreateDigitalOceanConnectionSchema,
SanitizedDigitalOceanConnectionSchema,
UpdateDigitalOceanConnectionSchema
} from "@app/services/app-connection/digital-ocean";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerDigitalOceanConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.DigitalOcean,
server,
createSchema: CreateDigitalOceanConnectionSchema,
updateSchema: UpdateDigitalOceanConnectionSchema,
sanitizedResponseSchema: SanitizedDigitalOceanConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/apps`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z.object({
apps: z
.object({
id: z.string(),
spec: z.object({
name: z.string()
})
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const apps = await server.services.appConnection.digitalOcean.listApps(connectionId, req.permission);
return { apps };
}
});
};
@@ -14,6 +14,7 @@ import { registerCamundaConnectionRouter } from "./camunda-connection-router";
import { registerChecklyConnectionRouter } from "./checkly-connection-router"; import { registerChecklyConnectionRouter } from "./checkly-connection-router";
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router"; import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router"; import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerDigitalOceanConnectionRouter } from "./digital-ocean-connection-router";
import { registerFlyioConnectionRouter } from "./flyio-connection-router"; import { registerFlyioConnectionRouter } from "./flyio-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router"; import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router"; import { registerGitHubConnectionRouter } from "./github-connection-router";
@@ -25,6 +26,7 @@ import { registerHumanitecConnectionRouter } from "./humanitec-connection-router
import { registerLdapConnectionRouter } from "./ldap-connection-router"; import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router"; import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerMySqlConnectionRouter } from "./mysql-connection-router"; import { registerMySqlConnectionRouter } from "./mysql-connection-router";
import { registerOktaConnectionRouter } from "./okta-connection-router";
import { registerPostgresConnectionRouter } from "./postgres-connection-router"; import { registerPostgresConnectionRouter } from "./postgres-connection-router";
import { registerRailwayConnectionRouter } from "./railway-connection-router"; import { registerRailwayConnectionRouter } from "./railway-connection-router";
import { registerRenderConnectionRouter } from "./render-connection-router"; import { registerRenderConnectionRouter } from "./render-connection-router";
@@ -72,5 +74,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Zabbix]: registerZabbixConnectionRouter, [AppConnection.Zabbix]: registerZabbixConnectionRouter,
[AppConnection.Railway]: registerRailwayConnectionRouter, [AppConnection.Railway]: registerRailwayConnectionRouter,
[AppConnection.Checkly]: registerChecklyConnectionRouter, [AppConnection.Checkly]: registerChecklyConnectionRouter,
[AppConnection.Supabase]: registerSupabaseConnectionRouter [AppConnection.Supabase]: registerSupabaseConnectionRouter,
[AppConnection.DigitalOcean]: registerDigitalOceanConnectionRouter,
[AppConnection.Okta]: registerOktaConnectionRouter
}; };
@@ -0,0 +1,52 @@
import { z } from "zod";
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 {
CreateOktaConnectionSchema,
SanitizedOktaConnectionSchema,
UpdateOktaConnectionSchema
} from "@app/services/app-connection/okta";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerOktaConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Okta,
server,
sanitizedResponseSchema: SanitizedOktaConnectionSchema,
createSchema: CreateOktaConnectionSchema,
updateSchema: UpdateOktaConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/apps`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z.object({
apps: z.object({ id: z.string(), label: z.string() }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const {
params: { connectionId }
} = req;
const apps = await server.services.appConnection.okta.listApps(connectionId, req.permission);
return { apps };
}
});
};
@@ -904,7 +904,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
projectId, projectId,
path: secretPath, path: secretPath,
search, search,
tagSlugs: tags tagSlugs: tags,
includeTagsInSearch: true,
includeMetadataInSearch: true
}); });
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) { if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
@@ -924,7 +926,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
search, search,
limit: remainingLimit, limit: remainingLimit,
offset: adjustedOffset, offset: adjustedOffset,
tagSlugs: tags tagSlugs: tags,
includeTagsInSearch: true,
includeMetadataInSearch: true
}) })
).secrets; ).secrets;
} }
@@ -1097,7 +1101,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
filters: { filters: {
...sharedFilters, ...sharedFilters,
tagSlugs: tags, tagSlugs: tags,
includeTagsInSearch: true includeTagsInSearch: true,
includeMetadataInSearch: true
} }
}, },
req.permission req.permission
+11
View File
@@ -42,6 +42,7 @@ import { registerProjectEnvRouter } from "./project-env-router";
import { registerProjectKeyRouter } from "./project-key-router"; import { registerProjectKeyRouter } from "./project-key-router";
import { registerProjectMembershipRouter } from "./project-membership-router"; import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router"; import { registerProjectRouter } from "./project-router";
import { SECRET_REMINDER_REGISTER_ROUTER_MAP } from "./reminder-routers";
import { registerSecretFolderRouter } from "./secret-folder-router"; import { registerSecretFolderRouter } from "./secret-folder-router";
import { registerSecretImportRouter } from "./secret-import-router"; import { registerSecretImportRouter } from "./secret-import-router";
import { registerSecretRequestsRouter } from "./secret-requests-router"; import { registerSecretRequestsRouter } from "./secret-requests-router";
@@ -172,4 +173,14 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
}, },
{ prefix: "/secret-syncs" } { prefix: "/secret-syncs" }
); );
await server.register(
async (reminderRouter) => {
// register service specific reminder endpoints (reminders/secret)
for await (const [reminderType, router] of Object.entries(SECRET_REMINDER_REGISTER_ROUTER_MAP)) {
await reminderRouter.register(router, { prefix: `/${reminderType}` });
}
},
{ prefix: "/reminders" }
);
}; };
@@ -158,7 +158,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
includeRoles: z includeRoles: z
.enum(["true", "false"]) .enum(["true", "false"])
.default("false") .default("false")
.transform((value) => value === "true") .transform((value) => value === "true"),
type: z.nativeEnum(ProjectType).optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -177,7 +178,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId actorOrgId: req.permission.orgId,
type: req.query.type
}); });
return { workspaces }; return { workspaces };
} }
@@ -1050,6 +1052,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
body: z.object({ body: z.object({
limit: z.number().default(100), limit: z.number().default(100),
offset: z.number().default(0), offset: z.number().default(0),
type: z.nativeEnum(ProjectType).optional(),
orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME), orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME),
orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC), orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC),
name: z name: z
@@ -0,0 +1,8 @@
import { ReminderType } from "@app/services/reminder/reminder-enums";
import { registerSecretReminderRouter } from "./secret-reminder-router";
export const SECRET_REMINDER_REGISTER_ROUTER_MAP: Record<ReminderType, (server: FastifyZodProvider) => Promise<void>> =
{
[ReminderType.SECRETS]: registerSecretReminderRouter
};
@@ -0,0 +1,154 @@
import { z } from "zod";
import { RemindersSchema } from "@app/db/schemas/reminders";
import { EventType } from "@app/ee/services/audit-log/audit-log-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";
export const registerSecretReminderRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:secretId",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
secretId: z.string().uuid()
}),
body: z
.object({
message: z.string().trim().max(1024).optional(),
repeatDays: z.number().min(1).nullable().optional(),
nextReminderDate: z.string().datetime().nullable().optional(),
recipients: z.string().array().optional()
})
.refine((data) => {
return data.repeatDays || data.nextReminderDate;
}, "At least one of repeatDays or nextReminderDate is required"),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.reminder.createReminder({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
reminder: {
secretId: req.params.secretId,
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
recipients: req.body.recipients
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.CREATE_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId,
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
recipients: req.body.recipients
}
}
});
return { message: "Successfully created reminder" };
}
});
server.route({
url: "/:secretId",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
secretId: z.string().uuid()
}),
response: {
200: z.object({
reminder: RemindersSchema.extend({
recipients: z.string().array().optional()
})
.optional()
.nullable()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const reminder = await server.services.reminder.getReminder({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
secretId: req.params.secretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.GET_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId
}
}
});
return { reminder };
}
});
server.route({
url: "/:secretId",
method: "DELETE",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
secretId: z.string().uuid()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.reminder.deleteReminder({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
secretId: req.params.secretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.DELETE_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId
}
}
});
return { message: "Successfully deleted reminder" };
}
});
};
@@ -0,0 +1,17 @@
import {
BitbucketSyncSchema,
CreateBitbucketSyncSchema,
UpdateBitbucketSyncSchema
} from "@app/services/secret-sync/bitbucket";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerBitbucketSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.Bitbucket,
server,
responseSchema: BitbucketSyncSchema,
createSchema: CreateBitbucketSyncSchema,
updateSchema: UpdateBitbucketSyncSchema
});
@@ -0,0 +1,17 @@
import {
CreateDigitalOceanAppPlatformSyncSchema,
DigitalOceanAppPlatformSyncSchema,
UpdateDigitalOceanAppPlatformSyncSchema
} from "@app/services/secret-sync/digital-ocean-app-platform";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerDigitalOceanAppPlatformSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.DigitalOceanAppPlatform,
server,
responseSchema: DigitalOceanAppPlatformSyncSchema,
createSchema: CreateDigitalOceanAppPlatformSyncSchema,
updateSchema: UpdateDigitalOceanAppPlatformSyncSchema
});
@@ -7,11 +7,13 @@ import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-
import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router"; import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router";
import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router"; import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router";
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router"; import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
import { registerBitbucketSyncRouter } from "./bitbucket-sync-router";
import { registerCamundaSyncRouter } from "./camunda-sync-router"; import { registerCamundaSyncRouter } from "./camunda-sync-router";
import { registerChecklySyncRouter } from "./checkly-sync-router"; import { registerChecklySyncRouter } from "./checkly-sync-router";
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router"; import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router"; import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
import { registerDatabricksSyncRouter } from "./databricks-sync-router"; import { registerDatabricksSyncRouter } from "./databricks-sync-router";
import { registerDigitalOceanAppPlatformSyncRouter } from "./digital-ocean-app-platform-sync-router";
import { registerFlyioSyncRouter } from "./flyio-sync-router"; import { registerFlyioSyncRouter } from "./flyio-sync-router";
import { registerGcpSyncRouter } from "./gcp-sync-router"; import { registerGcpSyncRouter } from "./gcp-sync-router";
import { registerGitHubSyncRouter } from "./github-sync-router"; import { registerGitHubSyncRouter } from "./github-sync-router";
@@ -57,5 +59,7 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.Supabase]: registerSupabaseSyncRouter, [SecretSync.Supabase]: registerSupabaseSyncRouter,
[SecretSync.Zabbix]: registerZabbixSyncRouter, [SecretSync.Zabbix]: registerZabbixSyncRouter,
[SecretSync.Railway]: registerRailwaySyncRouter, [SecretSync.Railway]: registerRailwaySyncRouter,
[SecretSync.Checkly]: registerChecklySyncRouter [SecretSync.Checkly]: registerChecklySyncRouter,
[SecretSync.DigitalOceanAppPlatform]: registerDigitalOceanAppPlatformSyncRouter,
[SecretSync.Bitbucket]: registerBitbucketSyncRouter
}; };
@@ -21,6 +21,7 @@ import {
} from "@app/services/secret-sync/azure-app-configuration"; } from "@app/services/secret-sync/azure-app-configuration";
import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops"; import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops";
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault"; import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
import { BitbucketSyncListItemSchema, BitbucketSyncSchema } from "@app/services/secret-sync/bitbucket";
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda"; import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas"; import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas";
import { import {
@@ -32,6 +33,10 @@ import {
CloudflareWorkersSyncSchema CloudflareWorkersSyncSchema
} from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas"; } from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas";
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks"; import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
import {
DigitalOceanAppPlatformSyncListItemSchema,
DigitalOceanAppPlatformSyncSchema
} from "@app/services/secret-sync/digital-ocean-app-platform";
import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-sync/flyio"; import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-sync/flyio";
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp"; import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github"; import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
@@ -75,7 +80,9 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
SupabaseSyncSchema, SupabaseSyncSchema,
ZabbixSyncSchema, ZabbixSyncSchema,
RailwaySyncSchema, RailwaySyncSchema,
ChecklySyncSchema ChecklySyncSchema,
DigitalOceanAppPlatformSyncSchema,
BitbucketSyncSchema
]); ]);
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
@@ -102,11 +109,12 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
GitLabSyncListItemSchema, GitLabSyncListItemSchema,
CloudflarePagesSyncListItemSchema, CloudflarePagesSyncListItemSchema,
CloudflareWorkersSyncListItemSchema, CloudflareWorkersSyncListItemSchema,
DigitalOceanAppPlatformSyncListItemSchema,
ZabbixSyncListItemSchema, ZabbixSyncListItemSchema,
RailwaySyncListItemSchema, RailwaySyncListItemSchema,
ChecklySyncListItemSchema, ChecklySyncListItemSchema,
SupabaseSyncListItemSchema SupabaseSyncListItemSchema,
BitbucketSyncListItemSchema
]); ]);
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => { export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
@@ -32,7 +32,9 @@ export enum AppConnection {
Railway = "railway", Railway = "railway",
Bitbucket = "bitbucket", Bitbucket = "bitbucket",
Checkly = "checkly", Checkly = "checkly",
Supabase = "supabase" Supabase = "supabase",
DigitalOcean = "digital-ocean",
Okta = "okta"
} }
export enum AWSRegion { export enum AWSRegion {
@@ -68,6 +68,11 @@ import {
getDatabricksConnectionListItem, getDatabricksConnectionListItem,
validateDatabricksConnectionCredentials validateDatabricksConnectionCredentials
} from "./databricks"; } from "./databricks";
import {
DigitalOceanConnectionMethod,
getDigitalOceanConnectionListItem,
validateDigitalOceanConnectionCredentials
} from "./digital-ocean";
import { FlyioConnectionMethod, getFlyioConnectionListItem, validateFlyioConnectionCredentials } from "./flyio"; import { FlyioConnectionMethod, getFlyioConnectionListItem, validateFlyioConnectionCredentials } from "./flyio";
import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp"; import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp";
import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github"; import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github";
@@ -92,6 +97,7 @@ import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnection
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql"; import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums"; import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums";
import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns"; import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns";
import { getOktaConnectionListItem, OktaConnectionMethod, validateOktaConnectionCredentials } from "./okta";
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres"; import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
import { getRailwayConnectionListItem, validateRailwayConnectionCredentials } from "./railway"; import { getRailwayConnectionListItem, validateRailwayConnectionCredentials } from "./railway";
import { RenderConnectionMethod } from "./render/render-connection-enums"; import { RenderConnectionMethod } from "./render/render-connection-enums";
@@ -155,7 +161,9 @@ export const listAppConnectionOptions = () => {
getRailwayConnectionListItem(), getRailwayConnectionListItem(),
getBitbucketConnectionListItem(), getBitbucketConnectionListItem(),
getChecklyConnectionListItem(), getChecklyConnectionListItem(),
getSupabaseConnectionListItem() getSupabaseConnectionListItem(),
getDigitalOceanConnectionListItem(),
getOktaConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name)); ].sort((a, b) => a.name.localeCompare(b.name));
}; };
@@ -241,7 +249,9 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator [AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.DigitalOcean]: validateDigitalOceanConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator
}; };
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection, gatewayService); return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection, gatewayService);
@@ -280,6 +290,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case CloudflareConnectionMethod.APIToken: case CloudflareConnectionMethod.APIToken:
case BitbucketConnectionMethod.ApiToken: case BitbucketConnectionMethod.ApiToken:
case ZabbixConnectionMethod.ApiToken: case ZabbixConnectionMethod.ApiToken:
case DigitalOceanConnectionMethod.ApiToken:
case OktaConnectionMethod.ApiToken:
return "API Token"; return "API Token";
case PostgresConnectionMethod.UsernameAndPassword: case PostgresConnectionMethod.UsernameAndPassword:
case MsSqlConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword:
@@ -367,7 +379,9 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Railway]: platformManagedCredentialsNotSupported, [AppConnection.Railway]: platformManagedCredentialsNotSupported,
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported, [AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
[AppConnection.Checkly]: platformManagedCredentialsNotSupported, [AppConnection.Checkly]: platformManagedCredentialsNotSupported,
[AppConnection.Supabase]: platformManagedCredentialsNotSupported [AppConnection.Supabase]: platformManagedCredentialsNotSupported,
[AppConnection.DigitalOcean]: platformManagedCredentialsNotSupported,
[AppConnection.Okta]: platformManagedCredentialsNotSupported
}; };
export const enterpriseAppCheck = async ( export const enterpriseAppCheck = async (
@@ -34,7 +34,9 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Railway]: "Railway", [AppConnection.Railway]: "Railway",
[AppConnection.Bitbucket]: "Bitbucket", [AppConnection.Bitbucket]: "Bitbucket",
[AppConnection.Checkly]: "Checkly", [AppConnection.Checkly]: "Checkly",
[AppConnection.Supabase]: "Supabase" [AppConnection.Supabase]: "Supabase",
[AppConnection.DigitalOcean]: "DigitalOcean App Platform",
[AppConnection.Okta]: "Okta"
}; };
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = { export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
@@ -71,5 +73,7 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.Railway]: AppConnectionPlanType.Regular, [AppConnection.Railway]: AppConnectionPlanType.Regular,
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular, [AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
[AppConnection.Checkly]: AppConnectionPlanType.Regular, [AppConnection.Checkly]: AppConnectionPlanType.Regular,
[AppConnection.Supabase]: AppConnectionPlanType.Regular [AppConnection.Supabase]: AppConnectionPlanType.Regular,
[AppConnection.DigitalOcean]: AppConnectionPlanType.Regular,
[AppConnection.Okta]: AppConnectionPlanType.Regular
}; };
@@ -61,6 +61,8 @@ import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/clou
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service"; import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks"; import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
import { databricksConnectionService } from "./databricks/databricks-connection-service"; import { databricksConnectionService } from "./databricks/databricks-connection-service";
import { ValidateDigitalOceanConnectionCredentialsSchema } from "./digital-ocean";
import { digitalOceanAppPlatformConnectionService } from "./digital-ocean/digital-ocean-connection-service";
import { ValidateFlyioConnectionCredentialsSchema } from "./flyio"; import { ValidateFlyioConnectionCredentialsSchema } from "./flyio";
import { flyioConnectionService } from "./flyio/flyio-connection-service"; import { flyioConnectionService } from "./flyio/flyio-connection-service";
import { ValidateGcpConnectionCredentialsSchema } from "./gcp"; import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
@@ -79,6 +81,8 @@ import { humanitecConnectionService } from "./humanitec/humanitec-connection-ser
import { ValidateLdapConnectionCredentialsSchema } from "./ldap"; import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { ValidateMySqlConnectionCredentialsSchema } from "./mysql"; import { ValidateMySqlConnectionCredentialsSchema } from "./mysql";
import { ValidateOktaConnectionCredentialsSchema } from "./okta";
import { oktaConnectionService } from "./okta/okta-connection-service";
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres"; import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
import { ValidateRailwayConnectionCredentialsSchema } from "./railway"; import { ValidateRailwayConnectionCredentialsSchema } from "./railway";
import { railwayConnectionService } from "./railway/railway-connection-service"; import { railwayConnectionService } from "./railway/railway-connection-service";
@@ -142,7 +146,9 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema, [AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema,
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema, [AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema, [AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema,
[AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema [AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema,
[AppConnection.DigitalOcean]: ValidateDigitalOceanConnectionCredentialsSchema,
[AppConnection.Okta]: ValidateOktaConnectionCredentialsSchema
}; };
export const appConnectionServiceFactory = ({ export const appConnectionServiceFactory = ({
@@ -603,6 +609,8 @@ export const appConnectionServiceFactory = ({
railway: railwayConnectionService(connectAppConnectionById), railway: railwayConnectionService(connectAppConnectionById),
bitbucket: bitbucketConnectionService(connectAppConnectionById), bitbucket: bitbucketConnectionService(connectAppConnectionById),
checkly: checklyConnectionService(connectAppConnectionById), checkly: checklyConnectionService(connectAppConnectionById),
supabase: supabaseConnectionService(connectAppConnectionById) supabase: supabaseConnectionService(connectAppConnectionById),
digitalOcean: digitalOceanAppPlatformConnectionService(connectAppConnectionById),
okta: oktaConnectionService(connectAppConnectionById)
}; };
}; };
@@ -87,6 +87,12 @@ import {
TDatabricksConnectionInput, TDatabricksConnectionInput,
TValidateDatabricksConnectionCredentialsSchema TValidateDatabricksConnectionCredentialsSchema
} from "./databricks"; } from "./databricks";
import {
TDigitalOceanConnection,
TDigitalOceanConnectionConfig,
TDigitalOceanConnectionInput,
TValidateDigitalOceanCredentialsSchema
} from "./digital-ocean";
import { import {
TFlyioConnection, TFlyioConnection,
TFlyioConnectionConfig, TFlyioConnectionConfig,
@@ -143,6 +149,12 @@ import {
} from "./ldap"; } from "./ldap";
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { TMySqlConnection, TMySqlConnectionInput, TValidateMySqlConnectionCredentialsSchema } from "./mysql"; import { TMySqlConnection, TMySqlConnectionInput, TValidateMySqlConnectionCredentialsSchema } from "./mysql";
import {
TOktaConnection,
TOktaConnectionConfig,
TOktaConnectionInput,
TValidateOktaConnectionCredentialsSchema
} from "./okta";
import { import {
TPostgresConnection, TPostgresConnection,
TPostgresConnectionInput, TPostgresConnectionInput,
@@ -232,6 +244,8 @@ export type TAppConnection = { id: string } & (
| TRailwayConnection | TRailwayConnection
| TChecklyConnection | TChecklyConnection
| TSupabaseConnection | TSupabaseConnection
| TDigitalOceanConnection
| TOktaConnection
); );
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>; export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
@@ -273,6 +287,8 @@ export type TAppConnectionInput = { id: string } & (
| TRailwayConnectionInput | TRailwayConnectionInput
| TChecklyConnectionInput | TChecklyConnectionInput
| TSupabaseConnectionInput | TSupabaseConnectionInput
| TDigitalOceanConnectionInput
| TOktaConnectionInput
); );
export type TSqlConnectionInput = export type TSqlConnectionInput =
@@ -321,7 +337,9 @@ export type TAppConnectionConfig =
| TZabbixConnectionConfig | TZabbixConnectionConfig
| TRailwayConnectionConfig | TRailwayConnectionConfig
| TChecklyConnectionConfig | TChecklyConnectionConfig
| TSupabaseConnectionConfig; | TSupabaseConnectionConfig
| TDigitalOceanConnectionConfig
| TOktaConnectionConfig;
export type TValidateAppConnectionCredentialsSchema = export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema | TValidateAwsConnectionCredentialsSchema
@@ -357,7 +375,9 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateZabbixConnectionCredentialsSchema | TValidateZabbixConnectionCredentialsSchema
| TValidateRailwayConnectionCredentialsSchema | TValidateRailwayConnectionCredentialsSchema
| TValidateChecklyConnectionCredentialsSchema | TValidateChecklyConnectionCredentialsSchema
| TValidateSupabaseConnectionCredentialsSchema; | TValidateSupabaseConnectionCredentialsSchema
| TValidateDigitalOceanCredentialsSchema
| TValidateOktaConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = { export type TListAwsConnectionKmsKeys = {
connectionId: string; connectionId: string;
@@ -9,6 +9,7 @@ import { BitbucketConnectionMethod } from "./bitbucket-connection-enums";
import { import {
TBitbucketConnection, TBitbucketConnection,
TBitbucketConnectionConfig, TBitbucketConnectionConfig,
TBitbucketEnvironment,
TBitbucketRepo, TBitbucketRepo,
TBitbucketWorkspace TBitbucketWorkspace
} from "./bitbucket-connection-types"; } from "./bitbucket-connection-types";
@@ -21,11 +22,15 @@ export const getBitbucketConnectionListItem = () => {
}; };
}; };
export const createAuthHeader = (email: string, apiToken: string): string => {
return `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
};
export const getBitbucketUser = async ({ email, apiToken }: { email: string; apiToken: string }) => { export const getBitbucketUser = async ({ email, apiToken }: { email: string; apiToken: string }) => {
try { try {
const { data } = await request.get<{ username: string }>(`${IntegrationUrls.BITBUCKET_API_URL}/2.0/user`, { const { data } = await request.get<{ username: string }>(`${IntegrationUrls.BITBUCKET_API_URL}/2.0/user`, {
headers: { headers: {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Authorization: createAuthHeader(email, apiToken),
Accept: "application/json" Accept: "application/json"
} }
}); });
@@ -57,7 +62,7 @@ export const listBitbucketWorkspaces = async (appConnection: TBitbucketConnectio
const { email, apiToken } = appConnection.credentials; const { email, apiToken } = appConnection.credentials;
const headers = { const headers = {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Authorization: createAuthHeader(email, apiToken),
Accept: "application/json" Accept: "application/json"
}; };
@@ -89,7 +94,7 @@ export const listBitbucketRepositories = async (appConnection: TBitbucketConnect
const { email, apiToken } = appConnection.credentials; const { email, apiToken } = appConnection.credentials;
const headers = { const headers = {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Authorization: createAuthHeader(email, apiToken),
Accept: "application/json" Accept: "application/json"
}; };
@@ -115,3 +120,43 @@ export const listBitbucketRepositories = async (appConnection: TBitbucketConnect
return allRepos; return allRepos;
}; };
export const listBitbucketEnvironments = async (
appConnection: TBitbucketConnection,
workspaceSlug: string,
repositorySlug: string
) => {
const { email, apiToken } = appConnection.credentials;
const headers = {
Authorization: createAuthHeader(email, apiToken),
Accept: "application/json"
};
const environments: TBitbucketEnvironment[] = [];
let hasNextPage = true;
let environmentsUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspaceSlug)}/${encodeURIComponent(repositorySlug)}/environments?pagelen=100`;
let iterationCount = 0;
// Limit to 10 iterations, fetching at most 10 * 100 = 1000 environments
while (hasNextPage && iterationCount < 10) {
// eslint-disable-next-line no-await-in-loop
const { data }: { data: { values: TBitbucketEnvironment[]; next: string } } = await request.get(environmentsUrl, {
headers
});
if (data?.values.length > 0) {
environments.push(...data.values);
}
if (data.next) {
environmentsUrl = data.next;
} else {
hasNextPage = false;
}
iterationCount += 1;
}
return environments;
};
@@ -1,8 +1,16 @@
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../app-connection-enums";
import { listBitbucketRepositories, listBitbucketWorkspaces } from "./bitbucket-connection-fns"; import {
import { TBitbucketConnection, TGetBitbucketRepositoriesDTO } from "./bitbucket-connection-types"; listBitbucketEnvironments,
listBitbucketRepositories,
listBitbucketWorkspaces
} from "./bitbucket-connection-fns";
import {
TBitbucketConnection,
TGetBitbucketEnvironmentsDTO,
TGetBitbucketRepositoriesDTO
} from "./bitbucket-connection-types";
type TGetAppConnectionFunc = ( type TGetAppConnectionFunc = (
app: AppConnection, app: AppConnection,
@@ -26,8 +34,18 @@ export const bitbucketConnectionService = (getAppConnection: TGetAppConnectionFu
return repositories; return repositories;
}; };
const listEnvironments = async (
{ connectionId, workspaceSlug, repositorySlug }: TGetBitbucketEnvironmentsDTO,
actor: OrgServiceActor
) => {
const appConnection = await getAppConnection(AppConnection.Bitbucket, connectionId, actor);
const environments = await listBitbucketEnvironments(appConnection, workspaceSlug, repositorySlug);
return environments;
};
return { return {
listWorkspaces, listWorkspaces,
listRepositories listRepositories,
listEnvironments
}; };
}; };
@@ -38,3 +38,20 @@ export type TBitbucketRepo = {
full_name: string; // workspace-slug/repo-slug full_name: string; // workspace-slug/repo-slug
slug: string; slug: string;
}; };
export type TGetBitbucketEnvironmentsDTO = {
connectionId: string;
workspaceSlug: string;
repositorySlug: string;
};
export type TBitbucketEnvironment = {
uuid: string;
slug: string;
name: string;
};
export type TBitbucketEnvironmentsResponse = {
values: TBitbucketEnvironment[];
next?: string;
};
@@ -0,0 +1,3 @@
export enum DigitalOceanConnectionMethod {
ApiToken = "api-token"
}
@@ -0,0 +1,37 @@
/* eslint-disable no-await-in-loop */
import { AxiosError } from "axios";
import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { DigitalOceanConnectionMethod } from "./digital-ocean-connection-constants";
import { DigitalOceanAppPlatformPublicAPI } from "./digital-ocean-connection-public-client";
import { DigitalOceanConnectionListItemSchema } from "./digital-ocean-connection-schemas";
import { TDigitalOceanConnectionConfig } from "./digital-ocean-connection-types";
export const getDigitalOceanConnectionListItem = () => {
return {
name: "Digital Ocean" as z.infer<typeof DigitalOceanConnectionListItemSchema>["name"],
app: AppConnection.DigitalOcean as const,
methods: Object.values(DigitalOceanConnectionMethod)
};
};
export const validateDigitalOceanConnectionCredentials = async (config: TDigitalOceanConnectionConfig) => {
try {
await DigitalOceanAppPlatformPublicAPI.healthcheck(config);
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection - verify credentials"
});
}
return config.credentials;
};
@@ -0,0 +1,105 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable class-methods-use-this */
import { AxiosInstance } from "axios";
import { createRequestClient } from "@app/lib/config/request";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { DigitalOceanConnectionMethod } from "./digital-ocean-connection-constants";
import {
TDigitalOceanApp,
TDigitalOceanConnectionConfig,
TDigitalOceanVariable
} from "./digital-ocean-connection-types";
class DigitalOceanAppPlatformPublicClient {
private readonly client: AxiosInstance;
constructor() {
this.client = createRequestClient({
baseURL: `${IntegrationUrls.DIGITAL_OCEAN_API_URL}/v2`,
headers: {
"Content-Type": "application/json",
Accept: "application/json"
}
});
}
async healthcheck(connection: TDigitalOceanConnectionConfig) {
switch (connection.method) {
case DigitalOceanConnectionMethod.ApiToken:
await this.getApps(connection);
break;
default:
throw new Error(`Unsupported connection method`);
}
}
async getApps(connection: TDigitalOceanConnectionConfig) {
const response = await this.client.get<{ apps: TDigitalOceanApp[] }>(`/apps`, {
headers: {
Authorization: `Bearer ${connection.credentials.apiToken}`
}
});
return response.data.apps;
}
async getApp(connection: TDigitalOceanConnectionConfig, appId: string) {
const response = await this.client.get<{ app: TDigitalOceanApp }>(`/apps/${appId}`, {
headers: {
Authorization: `Bearer ${connection.credentials.apiToken}`
}
});
return response.data.app;
}
async getVariables(connection: TDigitalOceanConnectionConfig, appId: string): Promise<TDigitalOceanVariable[]> {
const app = await this.getApp(connection, appId);
return app.spec.envs || [];
}
async putVariables(connection: TDigitalOceanConnectionConfig, appId: string, ...input: TDigitalOceanVariable[]) {
const response = await this.getApp(connection, appId);
return this.client.put(
`/apps/${appId}`,
{
spec: {
...response.spec,
envs: input
}
},
{
headers: {
Authorization: `Bearer ${connection.credentials.apiToken}`
}
}
);
}
async deleteVariables(connection: TDigitalOceanConnectionConfig, appId: string, ...input: TDigitalOceanVariable[]) {
const response = await this.getApp(connection, appId);
const existing = response.spec.envs || [];
const variables = existing.filter((v) => input.find((i) => i.key === v.key));
return this.client.put(
`/apps/${appId}`,
{
spec: {
...response.spec,
envs: variables
}
},
{
headers: {
Authorization: `Bearer ${connection.credentials.apiToken}`
}
}
);
}
}
export const DigitalOceanAppPlatformPublicAPI = new DigitalOceanAppPlatformPublicClient();
@@ -0,0 +1,67 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { DigitalOceanConnectionMethod } from "./digital-ocean-connection-constants";
export const DigitalOceanConnectionMethodSchema = z
.nativeEnum(DigitalOceanConnectionMethod)
.describe(AppConnections.CREATE(AppConnection.DigitalOcean).method);
export const DigitalOceanConnectionAccessTokenCredentialsSchema = z.object({
apiToken: z
.string()
.trim()
.min(1, "API Token required")
.max(255)
.describe(AppConnections.CREDENTIALS.DIGITAL_OCEAN_APP_PLATFORM.apiToken)
});
const BaseDigitalOceanConnectionSchema = BaseAppConnectionSchema.extend({
app: z.literal(AppConnection.DigitalOcean)
});
export const DigitalOceanConnectionSchema = BaseDigitalOceanConnectionSchema.extend({
method: DigitalOceanConnectionMethodSchema,
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema
});
export const SanitizedDigitalOceanConnectionSchema = z.discriminatedUnion("method", [
BaseDigitalOceanConnectionSchema.extend({
method: DigitalOceanConnectionMethodSchema,
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema.pick({})
})
]);
export const ValidateDigitalOceanConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: DigitalOceanConnectionMethodSchema,
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.DigitalOcean).credentials
)
})
]);
export const CreateDigitalOceanConnectionSchema = ValidateDigitalOceanConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.DigitalOcean)
);
export const UpdateDigitalOceanConnectionSchema = z
.object({
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.DigitalOcean).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.DigitalOcean));
export const DigitalOceanConnectionListItemSchema = z.object({
name: z.literal("Digital Ocean"),
app: z.literal(AppConnection.DigitalOcean),
methods: z.nativeEnum(DigitalOceanConnectionMethod).array()
});
@@ -0,0 +1,29 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { DigitalOceanAppPlatformPublicAPI } from "./digital-ocean-connection-public-client";
import { TDigitalOceanConnection } from "./digital-ocean-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TDigitalOceanConnection>;
export const digitalOceanAppPlatformConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listApps = async (connectionId: string, actor: OrgServiceActor) => {
const connection = await getAppConnection(AppConnection.DigitalOcean, connectionId, actor);
try {
const apps = await DigitalOceanAppPlatformPublicAPI.getApps(connection);
return apps;
} catch (error) {
logger.error(error, "Failed to list apps on Digital Ocean");
return [];
}
};
return {
listApps
};
};
@@ -0,0 +1,42 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateDigitalOceanConnectionSchema,
DigitalOceanConnectionSchema,
ValidateDigitalOceanConnectionCredentialsSchema
} from "./digital-ocean-connection-schemas";
export type TDigitalOceanConnection = z.infer<typeof DigitalOceanConnectionSchema>;
export type TDigitalOceanConnectionInput = z.infer<typeof CreateDigitalOceanConnectionSchema> & {
app: AppConnection.DigitalOcean;
};
export type TValidateDigitalOceanCredentialsSchema = typeof ValidateDigitalOceanConnectionCredentialsSchema;
export type TDigitalOceanConnectionConfig = DiscriminativePick<
TDigitalOceanConnection,
"method" | "app" | "credentials"
> & {
orgId: string;
};
export type TDigitalOceanVariable = {
key: string;
value: string;
type: "SECRET" | "GENERAL";
};
export type TDigitalOceanApp = {
id: string;
spec: {
name: string;
services: Array<{
name: string;
}>;
envs?: TDigitalOceanVariable[];
};
};
@@ -0,0 +1,4 @@
export * from "./digital-ocean-connection-constants";
export * from "./digital-ocean-connection-fns";
export * from "./digital-ocean-connection-schemas";
export * from "./digital-ocean-connection-types";
@@ -0,0 +1,4 @@
export * from "./okta-connection-enums";
export * from "./okta-connection-fns";
export * from "./okta-connection-schemas";
export * from "./okta-connection-types";
@@ -0,0 +1,3 @@
export enum OktaConnectionMethod {
ApiToken = "api-token"
}
@@ -0,0 +1,57 @@
import { request } from "@app/lib/config/request";
import { UnauthorizedError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { OktaConnectionMethod } from "./okta-connection-enums";
import { TOktaApp, TOktaConnection, TOktaConnectionConfig } from "./okta-connection-types";
export const getOktaConnectionListItem = () => {
return {
name: "Okta" as const,
app: AppConnection.Okta as const,
methods: Object.values(OktaConnectionMethod) as [OktaConnectionMethod.ApiToken]
};
};
export const getOktaInstanceUrl = async (config: TOktaConnectionConfig) => {
const instanceUrl = removeTrailingSlash(config.credentials.instanceUrl);
await blockLocalAndPrivateIpAddresses(instanceUrl);
return instanceUrl;
};
export const validateOktaConnectionCredentials = async (config: TOktaConnectionConfig) => {
const { apiToken } = config.credentials;
const instanceUrl = await getOktaInstanceUrl(config);
try {
await request.get(`${instanceUrl}/api/v1/users/me`, {
headers: {
Accept: "application/json",
Authorization: `SSWS ${apiToken}`
},
validateStatus: (status) => status === 200
});
} catch (error: unknown) {
throw new UnauthorizedError({
message: "Unable to validate connection: invalid credentials"
});
}
return config.credentials;
};
export const listOktaApps = async (appConnection: TOktaConnection) => {
const { apiToken } = appConnection.credentials;
const instanceUrl = await getOktaInstanceUrl(appConnection);
const { data } = await request.get<TOktaApp[]>(`${instanceUrl}/api/v1/apps`, {
headers: {
Accept: "application/json",
Authorization: `SSWS ${apiToken}`
}
});
return data.filter((app) => app.status === "ACTIVE" && app.name === "oidc_client");
};
@@ -0,0 +1,69 @@
import RE2 from "re2";
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { OktaConnectionMethod } from "./okta-connection-enums";
export const OktaConnectionApiTokenCredentialsSchema = z.object({
instanceUrl: z
.string()
.trim()
.url("Invalid Instance URL")
.min(1, "Instance URL required")
.max(255)
.describe(AppConnections.CREDENTIALS.OKTA.instanceUrl),
apiToken: z
.string()
.trim()
.min(1, "API Token required")
.refine((value) => new RE2("^00[a-zA-Z0-9_-]{40}$").test(value), "Invalid Okta API Token format")
.describe(AppConnections.CREDENTIALS.OKTA.apiToken)
});
const BaseOktaConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Okta) });
export const OktaConnectionSchema = BaseOktaConnectionSchema.extend({
method: z.literal(OktaConnectionMethod.ApiToken),
credentials: OktaConnectionApiTokenCredentialsSchema
});
export const SanitizedOktaConnectionSchema = z.discriminatedUnion("method", [
BaseOktaConnectionSchema.extend({
method: z.literal(OktaConnectionMethod.ApiToken),
credentials: OktaConnectionApiTokenCredentialsSchema.pick({
instanceUrl: true
})
})
]);
export const ValidateOktaConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z.literal(OktaConnectionMethod.ApiToken).describe(AppConnections.CREATE(AppConnection.Okta).method),
credentials: OktaConnectionApiTokenCredentialsSchema.describe(AppConnections.CREATE(AppConnection.Okta).credentials)
})
]);
export const CreateOktaConnectionSchema = ValidateOktaConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Okta)
);
export const UpdateOktaConnectionSchema = z
.object({
credentials: OktaConnectionApiTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Okta).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Okta));
export const OktaConnectionListItemSchema = z.object({
name: z.literal("Okta"),
app: z.literal(AppConnection.Okta),
methods: z.nativeEnum(OktaConnectionMethod).array()
});
@@ -0,0 +1,23 @@
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listOktaApps } from "./okta-connection-fns";
import { TOktaConnection } from "./okta-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TOktaConnection>;
export const oktaConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listApps = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Okta, connectionId, actor);
const apps = await listOktaApps(appConnection);
return apps;
};
return {
listApps
};
};
@@ -0,0 +1,29 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateOktaConnectionSchema,
OktaConnectionSchema,
ValidateOktaConnectionCredentialsSchema
} from "./okta-connection-schemas";
export type TOktaConnection = z.infer<typeof OktaConnectionSchema>;
export type TOktaConnectionInput = z.infer<typeof CreateOktaConnectionSchema> & {
app: AppConnection.Okta;
};
export type TValidateOktaConnectionCredentialsSchema = typeof ValidateOktaConnectionCredentialsSchema;
export type TOktaConnectionConfig = DiscriminativePick<TOktaConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};
export type TOktaApp = {
id: string;
label: string;
status: "ACTIVE" | "INACTIVE";
name: "oidc_client"; // "oidc_client" or other types
};
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { TableName } from "@app/db/schemas"; import { ActionProjectType, TableName } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -100,7 +100,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId, projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -167,7 +168,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId: certificateAuthority.projectId, projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -215,7 +217,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId, projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -268,7 +271,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId: certificateAuthority.projectId, projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -341,7 +345,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId: certificateAuthority.projectId, projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(

Some files were not shown because too many files have changed in this diff Show More