diff --git a/backend/e2e-test/mocks/keystore.ts b/backend/e2e-test/mocks/keystore.ts index f4f251616..91b64ff0d 100644 --- a/backend/e2e-test/mocks/keystore.ts +++ b/backend/e2e-test/mocks/keystore.ts @@ -8,6 +8,9 @@ import { Lock } from "@app/lib/red-lock"; export const mockKeyStore = (): TKeyStoreFactory => { const store: Record = {}; + const getRegex = (pattern: string) => + new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`); + return { setItem: async (key, value) => { store[key] = value; @@ -23,7 +26,7 @@ export const mockKeyStore = (): TKeyStoreFactory => { return 1; }, deleteItems: async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }) => { - const regex = new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`); + const regex = getRegex(pattern); let totalDeleted = 0; const keys = Object.keys(store); @@ -53,6 +56,27 @@ export const mockKeyStore = (): TKeyStoreFactory => { incrementBy: async () => { return 1; }, + getItems: async (keys) => { + const values = keys.map((key) => { + const value = store[key]; + if (typeof value === "string") { + return value; + } + return null; + }); + return values; + }, + getKeysByPattern: async (pattern) => { + const regex = getRegex(pattern); + const keys = Object.keys(store); + return keys.filter((key) => regex.test(key)); + }, + deleteItemsByKeyIn: async (keys) => { + for (const key of keys) { + delete store[key]; + } + return keys.length; + }, acquireLock: () => { return Promise.resolve({ release: () => {} diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 4fe4e17cf..2956be192 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -74,6 +74,7 @@ import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-a import { TIdentityOciAuthServiceFactory } from "@app/services/identity-oci-auth/identity-oci-auth-service"; import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service"; import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; +import { TIdentityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-types"; import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service"; import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; import { TIntegrationServiceFactory } from "@app/services/integration/integration-service"; @@ -218,6 +219,7 @@ declare module "fastify" { identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory; identityGcpAuth: TIdentityGcpAuthServiceFactory; identityAliCloudAuth: TIdentityAliCloudAuthServiceFactory; + identityTlsCertAuth: TIdentityTlsCertAuthServiceFactory; identityAwsAuth: TIdentityAwsAuthServiceFactory; identityAzureAuth: TIdentityAzureAuthServiceFactory; identityOciAuth: TIdentityOciAuthServiceFactory; diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index 1d4ad1797..7ead9f84b 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -164,6 +164,9 @@ import { TIdentityProjectMemberships, TIdentityProjectMembershipsInsert, TIdentityProjectMembershipsUpdate, + TIdentityTlsCertAuths, + TIdentityTlsCertAuthsInsert, + TIdentityTlsCertAuthsUpdate, TIdentityTokenAuths, TIdentityTokenAuthsInsert, TIdentityTokenAuthsUpdate, @@ -794,6 +797,11 @@ declare module "knex/types/tables" { TIdentityAlicloudAuthsInsert, TIdentityAlicloudAuthsUpdate >; + [TableName.IdentityTlsCertAuth]: KnexOriginal.CompositeTableType< + TIdentityTlsCertAuths, + TIdentityTlsCertAuthsInsert, + TIdentityTlsCertAuthsUpdate + >; [TableName.IdentityAwsAuth]: KnexOriginal.CompositeTableType< TIdentityAwsAuths, TIdentityAwsAuthsInsert, diff --git a/backend/src/db/migrations/20250624061429_identity-tls-auth.ts b/backend/src/db/migrations/20250624061429_identity-tls-auth.ts new file mode 100644 index 000000000..3e6dc1af8 --- /dev/null +++ b/backend/src/db/migrations/20250624061429_identity-tls-auth.ts @@ -0,0 +1,28 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; +import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasTable(TableName.IdentityTlsCertAuth))) { + await knex.schema.createTable(TableName.IdentityTlsCertAuth, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable(); + t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable(); + t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable(); + t.jsonb("accessTokenTrustedIps").notNullable(); + t.timestamps(true, true, true); + t.uuid("identityId").notNullable().unique(); + t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE"); + t.string("allowedCommonNames").nullable(); + t.binary("encryptedCaCertificate").notNullable(); + }); + } + + await createOnUpdateTrigger(knex, TableName.IdentityTlsCertAuth); +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTableIfExists(TableName.IdentityTlsCertAuth); + await dropOnUpdateTrigger(knex, TableName.IdentityTlsCertAuth); +} diff --git a/backend/src/db/migrations/20250626101747_default-project-type.ts b/backend/src/db/migrations/20250626101747_default-project-type.ts new file mode 100644 index 000000000..5e14b6ea2 --- /dev/null +++ b/backend/src/db/migrations/20250626101747_default-project-type.ts @@ -0,0 +1,41 @@ +import { Knex } from "knex"; + +import { ProjectType, TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type"); + const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct"); + if (hasTypeColumn && !hasDefaultTypeColumn) { + await knex.schema.alterTable(TableName.Project, (t) => { + t.string("type").nullable().alter(); + t.string("defaultProduct").notNullable().defaultTo(ProjectType.SecretManager); + }); + + 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 + `) + }); + } + + const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type"); + if (hasTemplateTypeColumn) { + await knex.schema.alterTable(TableName.ProjectTemplates, (t) => { + t.string("type").nullable().alter(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct"); + if (hasDefaultTypeColumn) { + await knex.schema.alterTable(TableName.Project, (t) => { + t.dropColumn("defaultProduct"); + }); + } +} diff --git a/backend/src/db/schemas/identity-tls-cert-auths.ts b/backend/src/db/schemas/identity-tls-cert-auths.ts new file mode 100644 index 000000000..c907ead73 --- /dev/null +++ b/backend/src/db/schemas/identity-tls-cert-auths.ts @@ -0,0 +1,27 @@ +// 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 { zodBuffer } from "@app/lib/zod"; + +import { TImmutableDBKeys } from "./models"; + +export const IdentityTlsCertAuthsSchema = z.object({ + id: z.string().uuid(), + accessTokenTTL: z.coerce.number().default(7200), + accessTokenMaxTTL: z.coerce.number().default(7200), + accessTokenNumUsesLimit: z.coerce.number().default(0), + accessTokenTrustedIps: z.unknown(), + createdAt: z.date(), + updatedAt: z.date(), + identityId: z.string().uuid(), + allowedCommonNames: z.string().nullable().optional(), + encryptedCaCertificate: zodBuffer +}); + +export type TIdentityTlsCertAuths = z.infer; +export type TIdentityTlsCertAuthsInsert = Omit, TImmutableDBKeys>; +export type TIdentityTlsCertAuthsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index 292551c80..1642c3555 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -52,6 +52,7 @@ export * from "./identity-org-memberships"; export * from "./identity-project-additional-privilege"; export * from "./identity-project-membership-role"; export * from "./identity-project-memberships"; +export * from "./identity-tls-cert-auths"; export * from "./identity-token-auths"; export * from "./identity-ua-client-secrets"; export * from "./identity-universal-auths"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index ceba6e370..75d36833b 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -86,6 +86,7 @@ export enum TableName { IdentityOidcAuth = "identity_oidc_auths", IdentityJwtAuth = "identity_jwt_auths", IdentityLdapAuth = "identity_ldap_auths", + IdentityTlsCertAuth = "identity_tls_cert_auths", IdentityOrgMembership = "identity_org_memberships", IdentityProjectMembership = "identity_project_memberships", IdentityProjectMembershipRole = "identity_project_membership_role", @@ -251,6 +252,7 @@ export enum IdentityAuthMethod { ALICLOUD_AUTH = "alicloud-auth", AWS_AUTH = "aws-auth", AZURE_AUTH = "azure-auth", + TLS_CERT_AUTH = "tls-cert-auth", OCI_AUTH = "oci-auth", OIDC_AUTH = "oidc-auth", JWT_AUTH = "jwt-auth", @@ -265,16 +267,6 @@ export enum ProjectType { 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 { ASC = "asc", DESC = "desc" diff --git a/backend/src/db/schemas/project-templates.ts b/backend/src/db/schemas/project-templates.ts index f12386165..d1fe29a80 100644 --- a/backend/src/db/schemas/project-templates.ts +++ b/backend/src/db/schemas/project-templates.ts @@ -16,7 +16,7 @@ export const ProjectTemplatesSchema = z.object({ orgId: z.string().uuid(), createdAt: z.date(), updatedAt: z.date(), - type: z.string().default("secret-manager") + type: z.string().nullable().optional() }); export type TProjectTemplates = z.infer; diff --git a/backend/src/db/schemas/projects.ts b/backend/src/db/schemas/projects.ts index b4c98d8a2..00401575e 100644 --- a/backend/src/db/schemas/projects.ts +++ b/backend/src/db/schemas/projects.ts @@ -25,11 +25,12 @@ export const ProjectsSchema = z.object({ kmsSecretManagerKeyId: z.string().uuid().nullable().optional(), kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(), description: z.string().nullable().optional(), - type: z.string(), + type: z.string().nullable().optional(), enforceCapitalization: z.boolean().default(false), hasDeleteProtection: z.boolean().default(false).nullable().optional(), secretSharing: z.boolean().default(true), - showSnapshotsLegacy: z.boolean().default(false) + showSnapshotsLegacy: z.boolean().default(false), + defaultProduct: z.string().default("secret-manager") }); export type TProjects = z.infer; diff --git a/backend/src/ee/routes/v1/access-approval-request-router.ts b/backend/src/ee/routes/v1/access-approval-request-router.ts index 8a6b2be88..7a70d6374 100644 --- a/backend/src/ee/routes/v1/access-approval-request-router.ts +++ b/backend/src/ee/routes/v1/access-approval-request-router.ts @@ -60,7 +60,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv method: "GET", schema: { querystring: z.object({ - projectSlug: z.string().trim() + projectSlug: z.string().trim(), + policyId: z.string().trim().optional() }), response: { 200: z.object({ @@ -73,6 +74,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv handler: async (req) => { const { count } = await server.services.accessApprovalRequest.getCount({ projectSlug: req.query.projectSlug, + policyId: req.query.policyId, actor: req.permission.type, actorId: req.permission.id, actorOrgId: req.permission.orgId, diff --git a/backend/src/ee/routes/v1/project-template-router.ts b/backend/src/ee/routes/v1/project-template-router.ts index 5d33b4d58..a00f4aa0b 100644 --- a/backend/src/ee/routes/v1/project-template-router.ts +++ b/backend/src/ee/routes/v1/project-template-router.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { ProjectMembershipRole, ProjectTemplatesSchema, ProjectType } from "@app/db/schemas"; +import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns"; @@ -104,9 +104,6 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider) hide: false, tags: [ApiDocsTags.ProjectTemplates], description: "List project templates for the current organization.", - querystring: z.object({ - type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type) - }), response: { 200: z.object({ projectTemplates: SanitizedProjectTemplateSchema.array() @@ -115,8 +112,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider) }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { type } = req.query; - const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission, type); + const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission); const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name)); @@ -188,7 +184,6 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider) tags: [ApiDocsTags.ProjectTemplates], description: "Create a project template.", body: z.object({ - type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type), name: slugSchema({ field: "name" }) .refine((val) => !isInfisicalProjectTemplate(val), { message: `The requested project template name is reserved.` @@ -284,7 +279,6 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider) tags: [ApiDocsTags.ProjectTemplates], description: "Delete a project template.", params: z.object({ templateId: z.string().uuid().describe(ProjectTemplates.DELETE.templateId) }), - response: { 200: z.object({ projectTemplate: SanitizedProjectTemplateSchema diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index e558062b1..d53124e52 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -94,7 +94,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv }, schema: { querystring: z.object({ - workspaceId: z.string().trim() + workspaceId: z.string().trim(), + policyId: z.string().trim().optional() }), response: { 200: z.object({ @@ -112,7 +113,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, - projectId: req.query.workspaceId + projectId: req.query.workspaceId, + policyId: req.query.policyId }); return { approvals }; } diff --git a/backend/src/ee/routes/v1/ssh-certificate-router.ts b/backend/src/ee/routes/v1/ssh-certificate-router.ts index cb576e496..4706e992d 100644 --- a/backend/src/ee/routes/v1/ssh-certificate-router.ts +++ b/backend/src/ee/routes/v1/ssh-certificate-router.ts @@ -80,6 +80,7 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SignSshKey, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { certificateTemplateId: req.body.certificateTemplateId, principals: req.body.principals, @@ -171,6 +172,7 @@ export const registerSshCertRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.IssueSshCreds, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { certificateTemplateId: req.body.certificateTemplateId, principals: req.body.principals, diff --git a/backend/src/ee/routes/v1/ssh-host-router.ts b/backend/src/ee/routes/v1/ssh-host-router.ts index 4c749f6f5..50512a292 100644 --- a/backend/src/ee/routes/v1/ssh-host-router.ts +++ b/backend/src/ee/routes/v1/ssh-host-router.ts @@ -358,6 +358,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.IssueSshHostUserCert, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { sshHostId: req.params.sshHostId, hostname: host.hostname, @@ -427,6 +428,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.IssueSshHostHostCert, + organizationId: req.permission.orgId, distinctId: getTelemetryDistinctId(req), properties: { sshHostId: req.params.sshHostId, diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts index 4e818df2a..5976f5fff 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; @@ -97,8 +96,7 @@ export const accessApprovalPolicyServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -248,8 +246,7 @@ export const accessApprovalPolicyServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null }); @@ -301,8 +298,7 @@ export const accessApprovalPolicyServiceFactory = ({ actorId, projectId: accessApprovalPolicy.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); @@ -498,8 +494,7 @@ export const accessApprovalPolicyServiceFactory = ({ actorId, projectId: policy.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, @@ -549,8 +544,7 @@ export const accessApprovalPolicyServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!membership) { throw new ForbiddenRequestError({ message: "You are not a member of this project" }); @@ -589,8 +583,7 @@ export const accessApprovalPolicyServiceFactory = ({ actorId, projectId: policy.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts b/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts index 33e9f7a32..671d2c1de 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts @@ -220,7 +220,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit; - getCount: ({ projectId }: { projectId: string }) => Promise<{ + getCount: ({ projectId }: { projectId: string; policyId?: string }) => Promise<{ pendingCount: number; finalizedCount: number; }>; @@ -702,7 +702,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR } }; - const getCount: TAccessApprovalRequestDALFactory["getCount"] = async ({ projectId }) => { + const getCount: TAccessApprovalRequestDALFactory["getCount"] = async ({ projectId, policyId }) => { try { const accessRequests = await db .replicaNode()(TableName.AccessApprovalRequest) @@ -723,8 +723,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR `${TableName.AccessApprovalRequest}.id`, `${TableName.AccessApprovalRequestReviewer}.requestId` ) - .where(`${TableName.Environment}.projectId`, projectId) + .where((qb) => { + if (policyId) void qb.where(`${TableName.AccessApprovalPolicy}.id`, policyId); + }) .select(selectAllTableCols(TableName.AccessApprovalRequest)) .select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")) .select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId")) diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index 5a3af5aa5..4cee898f1 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -1,7 +1,7 @@ import slugify from "@sindresorhus/slugify"; import msFn from "ms"; -import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas"; +import { ProjectMembershipRole } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; @@ -107,8 +107,7 @@ export const accessApprovalRequestServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!membership) { throw new ForbiddenRequestError({ message: "You are not a member of this project" }); @@ -217,7 +216,7 @@ export const accessApprovalRequestServiceFactory = ({ ); const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`; - const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`; + const approvalUrl = `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`; await triggerWorkflowIntegrationNotification({ input: { @@ -290,8 +289,7 @@ export const accessApprovalRequestServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!membership) { throw new ForbiddenRequestError({ message: "You are not a member of this project" }); @@ -337,8 +335,7 @@ export const accessApprovalRequestServiceFactory = ({ actorId, projectId: accessApprovalRequest.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!membership) { @@ -350,6 +347,12 @@ export const accessApprovalRequestServiceFactory = ({ const canBypass = !policy.bypassers.length || policy.bypassers.some((bypasser) => bypasser.userId === actorId); const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypass); + // Calculate break glass attempt before sequence checks + const isBreakGlassApprovalAttempt = + policy.enforcementLevel === EnforcementLevel.Soft && + actorId === accessApprovalRequest.requestedByUserId && + status === ApprovalStatus.APPROVED; + const isApprover = policy.approvers.find((approver) => approver.userId === actorId); // If user is (not an approver OR cant self approve) AND can't bypass policy if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) { @@ -409,15 +412,14 @@ export const accessApprovalRequestServiceFactory = ({ const isApproverOfTheSequence = policy.approvers.find( (el) => el.sequence === presentSequence.step && el.userId === actorId ); - if (!isApproverOfTheSequence) throw new BadRequestError({ message: "You are not reviewer in this step" }); + + // Only throw if actor is not the approver and not bypassing + if (!isApproverOfTheSequence && !isBreakGlassApprovalAttempt) { + throw new BadRequestError({ message: "You are not a reviewer in this step" }); + } } const reviewStatus = await accessApprovalRequestReviewerDAL.transaction(async (tx) => { - const isBreakGlassApprovalAttempt = - policy.enforcementLevel === EnforcementLevel.Soft && - actorId === accessApprovalRequest.requestedByUserId && - status === ApprovalStatus.APPROVED; - let reviewForThisActorProcessing: { id: string; requestId: string; @@ -543,7 +545,7 @@ export const accessApprovalRequestServiceFactory = ({ bypassReason: bypassReason || "No reason provided", secretPath: policy.secretPath || "/", environment, - approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval`, + approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`, requestType: "access" }, template: SmtpTemplates.AccessSecretRequestBypassed @@ -560,6 +562,7 @@ export const accessApprovalRequestServiceFactory = ({ const getCount: TAccessApprovalRequestServiceFactory["getCount"] = async ({ projectSlug, + policyId, actor, actorAuthMethod, actorId, @@ -573,14 +576,13 @@ export const accessApprovalRequestServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!membership) { throw new ForbiddenRequestError({ message: "You are not a member of this project" }); } - const count = await accessApprovalRequestDAL.getCount({ projectId: project.id }); + const count = await accessApprovalRequestDAL.getCount({ projectId: project.id, policyId }); return { count }; }; diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-types.ts b/backend/src/ee/services/access-approval-request/access-approval-request-types.ts index 2550f2a96..9066aec8f 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-types.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-types.ts @@ -12,6 +12,7 @@ export type TVerifyPermission = { export type TGetAccessRequestCountDTO = { projectSlug: string; + policyId?: string; } & Omit; export type TReviewAccessRequestDTO = { diff --git a/backend/src/ee/services/assume-privilege/assume-privilege-service.ts b/backend/src/ee/services/assume-privilege/assume-privilege-service.ts index d4a643d8b..c1cfad082 100644 --- a/backend/src/ee/services/assume-privilege/assume-privilege-service.ts +++ b/backend/src/ee/services/assume-privilege/assume-privilege-service.ts @@ -1,7 +1,6 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; -import { ActionProjectType } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ActorType } from "@app/services/auth/auth-type"; @@ -38,8 +37,7 @@ export const assumePrivilegeServiceFactory = ({ actorId: actorPermissionDetails.id, projectId, actorAuthMethod: actorPermissionDetails.authMethod, - actorOrgId: actorPermissionDetails.orgId, - actionProjectType: ActionProjectType.Any + actorOrgId: actorPermissionDetails.orgId }); if (targetActorType === ActorType.USER) { @@ -60,8 +58,7 @@ export const assumePrivilegeServiceFactory = ({ actorId: targetActorId, projectId, actorAuthMethod: actorPermissionDetails.authMethod, - actorOrgId: actorPermissionDetails.orgId, - actionProjectType: ActionProjectType.Any + actorOrgId: actorPermissionDetails.orgId }); const appCfg = getConfig(); diff --git a/backend/src/ee/services/audit-log/audit-log-queue.ts b/backend/src/ee/services/audit-log/audit-log-queue.ts index e812cb7ea..5cd1f507e 100644 --- a/backend/src/ee/services/audit-log/audit-log-queue.ts +++ b/backend/src/ee/services/audit-log/audit-log-queue.ts @@ -131,7 +131,6 @@ export const auditLogQueueServiceFactory = async ({ }); try { - logger.info(`Streaming audit log [url=${url}] for org [orgId=${orgId}]`); const response = await request.post( url, { ...providerSpecificPayload(url), ...auditLog }, @@ -143,9 +142,6 @@ export const auditLogQueueServiceFactory = async ({ signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT) } ); - logger.info( - `Successfully streamed audit log [url=${url}] for org [orgId=${orgId}] [response=${JSON.stringify(response.data)}]` - ); return response; } catch (error) { logger.error( @@ -237,7 +233,6 @@ export const auditLogQueueServiceFactory = async ({ }); try { - logger.info(`Streaming audit log [url=${url}] for org [orgId=${orgId}]`); const response = await request.post( url, { ...providerSpecificPayload(url), ...auditLog }, @@ -249,9 +244,6 @@ export const auditLogQueueServiceFactory = async ({ signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT) } ); - logger.info( - `Successfully streamed audit log [url=${url}] for org [orgId=${orgId}] [response=${JSON.stringify(response.data)}]` - ); return response; } catch (error) { logger.error( diff --git a/backend/src/ee/services/audit-log/audit-log-service.ts b/backend/src/ee/services/audit-log/audit-log-service.ts index 4bea26ac6..bf00a499a 100644 --- a/backend/src/ee/services/audit-log/audit-log-service.ts +++ b/backend/src/ee/services/audit-log/audit-log-service.ts @@ -1,7 +1,6 @@ import { ForbiddenError } from "@casl/ability"; import { requestContext } from "@fastify/request-context"; -import { ActionProjectType } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { ActorType } from "@app/services/auth/auth-type"; @@ -38,8 +37,7 @@ export const auditLogServiceFactory = ({ actorId, projectId: filter.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); } else { diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index e72b9fa46..a2acb62c4 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -202,6 +202,12 @@ export enum EventType { REVOKE_IDENTITY_ALICLOUD_AUTH = "revoke-identity-alicloud-auth", GET_IDENTITY_ALICLOUD_AUTH = "get-identity-alicloud-auth", + LOGIN_IDENTITY_TLS_CERT_AUTH = "login-identity-tls-cert-auth", + ADD_IDENTITY_TLS_CERT_AUTH = "add-identity-tls-cert-auth", + UPDATE_IDENTITY_TLS_CERT_AUTH = "update-identity-tls-cert-auth", + REVOKE_IDENTITY_TLS_CERT_AUTH = "revoke-identity-tls-cert-auth", + GET_IDENTITY_TLS_CERT_AUTH = "get-identity-tls-cert-auth", + LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth", ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth", UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth", @@ -1141,6 +1147,53 @@ interface GetIdentityAliCloudAuthEvent { }; } +interface LoginIdentityTlsCertAuthEvent { + type: EventType.LOGIN_IDENTITY_TLS_CERT_AUTH; + metadata: { + identityId: string; + identityTlsCertAuthId: string; + identityAccessTokenId: string; + }; +} + +interface AddIdentityTlsCertAuthEvent { + type: EventType.ADD_IDENTITY_TLS_CERT_AUTH; + metadata: { + identityId: string; + allowedCommonNames: string | null | undefined; + accessTokenTTL: number; + accessTokenMaxTTL: number; + accessTokenNumUsesLimit: number; + accessTokenTrustedIps: Array; + }; +} + +interface DeleteIdentityTlsCertAuthEvent { + type: EventType.REVOKE_IDENTITY_TLS_CERT_AUTH; + metadata: { + identityId: string; + }; +} + +interface UpdateIdentityTlsCertAuthEvent { + type: EventType.UPDATE_IDENTITY_TLS_CERT_AUTH; + metadata: { + identityId: string; + allowedCommonNames: string | null | undefined; + accessTokenTTL?: number; + accessTokenMaxTTL?: number; + accessTokenNumUsesLimit?: number; + accessTokenTrustedIps?: Array; + }; +} + +interface GetIdentityTlsCertAuthEvent { + type: EventType.GET_IDENTITY_TLS_CERT_AUTH; + metadata: { + identityId: string; + }; +} + interface LoginIdentityOciAuthEvent { type: EventType.LOGIN_IDENTITY_OCI_AUTH; metadata: { @@ -3358,6 +3411,11 @@ export type Event = | UpdateIdentityAliCloudAuthEvent | GetIdentityAliCloudAuthEvent | DeleteIdentityAliCloudAuthEvent + | LoginIdentityTlsCertAuthEvent + | AddIdentityTlsCertAuthEvent + | UpdateIdentityTlsCertAuthEvent + | GetIdentityTlsCertAuthEvent + | DeleteIdentityTlsCertAuthEvent | LoginIdentityOciAuthEvent | AddIdentityOciAuthEvent | UpdateIdentityOciAuthEvent diff --git a/backend/src/ee/services/certificate-authority-crl/certificate-authority-crl-service.ts b/backend/src/ee/services/certificate-authority-crl/certificate-authority-crl-service.ts index 5ead798fa..cc6a6b5fe 100644 --- a/backend/src/ee/services/certificate-authority-crl/certificate-authority-crl-service.ts +++ b/backend/src/ee/services/certificate-authority-crl/certificate-authority-crl-service.ts @@ -1,7 +1,6 @@ import { ForbiddenError } from "@casl/ability"; 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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; @@ -78,8 +77,7 @@ export const certificateAuthorityCrlServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index cf37626c7..c2c596922 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -1,7 +1,6 @@ import { ForbiddenError, subject } from "@casl/ability"; import RE2 from "re2"; -import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { @@ -85,8 +84,7 @@ export const dynamicSecretLeaseServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const plan = await licenseService.getPlan(actorOrgId); @@ -202,8 +200,7 @@ export const dynamicSecretLeaseServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ @@ -300,8 +297,7 @@ export const dynamicSecretLeaseServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ @@ -389,8 +385,7 @@ export const dynamicSecretLeaseServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -437,8 +432,7 @@ export const dynamicSecretLeaseServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index d0d14ddaf..bfa39f39b 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { @@ -78,8 +77,7 @@ export const dynamicSecretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -202,8 +200,7 @@ export const dynamicSecretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const plan = await licenseService.getPlan(actorOrgId); @@ -354,8 +351,7 @@ export const dynamicSecretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -420,8 +416,7 @@ export const dynamicSecretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -485,8 +480,7 @@ export const dynamicSecretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); // verify user has access to each env in request @@ -529,8 +523,7 @@ export const dynamicSecretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionDynamicSecretActions.ReadRootCredential, @@ -578,8 +571,7 @@ export const dynamicSecretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); @@ -616,8 +608,7 @@ export const dynamicSecretServiceFactory = ({ actorId: actor.id, projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId: actor.orgId }); const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) => @@ -661,8 +652,7 @@ export const dynamicSecretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path); diff --git a/backend/src/ee/services/dynamic-secret/providers/github.ts b/backend/src/ee/services/dynamic-secret/providers/github.ts index 67d92b2a6..172041f6c 100644 --- a/backend/src/ee/services/dynamic-secret/providers/github.ts +++ b/backend/src/ee/services/dynamic-secret/providers/github.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import * as jwt from "jsonwebtoken"; +import jwt from "jsonwebtoken"; import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; diff --git a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts index 64da588f8..485e46885 100644 --- a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import { packRules } from "@casl/ability/extra"; -import { ActionProjectType, TableName } from "@app/db/schemas"; +import { TableName } from "@app/db/schemas"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { ms } from "@app/lib/ms"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; @@ -61,8 +61,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Edit, @@ -73,8 +72,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId: identityId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); // we need to validate that the privilege given is not higher than the assigning users permission @@ -160,8 +158,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Edit, @@ -172,8 +169,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId: identityProjectMembership.identityId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); // we need to validate that the privilege given is not higher than the assigning users permission @@ -260,8 +256,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Edit, @@ -272,8 +267,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId: identityProjectMembership.identityId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); const permissionBoundary = validatePrivilegeChangeOperation( membership.shouldUseNewPrivilegeSystem, @@ -321,8 +315,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Read, @@ -356,8 +349,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Read, @@ -392,8 +384,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Read, diff --git a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts index 828cf43a3..747e13f15 100644 --- a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts @@ -1,7 +1,6 @@ import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; -import { ActionProjectType } from "@app/db/schemas"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { ms } from "@app/lib/ms"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; @@ -73,8 +72,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -87,8 +85,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({ actorId: identityId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); // we need to validate that the privilege given is not higher than the assigning users permission @@ -175,8 +172,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -189,8 +185,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({ actorId: identityProjectMembership.identityId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); // we need to validate that the privilege given is not higher than the assigning users permission @@ -293,8 +288,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Edit, @@ -306,8 +300,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({ actorId: identityProjectMembership.identityId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); const permissionBoundary = validatePrivilegeChangeOperation( membership.shouldUseNewPrivilegeSystem, @@ -366,8 +359,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Read, @@ -409,8 +401,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({ actorId, projectId: identityProjectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/ee/services/kmip/kmip-operation-service.ts b/backend/src/ee/services/kmip/kmip-operation-service.ts index 55d8c2b42..2808108df 100644 --- a/backend/src/ee/services/kmip/kmip-operation-service.ts +++ b/backend/src/ee/services/kmip/kmip-operation-service.ts @@ -24,7 +24,7 @@ type TKmipOperationServiceFactoryDep = { kmsService: TKmsServiceFactory; kmsDAL: TKmsKeyDALFactory; kmipClientDAL: TKmipClientDALFactory; - projectDAL: Pick; + projectDAL: Pick; permissionService: Pick; }; diff --git a/backend/src/ee/services/kmip/kmip-service.ts b/backend/src/ee/services/kmip/kmip-service.ts index f8c52fe56..45a068a02 100644 --- a/backend/src/ee/services/kmip/kmip-service.ts +++ b/backend/src/ee/services/kmip/kmip-service.ts @@ -2,7 +2,6 @@ import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import crypto, { KeyObject } from "crypto"; -import { ActionProjectType } from "@app/db/schemas"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { isValidIp } from "@app/lib/ip"; import { ms } from "@app/lib/ms"; @@ -73,8 +72,7 @@ export const kmipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.KMS + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -127,8 +125,7 @@ export const kmipServiceFactory = ({ actorId, projectId: kmipClient.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.KMS + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -159,8 +156,7 @@ export const kmipServiceFactory = ({ actorId, projectId: kmipClient.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.KMS + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -193,8 +189,7 @@ export const kmipServiceFactory = ({ actorId, projectId: kmipClient.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.KMS + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip); @@ -215,8 +210,7 @@ export const kmipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.KMS + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip); @@ -252,8 +246,7 @@ export const kmipServiceFactory = ({ actorId, projectId: kmipClient.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.KMS + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index 11ee29852..9677c69b1 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -91,7 +91,7 @@ export interface TPermissionDALFactory { userId: string; projectId: string; username: string; - projectType: string; + projectType?: string | null; id: string; createdAt: Date; updatedAt: Date; @@ -163,7 +163,7 @@ export interface TPermissionDALFactory { createdAt: Date; updatedAt: Date; orgId: string; - projectType: string; + projectType?: string | null; shouldUseNewPrivilegeSystem: boolean; orgAuthEnforced: boolean; metadata: { @@ -201,7 +201,7 @@ export interface TPermissionDALFactory { userId: string; projectId: string; username: string; - projectType: string; + projectType?: string | null; id: string; createdAt: Date; updatedAt: Date; @@ -267,7 +267,7 @@ export interface TPermissionDALFactory { createdAt: Date; updatedAt: Date; orgId: string; - projectType: string; + projectType?: string | null; orgAuthEnforced: boolean; metadata: { id: string; diff --git a/backend/src/ee/services/permission/permission-service-types.ts b/backend/src/ee/services/permission/permission-service-types.ts index 5e71c65d9..72df88982 100644 --- a/backend/src/ee/services/permission/permission-service-types.ts +++ b/backend/src/ee/services/permission/permission-service-types.ts @@ -1,7 +1,6 @@ import { MongoAbility, RawRuleOf } from "@casl/ability"; import { MongoQuery } from "@ucast/mongo2js"; -import { ActionProjectType } from "@app/db/schemas"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { OrgPermissionSet } from "./org-permission"; @@ -21,7 +20,6 @@ export type TGetUserProjectPermissionArg = { userId: string; projectId: string; authMethod: ActorAuthMethod; - actionProjectType: ActionProjectType; userOrgId?: string; }; @@ -29,14 +27,12 @@ export type TGetIdentityProjectPermissionArg = { identityId: string; projectId: string; identityOrgId?: string; - actionProjectType: ActionProjectType; }; export type TGetServiceTokenProjectPermissionArg = { serviceTokenId: string; projectId: string; actorOrgId?: string; - actionProjectType: ActionProjectType; }; export type TGetProjectPermissionArg = { @@ -45,7 +41,6 @@ export type TGetProjectPermissionArg = { projectId: string; actorAuthMethod: ActorAuthMethod; actorOrgId?: string; - actionProjectType: ActionProjectType; }; export type TPermissionServiceFactory = { @@ -143,13 +138,7 @@ export type TPermissionServiceFactory = { }; } >; - getUserProjectPermission: ({ - userId, - projectId, - authMethod, - userOrgId, - actionProjectType - }: TGetUserProjectPermissionArg) => Promise<{ + getUserProjectPermission: ({ userId, projectId, authMethod, userOrgId }: TGetUserProjectPermissionArg) => Promise<{ permission: MongoAbility; membership: { id: string; diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 85ee82cca..32c01dcfb 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -5,7 +5,6 @@ import { MongoQuery } from "@ucast/mongo2js"; import handlebars from "handlebars"; import { - ActionProjectType, OrgMembershipRole, ProjectMembershipRole, ServiceTokenScopes, @@ -214,8 +213,7 @@ export const permissionServiceFactory = ({ userId, projectId, authMethod, - userOrgId, - actionProjectType + userOrgId }: TGetUserProjectPermissionArg): Promise> => { const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId); if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" }); @@ -242,12 +240,6 @@ export const permissionServiceFactory = ({ 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 const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; const additionalPrivileges = @@ -295,8 +287,7 @@ export const permissionServiceFactory = ({ const getIdentityProjectPermission = async ({ identityId, projectId, - identityOrgId, - actionProjectType + identityOrgId }: TGetIdentityProjectPermissionArg): Promise> => { const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId); if (!identityProjectPermission) @@ -316,12 +307,6 @@ export const permissionServiceFactory = ({ 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 = identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; const additionalPrivileges = @@ -376,8 +361,7 @@ export const permissionServiceFactory = ({ const getServiceTokenProjectPermission = async ({ serviceTokenId, projectId, - actorOrgId, - actionProjectType + actorOrgId }: TGetServiceTokenProjectPermissionArg) => { const serviceToken = await serviceTokenDAL.findById(serviceTokenId); if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` }); @@ -402,12 +386,6 @@ 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 || []); return { permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions), @@ -559,8 +537,7 @@ export const permissionServiceFactory = ({ actorId: inputActorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType + actorOrgId }: TGetProjectPermissionArg): Promise> => { let actor = inputActor; let actorId = inputActorId; @@ -581,22 +558,19 @@ export const permissionServiceFactory = ({ userId: actorId, projectId, authMethod: actorAuthMethod, - userOrgId: actorOrgId, - actionProjectType + userOrgId: actorOrgId }) as Promise>; case ActorType.SERVICE: return getServiceTokenProjectPermission({ serviceTokenId: actorId, projectId, - actorOrgId, - actionProjectType + actorOrgId }) as Promise>; case ActorType.IDENTITY: return getIdentityProjectPermission({ identityId: actorId, projectId, - identityOrgId: actorOrgId, - actionProjectType + identityOrgId: actorOrgId }) as Promise>; default: throw new BadRequestError({ diff --git a/backend/src/ee/services/pit/pit-service.ts b/backend/src/ee/services/pit/pit-service.ts index 0eb223fb4..c2a485b35 100644 --- a/backend/src/ee/services/pit/pit-service.ts +++ b/backend/src/ee/services/pit/pit-service.ts @@ -1,7 +1,6 @@ /* eslint-disable no-await-in-loop */ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -321,8 +320,7 @@ export const pitServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(userPermission).throwUnlessCan( diff --git a/backend/src/ee/services/project-template/project-template-fns.ts b/backend/src/ee/services/project-template/project-template-fns.ts index 8e8ebfa13..5d4d0a953 100644 --- a/backend/src/ee/services/project-template/project-template-fns.ts +++ b/backend/src/ee/services/project-template/project-template-fns.ts @@ -1,4 +1,3 @@ -import { ProjectType } from "@app/db/schemas"; import { InfisicalProjectTemplate, TUnpackedPermission @@ -7,21 +6,18 @@ import { getPredefinedRoles } from "@app/services/project-role/project-role-fns" import { ProjectTemplateDefaultEnvironments } from "./project-template-constants"; -export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({ +export const getDefaultProjectTemplate = (orgId: string) => ({ id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod - type, name: InfisicalProjectTemplate.Default, createdAt: new Date(), updatedAt: new Date(), - description: `Infisical's ${type} default project template`, - environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null, - roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map( - ({ name, slug, permissions }) => ({ - name, - slug, - permissions: permissions as TUnpackedPermission[] - }) - ), + description: `Infisical's default project template`, + environments: ProjectTemplateDefaultEnvironments, + roles: getPredefinedRoles({ projectId: "project-template" }) as Array<{ + name: string; + slug: string; + permissions: TUnpackedPermission[]; + }>, orgId }); diff --git a/backend/src/ee/services/project-template/project-template-service.ts b/backend/src/ee/services/project-template/project-template-service.ts index 9d585fc9a..510105572 100644 --- a/backend/src/ee/services/project-template/project-template-service.ts +++ b/backend/src/ee/services/project-template/project-template-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError } from "@casl/ability"; import { packRules } from "@casl/ability/extra"; -import { ProjectType, TProjectTemplates } from "@app/db/schemas"; +import { TProjectTemplates } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; @@ -29,13 +29,11 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla ...rest, environments: environments as TProjectTemplateEnvironment[], roles: [ - ...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map( - ({ name, slug, permissions }) => ({ - name, - slug, - permissions: permissions as TUnpackedPermission[] - }) - ), + ...getPredefinedRoles({ projectId: "project-template" }).map(({ name, slug, permissions }) => ({ + name, + slug, + permissions: permissions as TUnpackedPermission[] + })), ...(roles as TProjectTemplateRole[]).map((role) => ({ ...role, permissions: unpackPermissions(role.permissions) @@ -48,10 +46,7 @@ export const projectTemplateServiceFactory = ({ permissionService, projectTemplateDAL }: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => { - const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async ( - actor, - type - ) => { + const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (actor) => { const plan = await licenseService.getPlan(actor.orgId); if (!plan.projectTemplates) @@ -70,14 +65,11 @@ export const projectTemplateServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); const projectTemplates = await projectTemplateDAL.find({ - orgId: actor.orgId, - ...(type ? { type } : {}) + orgId: actor.orgId }); return [ - ...(type - ? [getDefaultProjectTemplate(actor.orgId, type)] - : Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))), + getDefaultProjectTemplate(actor.orgId), ...projectTemplates.map((template) => $unpackProjectTemplate(template)) ]; }; @@ -142,7 +134,7 @@ export const projectTemplateServiceFactory = ({ }; const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async ( - { roles, environments, type, ...params }, + { roles, environments, ...params }, actor ) => { const plan = await licenseService.getPlan(actor.orgId); @@ -162,10 +154,6 @@ export const projectTemplateServiceFactory = ({ 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) { throw new BadRequestError({ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions @@ -188,10 +176,8 @@ export const projectTemplateServiceFactory = ({ const projectTemplate = await projectTemplateDAL.create({ ...params, roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))), - environments: - type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null, - orgId: actor.orgId, - type + environments: environments ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null, + orgId: actor.orgId }); return $unpackProjectTemplate(projectTemplate); @@ -223,12 +209,6 @@ export const projectTemplateServiceFactory = ({ 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) { throw new BadRequestError({ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions diff --git a/backend/src/ee/services/project-template/project-template-types.ts b/backend/src/ee/services/project-template/project-template-types.ts index 2684e10e5..e705a096d 100644 --- a/backend/src/ee/services/project-template/project-template-types.ts +++ b/backend/src/ee/services/project-template/project-template-types.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas"; +import { ProjectMembershipRole, TProjectEnvironments } from "@app/db/schemas"; import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { OrgServiceActor } from "@app/lib/types"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; @@ -16,7 +16,6 @@ export type TProjectTemplateRole = { export type TCreateProjectTemplateDTO = { name: string; description?: string; - type: ProjectType; roles: TProjectTemplateRole[]; environments?: TProjectTemplateEnvironment[] | null; }; @@ -30,14 +29,10 @@ export enum InfisicalProjectTemplate { } export type TProjectTemplateServiceFactory = { - listProjectTemplatesByOrg: ( - actor: OrgServiceActor, - type?: ProjectType - ) => Promise< + listProjectTemplatesByOrg: (actor: OrgServiceActor) => Promise< ( | { id: string; - type: ProjectType; name: InfisicalProjectTemplate; createdAt: Date; updatedAt: Date; @@ -74,7 +69,6 @@ export type TProjectTemplateServiceFactory = { name: string; }[]; name: string; - type: string; orgId: string; id: string; createdAt: Date; @@ -99,7 +93,6 @@ export type TProjectTemplateServiceFactory = { name: string; }[]; name: string; - type: string; orgId: string; id: string; createdAt: Date; @@ -123,7 +116,6 @@ export type TProjectTemplateServiceFactory = { name: string; }[]; name: string; - type: string; orgId: string; id: string; createdAt: Date; @@ -146,7 +138,6 @@ export type TProjectTemplateServiceFactory = { name: string; }[]; name: string; - type: string; orgId: string; id: string; createdAt: Date; @@ -170,7 +161,6 @@ export type TProjectTemplateServiceFactory = { name: string; }[]; name: string; - type: string; orgId: string; id: string; createdAt: Date; @@ -194,7 +184,6 @@ export type TProjectTemplateServiceFactory = { name: string; }[]; name: string; - type: string; orgId: string; id: string; createdAt: Date; diff --git a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts index 944775156..d44ab054d 100644 --- a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts +++ b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; -import { ActionProjectType, TableName } from "@app/db/schemas"; +import { TableName } from "@app/db/schemas"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { ms } from "@app/lib/ms"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; @@ -61,8 +61,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorId, projectId: projectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({ @@ -70,8 +69,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorId: projectMembership.userId, projectId: projectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); // we need to validate that the privilege given is not higher than the assigning users permission @@ -166,8 +164,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorId, projectId: projectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); const { permission: targetUserPermission } = await permissionService.getProjectPermission({ @@ -175,8 +172,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorId: projectMembership.userId, projectId: projectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); // we need to validate that the privilege given is not higher than the assigning users permission @@ -276,8 +272,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorId, projectId: projectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); @@ -322,8 +317,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorId, projectId: projectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); @@ -349,8 +343,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorId, projectId: projectMembership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts index c8df810ed..cb497aa7d 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts @@ -1,7 +1,6 @@ import { ForbiddenError } from "@casl/ability"; import picomatch from "picomatch"; -import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; @@ -91,8 +90,7 @@ export const secretApprovalPolicyServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, @@ -267,8 +265,7 @@ export const secretApprovalPolicyServiceFactory = ({ actorId, projectId: secretApprovalPolicy.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); @@ -423,8 +420,7 @@ export const secretApprovalPolicyServiceFactory = ({ actorId, projectId: sapPolicy.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, @@ -463,8 +459,7 @@ export const secretApprovalPolicyServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); @@ -508,8 +503,7 @@ export const secretApprovalPolicyServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); return getSecretApprovalPolicy(projectId, environment, secretPath); @@ -535,8 +529,7 @@ export const secretApprovalPolicyServiceFactory = ({ actorId, projectId: sapPolicy.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts index 5e1e546d6..ec6a17d97 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts @@ -290,7 +290,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { } }; - const findProjectRequestCount = async (projectId: string, userId: string, tx?: Knex) => { + const findProjectRequestCount = async (projectId: string, userId: string, policyId?: string, tx?: Knex) => { try { const docs = await (tx || db) .with( @@ -309,6 +309,9 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { `${TableName.SecretApprovalPolicy}.id` ) .where({ projectId }) + .where((qb) => { + if (policyId) void qb.where(`${TableName.SecretApprovalPolicy}.id`, policyId); + }) .andWhere( (bd) => void bd diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts index 58a39dfa7..5e4e0e3d6 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts @@ -36,7 +36,7 @@ export const sendApprovalEmailsFn = async ({ firstName: reviewerUser.firstName, projectName: project.name, organizationName: project.organization.name, - approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval?requestId=${secretApprovalRequest.id}` + approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval?requestId=${secretApprovalRequest.id}` }, template: SmtpTemplates.SecretApprovalRequestNeedsReview }); diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index 49f336111..c0242f8e7 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -2,7 +2,6 @@ import { ForbiddenError, subject } from "@casl/ability"; import { - ActionProjectType, ProjectMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, @@ -168,7 +167,14 @@ export const secretApprovalRequestServiceFactory = ({ microsoftTeamsService, folderCommitService }: TSecretApprovalRequestServiceFactoryDep) => { - const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => { + const requestCount = async ({ + projectId, + policyId, + actor, + actorId, + actorOrgId, + actorAuthMethod + }: TApprovalRequestCountDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); await permissionService.getProjectPermission({ @@ -176,11 +182,10 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); - const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId); + const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId); return count; }; @@ -204,8 +209,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); @@ -257,8 +261,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if ( !hasRole(ProjectMembershipRole.Admin) && @@ -407,8 +410,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId: secretApprovalRequest.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if ( !hasRole(ProjectMembershipRole.Admin) && @@ -477,8 +479,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId: secretApprovalRequest.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if ( !hasRole(ProjectMembershipRole.Admin) && @@ -534,8 +535,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if ( @@ -951,7 +951,7 @@ export const secretApprovalRequestServiceFactory = ({ bypassReason, secretPath: policy.secretPath, environment: env.name, - approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval` + approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval` }, template: SmtpTemplates.AccessSecretRequestBypassed }); @@ -980,8 +980,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { @@ -1271,8 +1270,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); if (!folder) diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-types.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-types.ts index 2fdb0bb9d..4d4273d27 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-types.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-types.ts @@ -84,7 +84,7 @@ export type TReviewRequestDTO = { comment?: string; } & Omit; -export type TApprovalRequestCountDTO = TProjectPermission; +export type TApprovalRequestCountDTO = TProjectPermission & { policyId?: string }; export type TListApprovalsDTO = { projectId: string; diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-queue.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-queue.ts index f15cc4974..38ac137dc 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-queue.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-queue.ts @@ -166,7 +166,9 @@ export const secretRotationV2QueueServiceFactory = async ({ secretPath: folder.path, environment: environment.name, projectName: project.name, - rotationUrl: encodeURI(`${appCfg.SITE_URL}/secret-manager/${projectId}/secrets/${environment.slug}`) + rotationUrl: encodeURI( + `${appCfg.SITE_URL}/projects/${projectId}/secret-manager/secrets/${environment.slug}` + ) } }); } catch (error) { diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts index c1b5b8c25..cd88f889e 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts @@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import { Knex } from "knex"; import isEqual from "lodash.isequal"; -import { ActionProjectType, SecretType, TableName } from "@app/db/schemas"; +import { SecretType, TableName } from "@app/db/schemas"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { hasSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; @@ -218,7 +218,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -269,7 +269,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -315,7 +315,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -380,7 +380,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -424,7 +424,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -625,7 +625,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -775,7 +775,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -1105,7 +1105,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -1152,7 +1152,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -1204,7 +1204,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -1320,8 +1320,7 @@ export const secretRotationV2ServiceFactory = ({ actorId: actor.id, projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId: actor.orgId }); const permissiveFolderMappings = folderMappings.filter(({ path, environment }) => diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts index 1099e17a7..4f366870f 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import Ajv from "ajv"; -import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas"; +import { ProjectVersion, TableName } from "@app/db/schemas"; import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectPermission } from "@app/lib/types"; @@ -66,8 +66,7 @@ export const secretRotationServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretRotationActions.Read, @@ -98,8 +97,7 @@ export const secretRotationServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretRotationActions.Read, @@ -215,8 +213,7 @@ export const secretRotationServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretRotationActions.Read, @@ -264,8 +261,7 @@ export const secretRotationServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretRotationActions.Edit, @@ -285,8 +281,7 @@ export const secretRotationServiceFactory = ({ actorId, projectId: doc.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretRotationActions.Delete, diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts index aa8519027..938d50a77 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts @@ -588,7 +588,7 @@ export const secretScanningV2QueueServiceFactory = async ({ numberOfSecrets: payload.numberOfSecrets, isDiffScan: payload.isDiffScan, url: encodeURI( - `${appCfg.SITE_URL}/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}` + `${appCfg.SITE_URL}/projects/${projectId}/secret-scanning/findings?search=scanId:${payload.scanId}` ), timestamp } @@ -599,7 +599,7 @@ export const secretScanningV2QueueServiceFactory = async ({ timestamp, errorMessage: payload.errorMessage, url: encodeURI( - `${appCfg.SITE_URL}/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}` + `${appCfg.SITE_URL}/projects/${projectId}/secret-scanning/data-sources/${dataSource.type}/${dataSource.id}` ) } }); diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts index 34a981116..f1f09506f 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts @@ -1,7 +1,6 @@ import { ForbiddenError } from "@casl/ability"; import { join } from "path"; -import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { @@ -92,7 +91,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId }); @@ -154,7 +153,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: dataSource.projectId }); @@ -199,7 +198,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId }); @@ -233,7 +232,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: payload.projectId }); @@ -346,7 +345,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: dataSource.projectId }); @@ -399,7 +398,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: dataSource.projectId }); @@ -444,7 +443,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: dataSource.projectId }); @@ -508,7 +507,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: dataSource.projectId }); @@ -553,7 +552,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: dataSource.projectId }); @@ -596,7 +595,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: dataSource.projectId }); @@ -639,7 +638,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: dataSource.projectId }); @@ -672,7 +671,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId }); @@ -706,7 +705,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId }); @@ -746,7 +745,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId: finding.projectId }); @@ -777,7 +776,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId }); @@ -812,7 +811,7 @@ export const secretScanningV2ServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretScanning, + projectId }); diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index c9f6dac9d..f4d2e5e3d 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -2,7 +2,7 @@ // akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas"; +import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas"; import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; import { InternalServerError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; @@ -103,8 +103,7 @@ export const secretSnapshotServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); @@ -140,8 +139,7 @@ export const secretSnapshotServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); @@ -169,8 +167,7 @@ export const secretSnapshotServiceFactory = ({ actorId, projectId: snapshot.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); @@ -390,8 +387,7 @@ export const secretSnapshotServiceFactory = ({ actorId, projectId: snapshot.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, diff --git a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-service.ts b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-service.ts index 49d8c1ab6..e679fdfac 100644 --- a/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-service.ts +++ b/backend/src/ee/services/ssh-certificate-template/ssh-certificate-template-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; @@ -59,8 +58,7 @@ export const sshCertificateTemplateServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -132,8 +130,7 @@ export const sshCertificateTemplateServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -201,8 +198,7 @@ export const sshCertificateTemplateServiceFactory = ({ actorId, projectId: certificateTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -228,8 +224,7 @@ export const sshCertificateTemplateServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts b/backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts index aa6d4f66a..fba849d93 100644 --- a/backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts +++ b/backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; @@ -80,8 +79,7 @@ export const sshHostGroupServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.SshHostGroups); @@ -173,8 +171,7 @@ export const sshHostGroupServiceFactory = ({ actorId, projectId: sshHostGroup.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); @@ -270,8 +267,7 @@ export const sshHostGroupServiceFactory = ({ actorId, projectId: sshHostGroup.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups); @@ -294,8 +290,7 @@ export const sshHostGroupServiceFactory = ({ actorId, projectId: sshHostGroup.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.SshHostGroups); @@ -321,8 +316,7 @@ export const sshHostGroupServiceFactory = ({ actorId, projectId: sshHostGroup.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups); @@ -360,8 +354,7 @@ export const sshHostGroupServiceFactory = ({ actorId, projectId: sshHostGroup.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); @@ -400,8 +393,7 @@ export const sshHostGroupServiceFactory = ({ actorId, projectId: sshHostGroup.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); diff --git a/backend/src/ee/services/ssh-host/ssh-host-fns.ts b/backend/src/ee/services/ssh-host/ssh-host-fns.ts index dec15e093..5b2f98728 100644 --- a/backend/src/ee/services/ssh-host/ssh-host-fns.ts +++ b/backend/src/ee/services/ssh-host/ssh-host-fns.ts @@ -1,6 +1,5 @@ import { Knex } from "knex"; -import { ActionProjectType } from "@app/db/schemas"; import { BadRequestError } from "@app/lib/errors"; import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission"; @@ -63,8 +62,7 @@ export const createSshLoginMappings = async ({ userId: user.id, projectId, authMethod: actorAuthMethod, - userOrgId: actorOrgId, - actionProjectType: ActionProjectType.SSH + userOrgId: actorOrgId }); } diff --git a/backend/src/ee/services/ssh-host/ssh-host-service.ts b/backend/src/ee/services/ssh-host/ssh-host-service.ts index 64abfebbc..36bc1bbb3 100644 --- a/backend/src/ee/services/ssh-host/ssh-host-service.ts +++ b/backend/src/ee/services/ssh-host/ssh-host-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType, ProjectType } from "@app/db/schemas"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; @@ -12,11 +11,13 @@ import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certif import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; import { TSshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal"; import { TSshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal"; +import { PgSqlLock } from "@app/keystore/keystore"; import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { ActorType } from "@app/services/auth/auth-type"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; +import { bootstrapSshProject } from "@app/services/project/project-fns"; import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; @@ -43,9 +44,9 @@ type TSshHostServiceFactoryDep = { userDAL: Pick; groupDAL: Pick; projectDAL: Pick; - projectSshConfigDAL: Pick; - sshCertificateAuthorityDAL: Pick; - sshCertificateAuthoritySecretDAL: Pick; + projectSshConfigDAL: Pick; + sshCertificateAuthorityDAL: Pick; + sshCertificateAuthoritySecretDAL: Pick; sshCertificateDAL: Pick; sshCertificateBodyDAL: Pick; userGroupMembershipDAL: Pick; @@ -98,8 +99,7 @@ export const sshHostServiceFactory = ({ } const sshProjects = await projectDAL.find({ - orgId: actorOrgId, - type: ProjectType.SSH + orgId: actorOrgId }); const allowedHosts = []; @@ -111,8 +111,7 @@ export const sshHostServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); const projectHosts = await sshHostDAL.findUserAccessibleSshHosts([project.id], actorId); @@ -145,8 +144,7 @@ export const sshHostServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -184,7 +182,25 @@ export const sshHostServiceFactory = ({ return ca.id; }; - const projectSshConfig = await projectSshConfigDAL.findOne({ projectId }); + let projectSshConfig = await projectSshConfigDAL.findOne({ projectId }); + if (!projectSshConfig) { + projectSshConfig = await projectSshConfigDAL.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.SshInit(projectId)]); + + let sshConfig = await projectSshConfigDAL.findOne({ projectId }, tx); + if (sshConfig) return sshConfig; + + sshConfig = await bootstrapSshProject({ + projectId, + sshCertificateAuthorityDAL, + sshCertificateAuthoritySecretDAL, + kmsService, + projectSshConfigDAL, + tx + }); + return sshConfig; + }); + } const userSshCaId = await resolveSshCaId({ requestedId: requestedUserSshCaId, @@ -257,8 +273,7 @@ export const sshHostServiceFactory = ({ actorId, projectId: host.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -319,8 +334,7 @@ export const sshHostServiceFactory = ({ actorId, projectId: host.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -348,8 +362,7 @@ export const sshHostServiceFactory = ({ actorId, projectId: host.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -388,8 +401,7 @@ export const sshHostServiceFactory = ({ actorId, projectId: host.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); const internalPrincipals = await convertActorToPrincipals({ @@ -508,8 +520,7 @@ export const sshHostServiceFactory = ({ actorId, projectId: host.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts b/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts index 6c35f0ddd..2e45c836d 100644 --- a/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts +++ b/backend/src/ee/services/ssh/ssh-certificate-authority-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; @@ -73,8 +72,7 @@ export const sshCertificateAuthorityServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -109,8 +107,7 @@ export const sshCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -178,8 +175,7 @@ export const sshCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -217,8 +213,7 @@ export const sshCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -259,8 +254,7 @@ export const sshCertificateAuthorityServiceFactory = ({ actorId, projectId: sshCertificateTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -381,8 +375,7 @@ export const sshCertificateAuthorityServiceFactory = ({ actorId, projectId: sshCertificateTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -479,8 +472,7 @@ export const sshCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/ee/services/trusted-ip/trusted-ip-service.ts b/backend/src/ee/services/trusted-ip/trusted-ip-service.ts index 6b9686e25..69e7e5e1d 100644 --- a/backend/src/ee/services/trusted-ip/trusted-ip-service.ts +++ b/backend/src/ee/services/trusted-ip/trusted-ip-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { BadRequestError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { TProjectDALFactory } from "@app/services/project/project-dal"; @@ -36,8 +35,7 @@ export const trustedIpServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); const trustedIps = await trustedIpDAL.find({ @@ -61,8 +59,7 @@ export const trustedIpServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); @@ -107,8 +104,7 @@ export const trustedIpServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); @@ -153,8 +149,7 @@ export const trustedIpServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); diff --git a/backend/src/keystore/keystore.ts b/backend/src/keystore/keystore.ts index 1e641e813..79679d256 100644 --- a/backend/src/keystore/keystore.ts +++ b/backend/src/keystore/keystore.ts @@ -12,7 +12,8 @@ export const PgSqlLock = { OrgGatewayCertExchange: (orgId: string) => pgAdvisoryLockHashText(`org-gateway-cert-exchange:${orgId}`), SecretRotationV2Creation: (folderId: string) => pgAdvisoryLockHashText(`secret-rotation-v2-creation:${folderId}`), CreateProject: (orgId: string) => pgAdvisoryLockHashText(`create-project:${orgId}`), - CreateFolder: (envId: string, projectId: string) => pgAdvisoryLockHashText(`create-folder:${envId}-${projectId}`) + CreateFolder: (envId: string, projectId: string) => pgAdvisoryLockHashText(`create-folder:${envId}-${projectId}`), + SshInit: (projectId: string) => pgAdvisoryLockHashText(`ssh-bootstrap:${projectId}`) } as const; // all the key prefixes used must be set here to avoid conflict @@ -73,6 +74,7 @@ type TWaitTillReady = { export type TKeyStoreFactory = { setItem: (key: string, value: string | number | Buffer, prefix?: string) => Promise<"OK">; getItem: (key: string, prefix?: string) => Promise; + getItems: (keys: string[], prefix?: string) => Promise<(string | null)[]>; setExpiry: (key: string, expiryInSeconds: number) => Promise; setItemWithExpiry: ( key: string, @@ -81,6 +83,7 @@ export type TKeyStoreFactory = { prefix?: string ) => Promise<"OK">; deleteItem: (key: string) => Promise; + deleteItemsByKeyIn: (keys: string[]) => Promise; deleteItems: (arg: TDeleteItems) => Promise; incrementBy: (key: string, value: number) => Promise; acquireLock( @@ -89,6 +92,7 @@ export type TKeyStoreFactory = { settings?: Partial ): Promise<{ release: () => Promise }>; waitTillReady: ({ key, waitingCb, keyCheckCb, waitIteration, delay, jitter }: TWaitTillReady) => Promise; + getKeysByPattern: (pattern: string, limit?: number) => Promise; }; export const keyStoreFactory = (redisConfigKeys: TRedisConfigKeys): TKeyStoreFactory => { @@ -100,6 +104,9 @@ export const keyStoreFactory = (redisConfigKeys: TRedisConfigKeys): TKeyStoreFac const getItem = async (key: string, prefix?: string) => redis.get(prefix ? `${prefix}:${key}` : key); + const getItems = async (keys: string[], prefix?: string) => + redis.mget(keys.map((key) => (prefix ? `${prefix}:${key}` : key))); + const setItemWithExpiry = async ( key: string, expiryInSeconds: number | string, @@ -109,6 +116,11 @@ export const keyStoreFactory = (redisConfigKeys: TRedisConfigKeys): TKeyStoreFac const deleteItem = async (key: string) => redis.del(key); + const deleteItemsByKeyIn = async (keys: string[]) => { + if (keys.length === 0) return 0; + return redis.del(keys); + }; + const deleteItems = async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }: TDeleteItems) => { let cursor = "0"; let totalDeleted = 0; @@ -164,6 +176,24 @@ export const keyStoreFactory = (redisConfigKeys: TRedisConfigKeys): TKeyStoreFac } }; + const getKeysByPattern = async (pattern: string, limit?: number) => { + let cursor = "0"; + const allKeys: string[] = []; + + do { + // eslint-disable-next-line no-await-in-loop + const [nextCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 1000); + cursor = nextCursor; + allKeys.push(...keys); + + if (limit && allKeys.length >= limit) { + return allKeys.slice(0, limit); + } + } while (cursor !== "0"); + + return allKeys; + }; + return { setItem, getItem, @@ -175,6 +205,9 @@ export const keyStoreFactory = (redisConfigKeys: TRedisConfigKeys): TKeyStoreFac acquireLock(resources: string[], duration: number, settings?: Partial) { return redisLock.acquire(resources, duration, settings); }, - waitTillReady + waitTillReady, + getKeysByPattern, + deleteItemsByKeyIn, + getItems }; }; diff --git a/backend/src/keystore/memory.ts b/backend/src/keystore/memory.ts index 84cd06c03..cf9ba83bd 100644 --- a/backend/src/keystore/memory.ts +++ b/backend/src/keystore/memory.ts @@ -8,6 +8,8 @@ import { TKeyStoreFactory } from "./keystore"; export const inMemoryKeyStore = (): TKeyStoreFactory => { const store: Record = {}; + const getRegex = (pattern: string) => + new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`); return { setItem: async (key, value) => { @@ -24,7 +26,7 @@ export const inMemoryKeyStore = (): TKeyStoreFactory => { return 1; }, deleteItems: async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }) => { - const regex = new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`); + const regex = getRegex(pattern); let totalDeleted = 0; const keys = Object.keys(store); @@ -59,6 +61,27 @@ export const inMemoryKeyStore = (): TKeyStoreFactory => { release: () => {} }) as Promise; }, - waitTillReady: async () => {} + waitTillReady: async () => {}, + getKeysByPattern: async (pattern) => { + const regex = getRegex(pattern); + const keys = Object.keys(store); + return keys.filter((key) => regex.test(key)); + }, + deleteItemsByKeyIn: async (keys) => { + for (const key of keys) { + delete store[key]; + } + return keys.length; + }, + getItems: async (keys) => { + const values = keys.map((key) => { + const value = store[key]; + if (typeof value === "string") { + return value; + } + return null; + }); + return values; + } }; }; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index ef2f4f241..c6fd06dac 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -22,6 +22,7 @@ export enum ApiDocsTags { UniversalAuth = "Universal Auth", GcpAuth = "GCP Auth", AliCloudAuth = "Alibaba Cloud Auth", + TlsCertAuth = "TLS Certificate Auth", AwsAuth = "AWS Auth", OciAuth = "OCI Auth", AzureAuth = "Azure Auth", @@ -283,6 +284,38 @@ export const ALICLOUD_AUTH = { } } as const; +export const TLS_CERT_AUTH = { + LOGIN: { + identityId: "The ID of the identity to login." + }, + ATTACH: { + identityId: "The ID of the identity to attach the configuration onto.", + allowedCommonNames: + "The comma-separated list of trusted common names that are allowed to authenticate with Infisical.", + caCertificate: "The PEM-encoded CA certificate to validate client certificates.", + accessTokenTTL: "The lifetime for an access token in seconds.", + accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.", + accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.", + accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from." + }, + UPDATE: { + identityId: "The ID of the identity to update the auth method for.", + allowedCommonNames: + "The comma-separated list of trusted common names that are allowed to authenticate with Infisical.", + caCertificate: "The PEM-encoded CA certificate to validate client certificates.", + accessTokenTTL: "The new lifetime for an access token in seconds.", + accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.", + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.", + accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from." + }, + RETRIEVE: { + identityId: "The ID of the identity to retrieve the auth method for." + }, + REVOKE: { + identityId: "The ID of the identity to revoke the auth method for." + } +} as const; + export const AWS_AUTH = { LOGIN: { identityId: "The ID of the identity to login.", @@ -667,7 +700,8 @@ export const PROJECTS = { slug: "An optional slug for the project. (must be unique within the organization)", hasDeleteProtection: "Enable or disable delete protection for the project.", secretSharing: "Enable or disable secret sharing for the project.", - showSnapshotsLegacy: "Enable or disable legacy snapshots for the project." + showSnapshotsLegacy: "Enable or disable legacy snapshots for the project.", + defaultProduct: "The default product in which the project will open" }, GET_KEY: { workspaceId: "The ID of the project to get the key from." @@ -2398,7 +2432,8 @@ export const SecretSyncs = { keyOcid: "The OCID (Oracle Cloud Identifier) of the encryption key to use when creating secrets in the vault." }, ONEPASS: { - vaultId: "The ID of the 1Password vault to sync secrets to." + vaultId: "The ID of the 1Password vault to sync secrets to.", + valueLabel: "The label of the entry that holds the secret value." }, HEROKU: { app: "The ID of the Heroku app to sync secrets to.", diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 4fb19e7bb..8db5c16c4 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -193,6 +193,9 @@ const envSchema = z PYLON_API_KEY: zpStr(z.string().optional()), DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false"), SSL_CLIENT_CERTIFICATE_HEADER_KEY: zpStr(z.string().optional()).default("x-ssl-client-cert"), + IDENTITY_TLS_CERT_AUTH_CLIENT_CERTIFICATE_HEADER_KEY: zpStr(z.string().optional()).default( + "x-identity-tls-cert-auth-client-cert" + ), WORKFLOW_SLACK_CLIENT_ID: zpStr(z.string().optional()), WORKFLOW_SLACK_CLIENT_SECRET: zpStr(z.string().optional()), ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT: zodStrBool.default("true"), diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index b3be02c72..a8d0d10a6 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -62,7 +62,8 @@ export enum QueueName { SecretRotationV2 = "secret-rotation-v2", FolderTreeCheckpoint = "folder-tree-checkpoint", InvalidateCache = "invalidate-cache", - SecretScanningV2 = "secret-scanning-v2" + SecretScanningV2 = "secret-scanning-v2", + TelemetryAggregatedEvents = "telemetry-aggregated-events" } export enum QueueJobs { @@ -101,7 +102,8 @@ export enum QueueJobs { SecretScanningV2DiffScan = "secret-scanning-v2-diff-scan", SecretScanningV2SendNotification = "secret-scanning-v2-notification", CaOrderCertificateForSubscriber = "ca-order-certificate-for-subscriber", - PkiSubscriberDailyAutoRenewal = "pki-subscriber-daily-auto-renewal" + PkiSubscriberDailyAutoRenewal = "pki-subscriber-daily-auto-renewal", + TelemetryAggregatedEvents = "telemetry-aggregated-events" } export type TQueueJobTypes = { @@ -292,6 +294,10 @@ export type TQueueJobTypes = { name: QueueJobs.PkiSubscriberDailyAutoRenewal; payload: undefined; }; + [QueueName.TelemetryAggregatedEvents]: { + name: QueueJobs.TelemetryAggregatedEvents; + payload: undefined; + }; }; const SECRET_SCANNING_JOBS = [ diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 36e9aa0a5..2469bdfb2 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -193,6 +193,8 @@ import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal"; import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; +import { identityTlsCertAuthDALFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-dal"; +import { identityTlsCertAuthServiceFactory } from "@app/services/identity-tls-cert-auth/identity-tls-cert-auth-service"; import { identityTokenAuthDALFactory } from "@app/services/identity-token-auth/identity-token-auth-dal"; import { identityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service"; import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal"; @@ -384,6 +386,7 @@ export const registerRoutes = async ( const identityKubernetesAuthDAL = identityKubernetesAuthDALFactory(db); const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db); const identityAliCloudAuthDAL = identityAliCloudAuthDALFactory(db); + const identityTlsCertAuthDAL = identityTlsCertAuthDALFactory(db); const identityAwsAuthDAL = identityAwsAuthDALFactory(db); const identityGcpAuthDAL = identityGcpAuthDALFactory(db); const identityOciAuthDAL = identityOciAuthDALFactory(db); @@ -684,7 +687,8 @@ export const registerRoutes = async ( const telemetryQueue = telemetryQueueServiceFactory({ keyStore, telemetryDAL, - queueService + queueService, + telemetryService }); const invalidateCacheQueue = invalidateCacheQueueFactory({ @@ -991,8 +995,7 @@ export const registerRoutes = async ( pkiAlertDAL, pkiCollectionDAL, permissionService, - smtpService, - projectDAL + smtpService }); const pkiCollectionService = pkiCollectionServiceFactory({ @@ -1000,8 +1003,7 @@ export const registerRoutes = async ( pkiCollectionItemDAL, certificateAuthorityDAL, certificateDAL, - permissionService, - projectDAL + permissionService }); const projectTemplateService = projectTemplateServiceFactory({ @@ -1185,7 +1187,9 @@ export const registerRoutes = async ( projectEnvDAL, snapshotService, projectDAL, - folderCommitService + folderCommitService, + secretApprovalPolicyService, + secretV2BridgeDAL }); const secretImportService = secretImportServiceFactory({ @@ -1415,7 +1419,8 @@ export const registerRoutes = async ( const identityAccessTokenService = identityAccessTokenServiceFactory({ identityAccessTokenDAL, identityOrgMembershipDAL, - accessTokenQueue + accessTokenQueue, + identityDAL }); const identityProjectService = identityProjectServiceFactory({ @@ -1491,6 +1496,15 @@ export const registerRoutes = async ( permissionService }); + const identityTlsCertAuthService = identityTlsCertAuthServiceFactory({ + identityAccessTokenDAL, + identityTlsCertAuthDAL, + identityOrgMembershipDAL, + licenseService, + permissionService, + kmsService + }); + const identityAwsAuthService = identityAwsAuthServiceFactory({ identityAccessTokenDAL, identityAwsAuthDAL, @@ -1649,8 +1663,7 @@ export const registerRoutes = async ( const cmekService = cmekServiceFactory({ kmsDAL, kmsService, - permissionService, - projectDAL + permissionService }); const externalMigrationQueue = externalMigrationQueueFactory({ @@ -1792,7 +1805,6 @@ export const registerRoutes = async ( const certificateAuthorityService = certificateAuthorityServiceFactory({ certificateAuthorityDAL, - projectDAL, permissionService, appConnectionDAL, appConnectionService, @@ -1802,7 +1814,8 @@ export const registerRoutes = async ( certificateBodyDAL, certificateSecretDAL, kmsService, - pkiSubscriberDAL + pkiSubscriberDAL, + projectDAL }); const internalCaFns = InternalCertificateAuthorityFns({ @@ -1945,6 +1958,7 @@ export const registerRoutes = async ( identityAwsAuth: identityAwsAuthService, identityAzureAuth: identityAzureAuthService, identityOciAuth: identityOciAuthService, + identityTlsCertAuth: identityTlsCertAuthService, identityOidcAuth: identityOidcAuthService, identityJwtAuth: identityJwtAuthService, identityLdapAuth: identityLdapAuthService, diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index ce51b1079..beef663b9 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -251,6 +251,7 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({ name: true, description: true, type: true, + defaultProduct: true, slug: true, autoCapitalization: true, orgId: true, diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index f01f1722c..a21587db1 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -722,6 +722,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.InvalidateCache, + organizationId: req.permission.orgId, distinctId: getTelemetryDistinctId(req), properties: { ...req.auditLogInfo diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index 47594ac87..3d15b473a 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -692,6 +692,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.IssueCert, + organizationId: req.permission.orgId, distinctId: getTelemetryDistinctId(req), properties: { caId: ca.id, @@ -786,6 +787,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SignCert, + organizationId: req.permission.orgId, distinctId: getTelemetryDistinctId(req), properties: { caId: ca.id, diff --git a/backend/src/server/routes/v1/certificate-router.ts b/backend/src/server/routes/v1/certificate-router.ts index 8194b9481..443d64e22 100644 --- a/backend/src/server/routes/v1/certificate-router.ts +++ b/backend/src/server/routes/v1/certificate-router.ts @@ -266,6 +266,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.IssueCert, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { caId: req.body.caId, certificateTemplateId: req.body.certificateTemplateId, @@ -442,6 +443,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SignCert, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { caId: req.body.caId, certificateTemplateId: req.body.certificateTemplateId, diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index 373e2d51f..dae18c23e 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -475,6 +475,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretPulled, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secretCountFromEnv, workspaceId: projectId, @@ -979,6 +980,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretPulled, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secretCount, workspaceId: projectId, @@ -1144,6 +1146,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretPulled, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secretCountForEnv, workspaceId: projectId, @@ -1336,6 +1339,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretPulled, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId: projectId, diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index 2ea70af7a..c0578fc0a 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -85,6 +85,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.MachineIdentityCreated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { orgId: req.body.organizationId, name: identity.name, diff --git a/backend/src/server/routes/v1/identity-tls-cert-auth-router.ts b/backend/src/server/routes/v1/identity-tls-cert-auth-router.ts new file mode 100644 index 000000000..40060ad40 --- /dev/null +++ b/backend/src/server/routes/v1/identity-tls-cert-auth-router.ts @@ -0,0 +1,396 @@ +import crypto from "node:crypto"; + +import { z } from "zod"; + +import { IdentityTlsCertAuthsSchema } from "@app/db/schemas"; +import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ApiDocsTags, TLS_CERT_AUTH } from "@app/lib/api-docs"; +import { getConfig } from "@app/lib/config/env"; +import { BadRequestError } from "@app/lib/errors"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; +import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; +import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns"; + +const validateCommonNames = z + .string() + .min(1) + .trim() + .transform((el) => + el + .split(",") + .map((i) => i.trim()) + .join(",") + ); + +const validateCaCertificate = (caCert: string) => { + if (!caCert) return true; + try { + // eslint-disable-next-line no-new + new crypto.X509Certificate(caCert); + return true; + } catch (err) { + return false; + } +}; + +export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "POST", + url: "/login", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.TlsCertAuth], + description: "Login with TLS Certificate Auth", + body: z.object({ + identityId: z.string().trim().describe(TLS_CERT_AUTH.LOGIN.identityId) + }), + response: { + 200: z.object({ + accessToken: z.string(), + expiresIn: z.coerce.number(), + accessTokenMaxTTL: z.coerce.number(), + tokenType: z.literal("Bearer") + }) + } + }, + handler: async (req) => { + const appCfg = getConfig(); + const clientCertificate = req.headers[appCfg.IDENTITY_TLS_CERT_AUTH_CLIENT_CERTIFICATE_HEADER_KEY]; + if (!clientCertificate) { + throw new BadRequestError({ message: "Missing TLS certificate in header" }); + } + + const { identityTlsCertAuth, accessToken, identityAccessToken, identityMembershipOrg } = + await server.services.identityTlsCertAuth.login({ + identityId: req.body.identityId, + clientCertificate: clientCertificate as string + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: identityMembershipOrg?.orgId, + event: { + type: EventType.LOGIN_IDENTITY_TLS_CERT_AUTH, + metadata: { + identityId: identityTlsCertAuth.identityId, + identityAccessTokenId: identityAccessToken.id, + identityTlsCertAuthId: identityTlsCertAuth.id + } + } + }); + + return { + accessToken, + tokenType: "Bearer" as const, + expiresIn: identityTlsCertAuth.accessTokenTTL, + accessTokenMaxTTL: identityTlsCertAuth.accessTokenMaxTTL + }; + } + }); + + server.route({ + method: "POST", + url: "/identities/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.TlsCertAuth], + description: "Attach TLS Certificate Auth configuration onto identity", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().trim().describe(TLS_CERT_AUTH.ATTACH.identityId) + }), + body: z + .object({ + allowedCommonNames: validateCommonNames + .optional() + .nullable() + .describe(TLS_CERT_AUTH.ATTACH.allowedCommonNames), + caCertificate: z + .string() + .min(1) + .max(10240) + .refine(validateCaCertificate, "Invalid CA Certificate.") + .describe(TLS_CERT_AUTH.ATTACH.caCertificate), + accessTokenTrustedIps: z + .object({ + ipAddress: z.string().trim() + }) + .array() + .min(1) + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(TLS_CERT_AUTH.ATTACH.accessTokenTrustedIps), + accessTokenTTL: z + .number() + .int() + .min(0) + .max(315360000) + .default(2592000) + .describe(TLS_CERT_AUTH.ATTACH.accessTokenTTL), + accessTokenMaxTTL: z + .number() + .int() + .min(1) + .max(315360000) + .default(2592000) + .describe(TLS_CERT_AUTH.ATTACH.accessTokenMaxTTL), + accessTokenNumUsesLimit: z + .number() + .int() + .min(0) + .default(0) + .describe(TLS_CERT_AUTH.ATTACH.accessTokenNumUsesLimit) + }) + .refine( + (val) => val.accessTokenTTL <= val.accessTokenMaxTTL, + "Access Token TTL cannot be greater than Access Token Max TTL." + ), + response: { + 200: z.object({ + identityTlsCertAuth: IdentityTlsCertAuthsSchema + }) + } + }, + handler: async (req) => { + const identityTlsCertAuth = await server.services.identityTlsCertAuth.attachTlsCertAuth({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + identityId: req.params.identityId, + isActorSuperAdmin: isSuperAdmin(req.auth) + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.ADD_IDENTITY_TLS_CERT_AUTH, + metadata: { + identityId: identityTlsCertAuth.identityId, + allowedCommonNames: identityTlsCertAuth.allowedCommonNames, + accessTokenTTL: identityTlsCertAuth.accessTokenTTL, + accessTokenMaxTTL: identityTlsCertAuth.accessTokenMaxTTL, + accessTokenTrustedIps: identityTlsCertAuth.accessTokenTrustedIps as TIdentityTrustedIp[], + accessTokenNumUsesLimit: identityTlsCertAuth.accessTokenNumUsesLimit + } + } + }); + + return { identityTlsCertAuth }; + } + }); + + server.route({ + method: "PATCH", + url: "/identities/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.TlsCertAuth], + description: "Update TLS Certificate Auth configuration on identity", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().describe(TLS_CERT_AUTH.UPDATE.identityId) + }), + body: z + .object({ + caCertificate: z + .string() + .min(1) + .max(10240) + .refine(validateCaCertificate, "Invalid CA Certificate.") + .optional() + .describe(TLS_CERT_AUTH.UPDATE.caCertificate), + allowedCommonNames: validateCommonNames + .optional() + .nullable() + .describe(TLS_CERT_AUTH.UPDATE.allowedCommonNames), + accessTokenTrustedIps: z + .object({ + ipAddress: z.string().trim() + }) + .array() + .min(1) + .optional() + .describe(TLS_CERT_AUTH.UPDATE.accessTokenTrustedIps), + accessTokenTTL: z + .number() + .int() + .min(0) + .max(315360000) + .optional() + .describe(TLS_CERT_AUTH.UPDATE.accessTokenTTL), + accessTokenNumUsesLimit: z + .number() + .int() + .min(0) + .optional() + .describe(TLS_CERT_AUTH.UPDATE.accessTokenNumUsesLimit), + accessTokenMaxTTL: z + .number() + .int() + .max(315360000) + .min(0) + .optional() + .describe(TLS_CERT_AUTH.UPDATE.accessTokenMaxTTL) + }) + .refine( + (val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true), + "Access Token TTL cannot be greater than Access Token Max TTL." + ), + response: { + 200: z.object({ + identityTlsCertAuth: IdentityTlsCertAuthsSchema + }) + } + }, + handler: async (req) => { + const identityTlsCertAuth = await server.services.identityTlsCertAuth.updateTlsCertAuth({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.body, + identityId: req.params.identityId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.UPDATE_IDENTITY_TLS_CERT_AUTH, + metadata: { + identityId: identityTlsCertAuth.identityId, + allowedCommonNames: identityTlsCertAuth.allowedCommonNames, + accessTokenTTL: identityTlsCertAuth.accessTokenTTL, + accessTokenMaxTTL: identityTlsCertAuth.accessTokenMaxTTL, + accessTokenTrustedIps: identityTlsCertAuth.accessTokenTrustedIps as TIdentityTrustedIp[], + accessTokenNumUsesLimit: identityTlsCertAuth.accessTokenNumUsesLimit + } + } + }); + + return { identityTlsCertAuth }; + } + }); + + server.route({ + method: "GET", + url: "/identities/:identityId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.TlsCertAuth], + description: "Retrieve TLS Certificate Auth configuration on identity", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().describe(TLS_CERT_AUTH.RETRIEVE.identityId) + }), + response: { + 200: z.object({ + identityTlsCertAuth: IdentityTlsCertAuthsSchema.extend({ + caCertificate: z.string() + }) + }) + } + }, + handler: async (req) => { + const identityTlsCertAuth = await server.services.identityTlsCertAuth.getTlsCertAuth({ + identityId: req.params.identityId, + actor: req.permission.type, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.GET_IDENTITY_TLS_CERT_AUTH, + metadata: { + identityId: identityTlsCertAuth.identityId + } + } + }); + return { identityTlsCertAuth }; + } + }); + + server.route({ + method: "DELETE", + url: "/identities/:identityId", + config: { + rateLimit: writeLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.TlsCertAuth], + description: "Delete TLS Certificate Auth configuration on identity", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + identityId: z.string().describe(TLS_CERT_AUTH.REVOKE.identityId) + }), + response: { + 200: z.object({ + identityTlsCertAuth: IdentityTlsCertAuthsSchema + }) + } + }, + handler: async (req) => { + const identityTlsCertAuth = await server.services.identityTlsCertAuth.revokeTlsCertAuth({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + identityId: req.params.identityId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.REVOKE_IDENTITY_TLS_CERT_AUTH, + metadata: { + identityId: identityTlsCertAuth.identityId + } + } + }); + + return { identityTlsCertAuth }; + } + }); +}; diff --git a/backend/src/server/routes/v1/index.ts b/backend/src/server/routes/v1/index.ts index 2363147b6..e6aa2e83f 100644 --- a/backend/src/server/routes/v1/index.ts +++ b/backend/src/server/routes/v1/index.ts @@ -25,6 +25,7 @@ import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router"; import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router"; import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router"; import { registerIdentityRouter } from "./identity-router"; +import { registerIdentityTlsCertAuthRouter } from "./identity-tls-cert-auth-router"; import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router"; import { registerIdentityUaRouter } from "./identity-universal-auth-router"; import { registerIntegrationAuthRouter } from "./integration-auth-router"; @@ -66,6 +67,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => { await authRouter.register(registerIdentityAccessTokenRouter); await authRouter.register(registerIdentityAliCloudAuthRouter); await authRouter.register(registerIdentityAwsAuthRouter); + await authRouter.register(registerIdentityTlsCertAuthRouter, { prefix: "/tls-cert-auth" }); await authRouter.register(registerIdentityAzureAuthRouter); await authRouter.register(registerIdentityOciAuthRouter); await authRouter.register(registerIdentityOidcAuthRouter); diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index f3964e7b7..95477c341 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -103,6 +103,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.IntegrationCreated, + organizationId: req.permission.orgId, distinctId: getTelemetryDistinctId(req), properties: { ...createIntegrationEventProperty, diff --git a/backend/src/server/routes/v1/invite-org-router.ts b/backend/src/server/routes/v1/invite-org-router.ts index b688dc042..b98e94be0 100644 --- a/backend/src/server/routes/v1/invite-org-router.ts +++ b/backend/src/server/routes/v1/invite-org-router.ts @@ -64,6 +64,7 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.UserOrgInvitation, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { inviteeEmails: req.body.inviteeEmails, organizationRoleSlug: req.body.organizationRoleSlug, diff --git a/backend/src/server/routes/v1/pki-subscriber-router.ts b/backend/src/server/routes/v1/pki-subscriber-router.ts index 761904fd1..0e9ec6e0c 100644 --- a/backend/src/server/routes/v1/pki-subscriber-router.ts +++ b/backend/src/server/routes/v1/pki-subscriber-router.ts @@ -331,6 +331,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.IssueCert, + organizationId: req.permission.orgId, distinctId: getTelemetryDistinctId(req), properties: { subscriberId: subscriber.id, @@ -399,6 +400,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.IssueCert, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { subscriberId: subscriber.id, commonName: subscriber.commonName, @@ -471,6 +473,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SignCert, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { subscriberId: subscriber.id, commonName: subscriber.commonName, diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index cc94adede..2015842a5 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -158,17 +158,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { includeRoles: z .enum(["true", "false"]) .default("false") - .transform((value) => value === "true"), - type: z - .enum([ - ProjectType.SecretManager, - ProjectType.KMS, - ProjectType.CertificateManager, - ProjectType.SSH, - ProjectType.SecretScanning, - "all" - ]) - .optional() + .transform((value) => value === "true") }), response: { 200: z.object({ @@ -187,8 +177,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, actor: req.permission.type, - actorOrgId: req.permission.orgId, - type: req.query.type + actorOrgId: req.permission.orgId }); return { workspaces }; } @@ -377,7 +366,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { .optional() .describe(PROJECTS.UPDATE.slug), secretSharing: z.boolean().optional().describe(PROJECTS.UPDATE.secretSharing), - showSnapshotsLegacy: z.boolean().optional().describe(PROJECTS.UPDATE.showSnapshotsLegacy) + showSnapshotsLegacy: z.boolean().optional().describe(PROJECTS.UPDATE.showSnapshotsLegacy), + defaultProduct: z.nativeEnum(ProjectType).optional().describe(PROJECTS.UPDATE.defaultProduct) }), response: { 200: z.object({ @@ -396,6 +386,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { name: req.body.name, description: req.body.description, autoCapitalization: req.body.autoCapitalization, + defaultProduct: req.body.defaultProduct, hasDeleteProtection: req.body.hasDeleteProtection, slug: req.body.slug, secretSharing: req.body.secretSharing, @@ -1059,7 +1050,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { body: z.object({ limit: z.number().default(100), offset: z.number().default(0), - type: z.nativeEnum(ProjectType).optional(), orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME), orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC), name: z diff --git a/backend/src/server/routes/v1/secret-requests-router.ts b/backend/src/server/routes/v1/secret-requests-router.ts index a1e4eafc2..1bd044d64 100644 --- a/backend/src/server/routes/v1/secret-requests-router.ts +++ b/backend/src/server/routes/v1/secret-requests-router.ts @@ -165,6 +165,7 @@ export const registerSecretRequestsRouter = async (server: FastifyZodProvider) = await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretRequestDeleted, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { secretRequestId: req.params.id, organizationId: req.permission.orgId, @@ -256,6 +257,7 @@ export const registerSecretRequestsRouter = async (server: FastifyZodProvider) = await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretRequestCreated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { secretRequestId: shareRequest.id, organizationId: req.permission.orgId, diff --git a/backend/src/server/routes/v2/organization-router.ts b/backend/src/server/routes/v2/organization-router.ts index 504359726..fd60316db 100644 --- a/backend/src/server/routes/v2/organization-router.ts +++ b/backend/src/server/routes/v2/organization-router.ts @@ -4,7 +4,6 @@ import { OrgMembershipsSchema, ProjectMembershipsSchema, ProjectsSchema, - ProjectType, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas"; @@ -85,9 +84,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { params: z.object({ organizationId: z.string().trim().describe(ORGANIZATIONS.GET_PROJECTS.organizationId) }), - querystring: z.object({ - type: z.nativeEnum(ProjectType).optional().describe(ORGANIZATIONS.GET_PROJECTS.type) - }), response: { 200: z.object({ workspaces: z @@ -114,8 +110,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod, - orgId: req.params.organizationId, - type: req.query.type + orgId: req.params.organizationId }); return { workspaces }; diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index d14a75ded..2a883dcb6 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -198,6 +198,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.ProjectCreated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { orgId: project.orgId, name: project.name, @@ -456,6 +457,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { rateLimit: readLimit }, schema: { + hide: false, + tags: [ApiDocsTags.PkiAlerting], params: z.object({ projectId: z.string().trim() }), @@ -486,6 +489,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { rateLimit: readLimit }, schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificateCollections], params: z.object({ projectId: z.string().trim() }), @@ -548,6 +553,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { rateLimit: readLimit }, schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificateTemplates], params: z.object({ projectId: z.string().trim() }), diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 33878f1e7..8784989bd 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -333,6 +333,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretPulled, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId, @@ -489,6 +490,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretPulled, + organizationId: req.permission.orgId, distinctId: getTelemetryDistinctId(req), properties: { numberOfSecrets: 1, @@ -615,6 +617,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretCreated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, workspaceId: projectId, @@ -750,6 +753,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretUpdated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, workspaceId: projectId, @@ -850,6 +854,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretDeleted, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, workspaceId: projectId, @@ -957,6 +962,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretPulled, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId: req.query.workspaceId, @@ -1036,6 +1042,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretPulled, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, workspaceId: req.query.workspaceId, @@ -1207,6 +1214,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretCreated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, workspaceId: req.body.workspaceId, @@ -1396,6 +1404,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretUpdated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, workspaceId: req.body.workspaceId, @@ -1519,6 +1528,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretDeleted, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: 1, workspaceId: req.body.workspaceId, @@ -1702,6 +1712,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretCreated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId: req.body.workspaceId, @@ -1828,6 +1839,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretUpdated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId: req.body.workspaceId, @@ -1946,6 +1958,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretDeleted, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId: req.body.workspaceId, @@ -2054,6 +2067,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretCreated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId: secrets[0].workspace, @@ -2209,6 +2223,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretUpdated, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId: secrets[0].workspace, @@ -2307,6 +2322,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { await server.services.telemetry.sendPostHogEvents({ event: PostHogEventTypes.SecretDeleted, distinctId: getTelemetryDistinctId(req), + organizationId: req.permission.orgId, properties: { numberOfSecrets: secrets.length, workspaceId: secrets[0].workspace, diff --git a/backend/src/services/app-connection/gcp/gcp-connection-service.ts b/backend/src/services/app-connection/gcp/gcp-connection-service.ts index 74f2ab2c4..4f2337302 100644 --- a/backend/src/services/app-connection/gcp/gcp-connection-service.ts +++ b/backend/src/services/app-connection/gcp/gcp-connection-service.ts @@ -1,3 +1,4 @@ +import { logger } from "@app/lib/logger"; import { OrgServiceActor } from "@app/lib/types"; import { AppConnection } from "../app-connection-enums"; @@ -19,6 +20,7 @@ export const gcpConnectionService = (getAppConnection: TGetAppConnectionFunc) => return projects; } catch (error) { + logger.error(error, "Error listing GCP secret manager projects"); return []; } }; diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index 8a2aa0a5d..0f30e91c3 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -1,6 +1,6 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, ProjectType, TableName } from "@app/db/schemas"; +import { TableName } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; @@ -50,10 +50,7 @@ type TCertificateAuthorityServiceFactoryDep = { >; externalCertificateAuthorityDAL: Pick; internalCertificateAuthorityService: TInternalCertificateAuthorityServiceFactory; - projectDAL: Pick< - TProjectDALFactory, - "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction" | "getProjectFromSplitId" - >; + projectDAL: Pick; permissionService: Pick; certificateDAL: Pick; certificateBodyDAL: Pick; @@ -98,23 +95,12 @@ export const certificateAuthorityServiceFactory = ({ { type, projectId, name, enableDirectIssuance, configuration, status }: TCreateCertificateAuthorityDTO, actor: OrgServiceActor ) => { - let finalProjectId: string = projectId; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - - if (certManagerProjectFromSplit) { - finalProjectId = certManagerProjectFromSplit.id; - } - const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, - projectId: finalProjectId, + projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -126,7 +112,7 @@ export const certificateAuthorityServiceFactory = ({ const ca = await internalCertificateAuthorityService.createCa({ ...(configuration as TCreateInternalCertificateAuthorityDTO["configuration"]), isInternal: true, - projectId: finalProjectId, + projectId, enableDirectIssuance, name }); @@ -142,7 +128,7 @@ export const certificateAuthorityServiceFactory = ({ type, enableDirectIssuance: ca.enableDirectIssuance, name: ca.name, - projectId: finalProjectId, + projectId, status, configuration: ca.internalCa } as TCertificateAuthority; @@ -151,7 +137,7 @@ export const certificateAuthorityServiceFactory = ({ if (type === CaType.ACME) { return acmeFns.createCertificateAuthority({ name, - projectId: finalProjectId, + projectId, configuration: configuration as TCreateAcmeCertificateAuthorityDTO["configuration"], enableDirectIssuance, status, @@ -181,8 +167,7 @@ export const certificateAuthorityServiceFactory = ({ actorId: actor.id, projectId: certificateAuthority.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -225,23 +210,12 @@ export const certificateAuthorityServiceFactory = ({ { projectId, type }: { projectId: string; type: CaType }, actor: OrgServiceActor ) => { - let finalProjectId: string = projectId; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - - if (certManagerProjectFromSplit) { - finalProjectId = certManagerProjectFromSplit.id; - } - const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, - projectId: finalProjectId, + projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -251,7 +225,7 @@ export const certificateAuthorityServiceFactory = ({ if (type === CaType.INTERNAL) { const cas = await certificateAuthorityDAL.findWithAssociatedCa({ - [`${TableName.CertificateAuthority}.projectId` as "projectId"]: finalProjectId, + [`${TableName.CertificateAuthority}.projectId` as "projectId"]: projectId, $notNull: [`${TableName.InternalCertificateAuthority}.id` as "id"] }); @@ -269,7 +243,7 @@ export const certificateAuthorityServiceFactory = ({ } if (type === CaType.ACME) { - return acmeFns.listCertificateAuthorities({ projectId: finalProjectId }); + return acmeFns.listCertificateAuthorities({ projectId }); } throw new BadRequestError({ message: "Invalid certificate authority type" }); @@ -294,8 +268,7 @@ export const certificateAuthorityServiceFactory = ({ actorId: actor.id, projectId: certificateAuthority.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -368,8 +341,7 @@ export const certificateAuthorityServiceFactory = ({ actorId: actor.id, projectId: certificateAuthority.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts index 98d9491f7..a6a5b9f54 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts @@ -5,13 +5,7 @@ import slugify from "@sindresorhus/slugify"; import crypto, { KeyObject } from "crypto"; import { z } from "zod"; -import { - ActionProjectType, - ProjectType, - TableName, - TCertificateAuthorities, - TCertificateTemplates -} from "@app/db/schemas"; +import { TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, @@ -105,10 +99,7 @@ type TInternalCertificateAuthorityServiceFactoryDep = { certificateBodyDAL: Pick; pkiCollectionDAL: Pick; pkiCollectionItemDAL: Pick; - projectDAL: Pick< - TProjectDALFactory, - "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction" | "getProjectFromSplitId" - >; + projectDAL: Pick; kmsService: Pick; permissionService: Pick; }; @@ -154,21 +145,12 @@ export const internalCertificateAuthorityServiceFactory = ({ if (!project) throw new NotFoundError({ message: `Project with slug '${dto.projectSlug}' not found` }); projectId = project.id; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } - const { permission } = await permissionService.getProjectPermission({ actor: dto.actor, actorId: dto.actorId, projectId, actorAuthMethod: dto.actorAuthMethod, - actorOrgId: dto.actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: dto.actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -351,8 +333,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, @@ -376,8 +357,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId: dto.actorId, projectId: ca.projectId, actorAuthMethod: dto.actorAuthMethod, - actorOrgId: dto.actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: dto.actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -409,8 +389,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -435,8 +414,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -499,8 +477,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -786,8 +763,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -823,8 +799,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -904,8 +879,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -1052,8 +1026,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -1224,8 +1197,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -1581,8 +1553,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId: dto.actorId, projectId: ca.projectId, actorAuthMethod: dto.actorAuthMethod, - actorOrgId: dto.actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: dto.actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -1949,8 +1920,7 @@ export const internalCertificateAuthorityServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); const certificateTemplates = await certificateTemplateDAL.find({ caId }); diff --git a/backend/src/services/certificate-template/certificate-template-service.ts b/backend/src/services/certificate-template/certificate-template-service.ts index 4fa4e8283..f22926cb2 100644 --- a/backend/src/services/certificate-template/certificate-template-service.ts +++ b/backend/src/services/certificate-template/certificate-template-service.ts @@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import bcrypt from "bcrypt"; -import { ActionProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas"; +import { TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { @@ -76,8 +76,7 @@ export const certificateTemplateServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -138,8 +137,7 @@ export const certificateTemplateServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -203,8 +201,7 @@ export const certificateTemplateServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -230,8 +227,7 @@ export const certificateTemplateServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -272,8 +268,7 @@ export const certificateTemplateServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -355,8 +350,7 @@ export const certificateTemplateServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -435,8 +429,7 @@ export const certificateTemplateServiceFactory = ({ actorId: dto.actorId, projectId: certTemplate.projectId, actorAuthMethod: dto.actorAuthMethod, - actorOrgId: dto.actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId: dto.actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/services/certificate/certificate-service.ts b/backend/src/services/certificate/certificate-service.ts index 5558ccd65..202c89615 100644 --- a/backend/src/services/certificate/certificate-service.ts +++ b/backend/src/services/certificate/certificate-service.ts @@ -2,7 +2,6 @@ import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import { createPrivateKey, createPublicKey, sign, verify } from "crypto"; -import { ActionProjectType, ProjectType } from "@app/db/schemas"; import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { @@ -48,10 +47,7 @@ type TCertificateServiceFactoryDep = { certificateAuthoritySecretDAL: Pick; pkiCollectionDAL: Pick; pkiCollectionItemDAL: Pick; - projectDAL: Pick< - TProjectDALFactory, - "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction" | "getProjectFromSplitId" - >; + projectDAL: Pick; kmsService: Pick; permissionService: Pick; }; @@ -83,8 +79,7 @@ export const certificateServiceFactory = ({ actorId, projectId: cert.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -114,8 +109,7 @@ export const certificateServiceFactory = ({ actorId, projectId: cert.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -148,8 +142,7 @@ export const certificateServiceFactory = ({ actorId, projectId: cert.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -198,8 +191,7 @@ export const certificateServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -247,8 +239,7 @@ export const certificateServiceFactory = ({ actorId, projectId: cert.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -321,23 +312,14 @@ export const certificateServiceFactory = ({ const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); - let projectId = project.id; - - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } + const projectId = project.id; const { permission } = await permissionService.getProjectPermission({ actor, actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -541,8 +523,7 @@ export const certificateServiceFactory = ({ actorId, projectId: cert.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/services/cmek/cmek-service.ts b/backend/src/services/cmek/cmek-service.ts index fd6cbf39d..7817266b3 100644 --- a/backend/src/services/cmek/cmek-service.ts +++ b/backend/src/services/cmek/cmek-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, ProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionCmekActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { SigningAlgorithm } from "@app/lib/crypto/sign"; @@ -23,32 +22,23 @@ import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsKeyUsage } from "../kms/kms-types"; -import { TProjectDALFactory } from "../project/project-dal"; type TCmekServiceFactoryDep = { kmsService: TKmsServiceFactory; kmsDAL: TKmsKeyDALFactory; permissionService: TPermissionServiceFactory; - projectDAL: Pick; }; export type TCmekServiceFactory = ReturnType; -export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, projectDAL }: TCmekServiceFactoryDep) => { - const createCmek = async ({ projectId: preSplitProjectId, ...dto }: TCreateCmekDTO, actor: OrgServiceActor) => { - let projectId = preSplitProjectId; - const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS); - if (cmekProjectFromSplit) { - projectId = cmekProjectFromSplit.id; - } - +export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TCmekServiceFactoryDep) => { + const createCmek = async ({ projectId, ...dto }: TCreateCmekDTO, actor: OrgServiceActor) => { const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Create, ProjectPermissionSub.Cmek); @@ -87,8 +77,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Edit, ProjectPermissionSub.Cmek); @@ -124,8 +113,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Delete, ProjectPermissionSub.Cmek); @@ -135,23 +123,13 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj return key; }; - const listCmeksByProjectId = async ( - { projectId: preSplitProjectId, ...filters }: TListCmeksByProjectIdDTO, - actor: OrgServiceActor - ) => { - let projectId = preSplitProjectId; - const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(preSplitProjectId, ProjectType.KMS); - if (cmekProjectFromSplit) { - projectId = cmekProjectFromSplit.id; - } - + const listCmeksByProjectId = async ({ projectId, ...filters }: TListCmeksByProjectIdDTO, actor: OrgServiceActor) => { const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek); @@ -173,8 +151,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek); @@ -195,8 +172,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek); @@ -218,8 +194,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Encrypt, ProjectPermissionSub.Cmek); @@ -246,8 +221,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek); @@ -294,8 +268,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek); @@ -318,8 +291,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Sign, ProjectPermissionSub.Cmek); @@ -353,8 +325,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Verify, ProjectPermissionSub.Cmek); @@ -389,8 +360,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj actorId: actor.id, projectId: key.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.KMS + actorOrgId: actor.orgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Decrypt, ProjectPermissionSub.Cmek); diff --git a/backend/src/services/folder-commit/folder-commit-service.test.ts b/backend/src/services/folder-commit/folder-commit-service.test.ts index 1879cf493..28d603829 100644 --- a/backend/src/services/folder-commit/folder-commit-service.test.ts +++ b/backend/src/services/folder-commit/folder-commit-service.test.ts @@ -4,7 +4,7 @@ import { Knex } from "knex"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { ProjectType, TSecretFolderVersions, TSecretVersionsV2 } from "@app/db/schemas"; +import { TSecretFolderVersions, TSecretVersionsV2 } from "@app/db/schemas"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { ActorType } from "../auth/auth-type"; @@ -433,8 +433,7 @@ describe("folderCommitServiceFactory", () => { mockFolderCommitDAL.findCommitsToRecreate.mockResolvedValue([]); mockProjectDAL.findProjectByEnvId.mockResolvedValue({ id: "project-id", - name: "test-project", - type: ProjectType.SecretManager + name: "test-project" }); // Act diff --git a/backend/src/services/folder-commit/folder-commit-service.ts b/backend/src/services/folder-commit/folder-commit-service.ts index 35f032312..3576f444b 100644 --- a/backend/src/services/folder-commit/folder-commit-service.ts +++ b/backend/src/services/folder-commit/folder-commit-service.ts @@ -2,13 +2,7 @@ import { ForbiddenError } from "@casl/ability"; import { Knex } from "knex"; -import { - ActionProjectType, - TSecretFolders, - TSecretFolderVersions, - TSecretV2TagJunctionInsert, - TSecretVersionsV2 -} from "@app/db/schemas"; +import { TSecretFolders, TSecretFolderVersions, TSecretV2TagJunctionInsert, TSecretVersionsV2 } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; @@ -223,8 +217,7 @@ export const folderCommitServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCommitsActions.Read, ProjectPermissionSub.Commits); @@ -2067,8 +2060,7 @@ export const folderCommitServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/services/group-project/group-project-service.ts b/backend/src/services/group-project/group-project-service.ts index 47d8950cc..96fda42e3 100644 --- a/backend/src/services/group-project/group-project-service.ts +++ b/backend/src/services/group-project/group-project-service.ts @@ -1,6 +1,6 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas"; +import { ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas"; import { TListProjectGroupUsersDTO } from "@app/ee/services/group/group-types"; import { constructPermissionErrorMessage, @@ -79,8 +79,7 @@ export const groupProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Create, ProjectPermissionSub.Groups); @@ -267,8 +266,7 @@ export const groupProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Edit, ProjectPermissionSub.Groups); @@ -381,8 +379,7 @@ export const groupProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Delete, ProjectPermissionSub.Groups); @@ -426,8 +423,7 @@ export const groupProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups); @@ -454,8 +450,7 @@ export const groupProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups); @@ -496,8 +491,7 @@ export const groupProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups); diff --git a/backend/src/services/identity-access-token/identity-access-token-dal.ts b/backend/src/services/identity-access-token/identity-access-token-dal.ts index 879ca9fd3..8f65d8555 100644 --- a/backend/src/services/identity-access-token/identity-access-token-dal.ts +++ b/backend/src/services/identity-access-token/identity-access-token-dal.ts @@ -17,70 +17,11 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => { const doc = await (tx || db.replicaNode())(TableName.IdentityAccessToken) .where(filter) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`) - .leftJoin( - TableName.IdentityUaClientSecret, - `${TableName.IdentityAccessToken}.identityUAClientSecretId`, - `${TableName.IdentityUaClientSecret}.id` - ) - .leftJoin( - TableName.IdentityUniversalAuth, - `${TableName.IdentityUaClientSecret}.identityUAId`, - `${TableName.IdentityUniversalAuth}.id` - ) - .leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`) - .leftJoin( - TableName.IdentityAliCloudAuth, - `${TableName.Identity}.id`, - `${TableName.IdentityAliCloudAuth}.identityId` - ) - .leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`) - .leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`) - .leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`) - .leftJoin( - TableName.IdentityKubernetesAuth, - `${TableName.Identity}.id`, - `${TableName.IdentityKubernetesAuth}.identityId` - ) - .leftJoin(TableName.IdentityOciAuth, `${TableName.Identity}.id`, `${TableName.IdentityOciAuth}.identityId`) - .leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`) - .leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`) - .leftJoin(TableName.IdentityJwtAuth, `${TableName.Identity}.id`, `${TableName.IdentityJwtAuth}.identityId`) .select(selectAllTableCols(TableName.IdentityAccessToken)) - .select( - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityGcpAuth).as("accessTokenTrustedIpsGcp"), - db - .ref("accessTokenTrustedIps") - .withSchema(TableName.IdentityAliCloudAuth) - .as("accessTokenTrustedIpsAliCloud"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAwsAuth).as("accessTokenTrustedIpsAws"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAzureAuth).as("accessTokenTrustedIpsAzure"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOciAuth).as("accessTokenTrustedIpsOci"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityJwtAuth).as("accessTokenTrustedIpsJwt"), - db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityLdapAuth).as("accessTokenTrustedIpsLdap"), - db.ref("name").withSchema(TableName.Identity) - ) + .select(db.ref("name").withSchema(TableName.Identity)) .first(); - if (!doc) return; - - return { - ...doc, - trustedIpsUniversalAuth: doc.accessTokenTrustedIpsUa, - trustedIpsGcpAuth: doc.accessTokenTrustedIpsGcp, - trustedIpsAliCloudAuth: doc.accessTokenTrustedIpsAliCloud, - trustedIpsAwsAuth: doc.accessTokenTrustedIpsAws, - trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure, - trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s, - trustedIpsOciAuth: doc.accessTokenTrustedIpsOci, - trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc, - trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken, - trustedIpsAccessJwtAuth: doc.accessTokenTrustedIpsJwt, - trustedIpsAccessLdapAuth: doc.accessTokenTrustedIpsLdap - }; + return doc; } catch (error) { throw new DatabaseError({ error, name: "IdAccessTokenFindOne" }); } diff --git a/backend/src/services/identity-access-token/identity-access-token-service.ts b/backend/src/services/identity-access-token/identity-access-token-service.ts index 7c8944f50..0937b9640 100644 --- a/backend/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend/src/services/identity-access-token/identity-access-token-service.ts @@ -7,12 +7,14 @@ import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip"; import { TAccessTokenQueueServiceFactory } from "../access-token-queue/access-token-queue"; import { AuthTokenType } from "../auth/auth-type"; +import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "./identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload, TRenewAccessTokenDTO } from "./identity-access-token-types"; type TIdentityAccessTokenServiceFactoryDep = { identityAccessTokenDAL: TIdentityAccessTokenDALFactory; + identityDAL: Pick; identityOrgMembershipDAL: TIdentityOrgDALFactory; accessTokenQueue: Pick< TAccessTokenQueueServiceFactory, @@ -25,7 +27,8 @@ export type TIdentityAccessTokenServiceFactory = ReturnType { const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => { const { @@ -190,23 +193,11 @@ export const identityAccessTokenServiceFactory = ({ message: "Failed to authorize revoked access token, access token is revoked" }); - const trustedIpsMap: Record = { - [IdentityAuthMethod.UNIVERSAL_AUTH]: identityAccessToken.trustedIpsUniversalAuth, - [IdentityAuthMethod.GCP_AUTH]: identityAccessToken.trustedIpsGcpAuth, - [IdentityAuthMethod.ALICLOUD_AUTH]: identityAccessToken.trustedIpsAliCloudAuth, - [IdentityAuthMethod.AWS_AUTH]: identityAccessToken.trustedIpsAwsAuth, - [IdentityAuthMethod.OCI_AUTH]: identityAccessToken.trustedIpsOciAuth, - [IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth, - [IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth, - [IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth, - [IdentityAuthMethod.TOKEN_AUTH]: identityAccessToken.trustedIpsAccessTokenAuth, - [IdentityAuthMethod.JWT_AUTH]: identityAccessToken.trustedIpsAccessJwtAuth, - [IdentityAuthMethod.LDAP_AUTH]: identityAccessToken.trustedIpsAccessLdapAuth - }; - - const trustedIps = trustedIpsMap[identityAccessToken.authMethod as IdentityAuthMethod]; - - if (ipAddress) { + const trustedIps = await identityDAL.getTrustedIpsByAuthMethod( + identityAccessToken.identityId, + identityAccessToken.authMethod as IdentityAuthMethod + ); + if (ipAddress && trustedIps) { checkIPAgainstBlocklist({ ipAddress, trustedIps: trustedIps as TIp[] diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index 81387d141..f354477cf 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -1,6 +1,6 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas"; +import { ProjectMembershipRole } from "@app/db/schemas"; import { constructPermissionErrorMessage, validatePrivilegeChangeOperation @@ -62,8 +62,7 @@ export const identityProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Create, @@ -180,8 +179,7 @@ export const identityProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Edit, @@ -291,8 +289,7 @@ export const identityProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Delete, @@ -320,8 +317,7 @@ export const identityProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionIdentityActions.Read, @@ -354,8 +350,7 @@ export const identityProjectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -391,8 +386,7 @@ export const identityProjectServiceFactory = ({ actorId, projectId: membership.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-dal.ts b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-dal.ts new file mode 100644 index 000000000..951077f33 --- /dev/null +++ b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-dal.ts @@ -0,0 +1,10 @@ +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { ormify, TOrmify } from "@app/lib/knex"; + +export type TIdentityTlsCertAuthDALFactory = TOrmify; + +export const identityTlsCertAuthDALFactory = (db: TDbClient) => { + const orm = ormify(db, TableName.IdentityTlsCertAuth); + return orm; +}; diff --git a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts new file mode 100644 index 000000000..11dd312ad --- /dev/null +++ b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts @@ -0,0 +1,423 @@ +import crypto from "node:crypto"; + +import { ForbiddenError } from "@casl/ability"; +import jwt from "jsonwebtoken"; + +import { IdentityAuthMethod } from "@app/db/schemas"; +import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { + constructPermissionErrorMessage, + validatePrivilegeChangeOperation +} from "@app/ee/services/permission/permission-fns"; +import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; +import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate"; +import { getConfig } from "@app/lib/config/env"; +import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; + +import { ActorType, AuthTokenType } from "../auth/auth-type"; +import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; +import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; +import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; +import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns"; +import { TIdentityTlsCertAuthDALFactory } from "./identity-tls-cert-auth-dal"; +import { TIdentityTlsCertAuthServiceFactory } from "./identity-tls-cert-auth-types"; + +type TIdentityTlsCertAuthServiceFactoryDep = { + identityAccessTokenDAL: Pick; + identityTlsCertAuthDAL: Pick< + TIdentityTlsCertAuthDALFactory, + "findOne" | "transaction" | "create" | "updateById" | "delete" + >; + identityOrgMembershipDAL: Pick; + licenseService: Pick; + permissionService: Pick; + kmsService: Pick; +}; + +const parseSubjectDetails = (data: string) => { + const values: Record = {}; + data.split("\n").forEach((el) => { + const [key, value] = el.split("="); + values[key.trim()] = value.trim(); + }); + return values; +}; + +export const identityTlsCertAuthServiceFactory = ({ + identityAccessTokenDAL, + identityTlsCertAuthDAL, + identityOrgMembershipDAL, + licenseService, + permissionService, + kmsService +}: TIdentityTlsCertAuthServiceFactoryDep): TIdentityTlsCertAuthServiceFactory => { + const login: TIdentityTlsCertAuthServiceFactory["login"] = async ({ identityId, clientCertificate }) => { + const identityTlsCertAuth = await identityTlsCertAuthDAL.findOne({ identityId }); + if (!identityTlsCertAuth) { + throw new NotFoundError({ + message: "TLS Certificate auth method not found for identity, did you configure TLS Certificate auth?" + }); + } + + const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ + identityId: identityTlsCertAuth.identityId + }); + + if (!identityMembershipOrg) { + throw new NotFoundError({ + message: `Identity organization membership for identity with ID '${identityTlsCertAuth.identityId}' not found` + }); + } + + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId + }); + + const caCertificate = decryptor({ + cipherTextBlob: identityTlsCertAuth.encryptedCaCertificate + }).toString(); + + const leafCertificate = extractX509CertFromChain(decodeURIComponent(clientCertificate))?.[0]; + if (!leafCertificate) { + throw new BadRequestError({ message: "Missing client certificate" }); + } + + const clientCertificateX509 = new crypto.X509Certificate(leafCertificate); + const caCertificateX509 = new crypto.X509Certificate(caCertificate); + + const isValidCertificate = clientCertificateX509.verify(caCertificateX509.publicKey); + if (!isValidCertificate) + throw new UnauthorizedError({ + message: "Access denied: Certificate not issued by the provided CA." + }); + + if (new Date(clientCertificateX509.validTo) < new Date()) { + throw new UnauthorizedError({ + message: "Access denied: Certificate has expired." + }); + } + + if (new Date(clientCertificateX509.validFrom) > new Date()) { + throw new UnauthorizedError({ + message: "Access denied: Certificate not yet valid." + }); + } + + const subjectDetails = parseSubjectDetails(clientCertificateX509.subject); + if (identityTlsCertAuth.allowedCommonNames) { + const isValidCommonName = identityTlsCertAuth.allowedCommonNames.split(",").includes(subjectDetails.CN); + if (!isValidCommonName) { + throw new UnauthorizedError({ + message: "Access denied: TLS Certificate Auth common name not allowed." + }); + } + } + + // Generate the token + const identityAccessToken = await identityTlsCertAuthDAL.transaction(async (tx) => { + const newToken = await identityAccessTokenDAL.create( + { + identityId: identityTlsCertAuth.identityId, + isAccessTokenRevoked: false, + accessTokenTTL: identityTlsCertAuth.accessTokenTTL, + accessTokenMaxTTL: identityTlsCertAuth.accessTokenMaxTTL, + accessTokenNumUses: 0, + accessTokenNumUsesLimit: identityTlsCertAuth.accessTokenNumUsesLimit, + authMethod: IdentityAuthMethod.TLS_CERT_AUTH + }, + tx + ); + return newToken; + }); + + const appCfg = getConfig(); + const accessToken = jwt.sign( + { + identityId: identityTlsCertAuth.identityId, + identityAccessTokenId: identityAccessToken.id, + authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN + } as TIdentityAccessTokenJwtPayload, + appCfg.AUTH_SECRET, + Number(identityAccessToken.accessTokenTTL) === 0 + ? undefined + : { + expiresIn: Number(identityAccessToken.accessTokenTTL) + } + ); + + return { + identityTlsCertAuth, + accessToken, + identityAccessToken, + identityMembershipOrg + }; + }; + + const attachTlsCertAuth: TIdentityTlsCertAuthServiceFactory["attachTlsCertAuth"] = async ({ + identityId, + accessTokenTTL, + accessTokenMaxTTL, + accessTokenNumUsesLimit, + accessTokenTrustedIps, + actorId, + actorAuthMethod, + actor, + actorOrgId, + isActorSuperAdmin, + caCertificate, + allowedCommonNames + }) => { + await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin); + + const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); + if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + + if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { + throw new BadRequestError({ + message: "Failed to add TLS Certificate Auth to already configured identity" + }); + } + + if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { + throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); + } + + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); + + const plan = await licenseService.getPlan(identityMembershipOrg.orgId); + const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => { + if ( + !plan.ipAllowlisting && + accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" && + accessTokenTrustedIp.ipAddress !== "::/0" + ) + throw new BadRequestError({ + message: + "Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range." + }); + if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress)) + throw new BadRequestError({ + message: "The IP is not a valid IPv4, IPv6, or CIDR block" + }); + return extractIPDetails(accessTokenTrustedIp.ipAddress); + }); + + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId + }); + + const identityTlsCertAuth = await identityTlsCertAuthDAL.transaction(async (tx) => { + const doc = await identityTlsCertAuthDAL.create( + { + identityId: identityMembershipOrg.identityId, + accessTokenMaxTTL, + allowedCommonNames, + accessTokenTTL, + encryptedCaCertificate: encryptor({ plainText: Buffer.from(caCertificate) }).cipherTextBlob, + accessTokenNumUsesLimit, + accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps) + }, + tx + ); + return doc; + }); + return { ...identityTlsCertAuth, orgId: identityMembershipOrg.orgId }; + }; + + const updateTlsCertAuth: TIdentityTlsCertAuthServiceFactory["updateTlsCertAuth"] = async ({ + identityId, + caCertificate, + allowedCommonNames, + accessTokenTTL, + accessTokenMaxTTL, + accessTokenNumUsesLimit, + accessTokenTrustedIps, + actorId, + actorAuthMethod, + actor, + actorOrgId + }) => { + const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); + if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { + throw new NotFoundError({ + message: "The identity does not have TLS Certificate Auth attached" + }); + } + + const identityTlsCertAuth = await identityTlsCertAuthDAL.findOne({ identityId }); + + if ( + (accessTokenMaxTTL || identityTlsCertAuth.accessTokenMaxTTL) > 0 && + (accessTokenTTL || identityTlsCertAuth.accessTokenTTL) > + (accessTokenMaxTTL || identityTlsCertAuth.accessTokenMaxTTL) + ) { + throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); + } + + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); + + const plan = await licenseService.getPlan(identityMembershipOrg.orgId); + const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => { + if ( + !plan.ipAllowlisting && + accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" && + accessTokenTrustedIp.ipAddress !== "::/0" + ) + throw new BadRequestError({ + message: + "Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range." + }); + if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress)) + throw new BadRequestError({ + message: "The IP is not a valid IPv4, IPv6, or CIDR block" + }); + return extractIPDetails(accessTokenTrustedIp.ipAddress); + }); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId + }); + + const updatedTlsCertAuth = await identityTlsCertAuthDAL.updateById(identityTlsCertAuth.id, { + allowedCommonNames, + encryptedCaCertificate: caCertificate + ? encryptor({ plainText: Buffer.from(caCertificate) }).cipherTextBlob + : undefined, + accessTokenMaxTTL, + accessTokenTTL, + accessTokenNumUsesLimit, + accessTokenTrustedIps: reformattedAccessTokenTrustedIps + ? JSON.stringify(reformattedAccessTokenTrustedIps) + : undefined + }); + + return { ...updatedTlsCertAuth, orgId: identityMembershipOrg.orgId }; + }; + + const getTlsCertAuth: TIdentityTlsCertAuthServiceFactory["getTlsCertAuth"] = async ({ + identityId, + actorId, + actor, + actorAuthMethod, + actorOrgId + }) => { + const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); + if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { + throw new BadRequestError({ + message: "The identity does not have TLS Certificate Auth attached" + }); + } + + const identityAuth = await identityTlsCertAuthDAL.findOne({ identityId }); + + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId + }); + let caCertificate = ""; + if (identityAuth.encryptedCaCertificate) { + caCertificate = decryptor({ cipherTextBlob: identityAuth.encryptedCaCertificate }).toString(); + } + + return { ...identityAuth, caCertificate, orgId: identityMembershipOrg.orgId }; + }; + + const revokeTlsCertAuth: TIdentityTlsCertAuthServiceFactory["revokeTlsCertAuth"] = async ({ + identityId, + actorId, + actor, + actorAuthMethod, + actorOrgId + }) => { + const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); + if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { + throw new BadRequestError({ + message: "The identity does not have TLS Certificate auth" + }); + } + const { permission, membership } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); + + const { permission: rolePermission } = await permissionService.getOrgPermission( + ActorType.IDENTITY, + identityMembershipOrg.identityId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + + const permissionBoundary = validatePrivilegeChangeOperation( + membership.shouldUseNewPrivilegeSystem, + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ); + + if (!permissionBoundary.isValid) + throw new PermissionBoundaryError({ + message: constructPermissionErrorMessage( + "Failed to revoke TLS Certificate auth of identity with more privileged role", + membership.shouldUseNewPrivilegeSystem, + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity + ), + details: { missingPermissions: permissionBoundary.missingPermissions } + }); + + const revokedIdentityTlsCertAuth = await identityTlsCertAuthDAL.transaction(async (tx) => { + const deletedTlsCertAuth = await identityTlsCertAuthDAL.delete({ identityId }, tx); + await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.TLS_CERT_AUTH }, tx); + + return { ...deletedTlsCertAuth?.[0], orgId: identityMembershipOrg.orgId }; + }); + return revokedIdentityTlsCertAuth; + }; + + return { + login, + attachTlsCertAuth, + updateTlsCertAuth, + getTlsCertAuth, + revokeTlsCertAuth + }; +}; diff --git a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-types.ts b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-types.ts new file mode 100644 index 000000000..b7a08276b --- /dev/null +++ b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-types.ts @@ -0,0 +1,49 @@ +import { TIdentityAccessTokens, TIdentityOrgMemberships, TIdentityTlsCertAuths } from "@app/db/schemas"; +import { TProjectPermission } from "@app/lib/types"; + +export type TLoginTlsCertAuthDTO = { + identityId: string; + clientCertificate: string; +}; + +export type TAttachTlsCertAuthDTO = { + identityId: string; + caCertificate: string; + allowedCommonNames?: string | null; + accessTokenTTL: number; + accessTokenMaxTTL: number; + accessTokenNumUsesLimit: number; + accessTokenTrustedIps: { ipAddress: string }[]; + isActorSuperAdmin?: boolean; +} & Omit; + +export type TUpdateTlsCertAuthDTO = { + identityId: string; + caCertificate?: string; + allowedCommonNames?: string | null; + accessTokenTTL?: number; + accessTokenMaxTTL?: number; + accessTokenNumUsesLimit?: number; + accessTokenTrustedIps?: { ipAddress: string }[]; +} & Omit; + +export type TGetTlsCertAuthDTO = { + identityId: string; +} & Omit; + +export type TRevokeTlsCertAuthDTO = { + identityId: string; +} & Omit; + +export type TIdentityTlsCertAuthServiceFactory = { + login: (dto: TLoginTlsCertAuthDTO) => Promise<{ + identityTlsCertAuth: TIdentityTlsCertAuths; + accessToken: string; + identityAccessToken: TIdentityAccessTokens; + identityMembershipOrg: TIdentityOrgMemberships; + }>; + attachTlsCertAuth: (dto: TAttachTlsCertAuthDTO) => Promise; + updateTlsCertAuth: (dto: TUpdateTlsCertAuthDTO) => Promise; + revokeTlsCertAuth: (dto: TRevokeTlsCertAuthDTO) => Promise; + getTlsCertAuth: (dto: TGetTlsCertAuthDTO) => Promise; +}; diff --git a/backend/src/services/identity/identity-dal.ts b/backend/src/services/identity/identity-dal.ts index c4d0b6307..363412493 100644 --- a/backend/src/services/identity/identity-dal.ts +++ b/backend/src/services/identity/identity-dal.ts @@ -1,5 +1,5 @@ import { TDbClient } from "@app/db"; -import { TableName, TIdentities } from "@app/db/schemas"; +import { IdentityAuthMethod, TableName, TIdentities } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols } from "@app/lib/knex"; @@ -8,6 +8,28 @@ export type TIdentityDALFactory = ReturnType; export const identityDALFactory = (db: TDbClient) => { const identityOrm = ormify(db, TableName.Identity); + const getTrustedIpsByAuthMethod = async (identityId: string, authMethod: IdentityAuthMethod) => { + const authMethodToTableName = { + [IdentityAuthMethod.TOKEN_AUTH]: TableName.IdentityTokenAuth, + [IdentityAuthMethod.UNIVERSAL_AUTH]: TableName.IdentityUniversalAuth, + [IdentityAuthMethod.KUBERNETES_AUTH]: TableName.IdentityKubernetesAuth, + [IdentityAuthMethod.GCP_AUTH]: TableName.IdentityGcpAuth, + [IdentityAuthMethod.ALICLOUD_AUTH]: TableName.IdentityAliCloudAuth, + [IdentityAuthMethod.AWS_AUTH]: TableName.IdentityAwsAuth, + [IdentityAuthMethod.AZURE_AUTH]: TableName.IdentityAzureAuth, + [IdentityAuthMethod.TLS_CERT_AUTH]: TableName.IdentityTlsCertAuth, + [IdentityAuthMethod.OCI_AUTH]: TableName.IdentityOciAuth, + [IdentityAuthMethod.OIDC_AUTH]: TableName.IdentityOidcAuth, + [IdentityAuthMethod.JWT_AUTH]: TableName.IdentityJwtAuth, + [IdentityAuthMethod.LDAP_AUTH]: TableName.IdentityLdapAuth + } as const; + const tableName = authMethodToTableName[authMethod]; + if (!tableName) return; + const data = await db(tableName).where({ identityId }).first(); + if (!data) return; + return data.accessTokenTrustedIps; + }; + const getIdentitiesByFilter = async ({ limit, offset, @@ -38,5 +60,5 @@ export const identityDALFactory = (db: TDbClient) => { } }; - return { ...identityOrm, getIdentitiesByFilter }; + return { ...identityOrm, getTrustedIpsByAuthMethod, getIdentitiesByFilter }; }; diff --git a/backend/src/services/identity/identity-fns.ts b/backend/src/services/identity/identity-fns.ts index 3020d9c47..dee87fc49 100644 --- a/backend/src/services/identity/identity-fns.ts +++ b/backend/src/services/identity/identity-fns.ts @@ -11,7 +11,8 @@ export const buildAuthMethods = ({ azureId, tokenId, jwtId, - ldapId + ldapId, + tlsCertId }: { uaId?: string; gcpId?: string; @@ -24,6 +25,7 @@ export const buildAuthMethods = ({ tokenId?: string; jwtId?: string; ldapId?: string; + tlsCertId?: string; }) => { return [ ...[uaId ? IdentityAuthMethod.UNIVERSAL_AUTH : null], @@ -36,6 +38,7 @@ export const buildAuthMethods = ({ ...[azureId ? IdentityAuthMethod.AZURE_AUTH : null], ...[tokenId ? IdentityAuthMethod.TOKEN_AUTH : null], ...[jwtId ? IdentityAuthMethod.JWT_AUTH : null], - ...[ldapId ? IdentityAuthMethod.LDAP_AUTH : null] + ...[ldapId ? IdentityAuthMethod.LDAP_AUTH : null], + ...[tlsCertId ? IdentityAuthMethod.TLS_CERT_AUTH : null] ].filter((authMethod) => authMethod) as IdentityAuthMethod[]; }; diff --git a/backend/src/services/identity/identity-org-dal.ts b/backend/src/services/identity/identity-org-dal.ts index 28064c9bb..3c5ca8ffe 100644 --- a/backend/src/services/identity/identity-org-dal.ts +++ b/backend/src/services/identity/identity-org-dal.ts @@ -12,6 +12,7 @@ import { TIdentityOciAuths, TIdentityOidcAuths, TIdentityOrgMemberships, + TIdentityTlsCertAuths, TIdentityTokenAuths, TIdentityUniversalAuths, TOrgRoles @@ -99,7 +100,11 @@ export const identityOrgDALFactory = (db: TDbClient) => { `${TableName.IdentityOrgMembership}.identityId`, `${TableName.IdentityLdapAuth}.identityId` ) - + .leftJoin( + TableName.IdentityTlsCertAuth, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityTlsCertAuth}.identityId` + ) .select( selectAllTableCols(TableName.IdentityOrgMembership), @@ -114,6 +119,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth), db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth), db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth), + db.ref("id").as("tlsCertId").withSchema(TableName.IdentityTlsCertAuth), db.ref("name").withSchema(TableName.Identity), db.ref("hasDeleteProtection").withSchema(TableName.Identity) ); @@ -238,7 +244,11 @@ export const identityOrgDALFactory = (db: TDbClient) => { "paginatedIdentity.identityId", `${TableName.IdentityLdapAuth}.identityId` ) - + .leftJoin( + TableName.IdentityTlsCertAuth, + "paginatedIdentity.identityId", + `${TableName.IdentityTlsCertAuth}.identityId` + ) .select( db.ref("id").withSchema("paginatedIdentity"), db.ref("role").withSchema("paginatedIdentity"), @@ -260,7 +270,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth), db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth), db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth), - db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth) + db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth), + db.ref("id").as("tlsCertId").withSchema(TableName.IdentityTlsCertAuth) ) // cr stands for custom role .select(db.ref("id").as("crId").withSchema(TableName.OrgRoles)) @@ -306,6 +317,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { azureId, tokenId, ldapId, + tlsCertId, createdAt, updatedAt }) => ({ @@ -313,7 +325,6 @@ export const identityOrgDALFactory = (db: TDbClient) => { roleId, identityId, id, - orgId, createdAt, updatedAt, @@ -341,7 +352,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { azureId, tokenId, jwtId, - ldapId + ldapId, + tlsCertId }) } }), @@ -380,7 +392,12 @@ export const identityOrgDALFactory = (db: TDbClient) => { .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityOrgMembership}.identityId`) .where(`${TableName.IdentityOrgMembership}.orgId`, orgId) .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) - .orderBy(`${TableName.Identity}.${orderBy}`, orderDirection) + .orderBy( + orderBy === OrgIdentityOrderBy.Role + ? `${TableName.IdentityOrgMembership}.${orderBy}` + : `${TableName.Identity}.${orderBy}`, + orderDirection + ) .select(`${TableName.IdentityOrgMembership}.id`) .select<{ id: string; total_count: string }>( db.raw( @@ -511,6 +528,23 @@ export const identityOrgDALFactory = (db: TDbClient) => { if (orderBy === OrgIdentityOrderBy.Name) { void query.orderBy("identityName", orderDirection); + } else if (orderBy === OrgIdentityOrderBy.Role) { + void query.orderByRaw( + ` + CASE + WHEN ??.role = ? + THEN ??.slug + ELSE ??.role + END ? + `, + [ + TableName.IdentityOrgMembership, + "custom", + TableName.OrgRoles, + TableName.IdentityOrgMembership, + db.raw(orderDirection) + ] + ); } const docs = await query; diff --git a/backend/src/services/identity/identity-types.ts b/backend/src/services/identity/identity-types.ts index 8d23f34fe..4380f6f41 100644 --- a/backend/src/services/identity/identity-types.ts +++ b/backend/src/services/identity/identity-types.ts @@ -46,8 +46,8 @@ export type TListOrgIdentitiesByOrgIdDTO = { } & TOrgPermission; export enum OrgIdentityOrderBy { - Name = "name" - // Role = "role" + Name = "name", + Role = "role" } export type TSearchOrgIdentitiesByOrgIdDAL = { diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 2517c040c..27c22297b 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -4,13 +4,7 @@ import { Octokit } from "@octokit/rest"; import { Client as OctopusClient, SpaceRepository as OctopusSpaceRepository } from "@octopusdeploy/api-client"; import AWS from "aws-sdk"; -import { - ActionProjectType, - SecretEncryptionAlgo, - SecretKeyEncoding, - TIntegrationAuths, - TIntegrationAuthsInsert -} from "@app/db/schemas"; +import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; @@ -103,8 +97,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const authorizations = await integrationAuthDAL.find({ projectId }); @@ -122,8 +115,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: auth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); return permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations) ? auth : null; @@ -146,8 +138,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); return integrationAuth; @@ -172,8 +163,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); @@ -282,8 +272,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); @@ -417,8 +406,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); @@ -679,8 +667,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -714,8 +701,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -745,8 +731,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -787,8 +772,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -816,8 +800,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -891,8 +874,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -939,8 +921,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -974,8 +955,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1033,8 +1013,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1070,8 +1049,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1112,8 +1090,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1153,8 +1130,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1194,8 +1170,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1234,8 +1209,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1275,8 +1249,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1344,8 +1317,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1419,8 +1391,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1470,8 +1441,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1519,8 +1489,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1588,8 +1557,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1630,8 +1598,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1743,8 +1710,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); @@ -1767,8 +1733,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); @@ -1801,8 +1766,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(sourcePermission).throwUnlessCan( @@ -1815,8 +1779,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(targetPermission).throwUnlessCan( @@ -1849,8 +1812,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -1884,8 +1846,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); @@ -1925,8 +1886,7 @@ export const integrationAuthServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); diff --git a/backend/src/services/integration/integration-service.ts b/backend/src/services/integration/integration-service.ts index 2ef8615eb..e03ca1e8f 100644 --- a/backend/src/services/integration/integration-service.ts +++ b/backend/src/services/integration/integration-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { @@ -91,8 +90,7 @@ export const integrationServiceFactory = ({ actorId, projectId: integrationAuth.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); @@ -167,8 +165,7 @@ export const integrationServiceFactory = ({ actorId, projectId: integration.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); @@ -231,8 +228,7 @@ export const integrationServiceFactory = ({ actorId, projectId: integration.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -259,8 +255,7 @@ export const integrationServiceFactory = ({ actorId, projectId: integration.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -302,8 +297,7 @@ export const integrationServiceFactory = ({ actorId, projectId: integration.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); @@ -339,8 +333,7 @@ export const integrationServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -359,8 +352,7 @@ export const integrationServiceFactory = ({ actorId, projectId: integration.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); diff --git a/backend/src/services/microsoft-teams/microsoft-teams-fns.ts b/backend/src/services/microsoft-teams/microsoft-teams-fns.ts index 4111115bf..d477143a9 100644 --- a/backend/src/services/microsoft-teams/microsoft-teams-fns.ts +++ b/backend/src/services/microsoft-teams/microsoft-teams-fns.ts @@ -400,7 +400,7 @@ export const buildTeamsPayload = (notification: TNotification) => { { type: "Action.OpenUrl", title: "View request in Infisical", - url: `${appCfg.SITE_URL}/secret-manager/${payload.projectId}/approval?requestId=${payload.requestId}` + url: `${appCfg.SITE_URL}/projects/${payload.projectId}/secret-manager/approval?requestId=${payload.requestId}` } ] }; diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 1215b7c00..dd16cbc3c 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -5,7 +5,6 @@ import jwt from "jsonwebtoken"; import { Knex } from "knex"; import { - ActionProjectType, OrgMembershipRole, OrgMembershipStatus, ProjectMembershipRole, @@ -235,14 +234,14 @@ export const orgServiceFactory = ({ return org; }; - const findAllWorkspaces = async ({ actor, actorId, orgId, type }: TFindAllWorkspacesDTO) => { + const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => { if (actor === ActorType.USER) { - const workspaces = await projectDAL.findUserProjects(actorId, orgId, type || "all"); + const workspaces = await projectDAL.findUserProjects(actorId, orgId); return workspaces; } if (actor === ActorType.IDENTITY) { - const workspaces = await projectDAL.findAllProjectsByIdentity(actorId, type); + const workspaces = await projectDAL.findAllProjectsByIdentity(actorId); return workspaces; } @@ -972,8 +971,7 @@ export const orgServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(projectPermission).throwUnlessCan( ProjectPermissionMemberActions.Create, diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index 8b2485ac4..0736f174d 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -1,4 +1,3 @@ -import { ProjectType } from "@app/db/schemas"; import { TOrgPermission } from "@app/lib/types"; import { ActorAuthMethod, ActorType, MfaMethod } from "../auth/auth-type"; @@ -60,7 +59,6 @@ export type TFindAllWorkspacesDTO = { actorOrgId: string | undefined; actorAuthMethod: ActorAuthMethod; orgId: string; - type?: ProjectType; }; export type TUpdateOrgDTO = { diff --git a/backend/src/services/pki-alert/pki-alert-service.ts b/backend/src/services/pki-alert/pki-alert-service.ts index 946740b66..8b348085f 100644 --- a/backend/src/services/pki-alert/pki-alert-service.ts +++ b/backend/src/services/pki-alert/pki-alert-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, ProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; @@ -9,7 +8,6 @@ import { TPkiCollectionDALFactory } from "@app/services/pki-collection/pki-colle import { pkiItemTypeToNameMap } from "@app/services/pki-collection/pki-collection-types"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; -import { TProjectDALFactory } from "../project/project-dal"; import { TPkiAlertDALFactory } from "./pki-alert-dal"; import { TCreateAlertDTO, TDeleteAlertDTO, TGetAlertByIdDTO, TUpdateAlertDTO } from "./pki-alert-types"; @@ -21,7 +19,6 @@ type TPkiAlertServiceFactoryDep = { pkiCollectionDAL: Pick; permissionService: Pick; smtpService: Pick; - projectDAL: Pick; }; export type TPkiAlertServiceFactory = ReturnType; @@ -30,8 +27,7 @@ export const pkiAlertServiceFactory = ({ pkiAlertDAL, pkiCollectionDAL, permissionService, - smtpService, - projectDAL + smtpService }: TPkiAlertServiceFactoryDep) => { const sendPkiItemExpiryNotices = async () => { const allAlertItems = await pkiAlertDAL.getExpiringPkiCollectionItemsForAlerting(); @@ -67,7 +63,7 @@ export const pkiAlertServiceFactory = ({ }; const createPkiAlert = async ({ - projectId: preSplitProjectId, + projectId, name, pkiCollectionId, alertBeforeDays, @@ -77,22 +73,12 @@ export const pkiAlertServiceFactory = ({ actor, actorOrgId }: TCreateAlertDTO) => { - let projectId = preSplitProjectId; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } - const { permission } = await permissionService.getProjectPermission({ actor, actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts); @@ -121,8 +107,7 @@ export const pkiAlertServiceFactory = ({ actorId, projectId: alert.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts); @@ -148,8 +133,7 @@ export const pkiAlertServiceFactory = ({ actorId, projectId: alert.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts); @@ -181,8 +165,7 @@ export const pkiAlertServiceFactory = ({ actorId, projectId: alert.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts); diff --git a/backend/src/services/pki-collection/pki-collection-service.ts b/backend/src/services/pki-collection/pki-collection-service.ts index 8d758b5e9..7c89ce255 100644 --- a/backend/src/services/pki-collection/pki-collection-service.ts +++ b/backend/src/services/pki-collection/pki-collection-service.ts @@ -1,13 +1,12 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, ProjectType, TPkiCollectionItems } from "@app/db/schemas"; +import { TPkiCollectionItems } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; -import { TProjectDALFactory } from "../project/project-dal"; import { TPkiCollectionDALFactory } from "./pki-collection-dal"; import { transformPkiCollectionItem } from "./pki-collection-fns"; import { TPkiCollectionItemDALFactory } from "./pki-collection-item-dal"; @@ -31,7 +30,6 @@ type TPkiCollectionServiceFactoryDep = { certificateAuthorityDAL: Pick; certificateDAL: Pick; permissionService: Pick; - projectDAL: Pick; }; export type TPkiCollectionServiceFactory = ReturnType; @@ -41,34 +39,23 @@ export const pkiCollectionServiceFactory = ({ pkiCollectionItemDAL, certificateAuthorityDAL, certificateDAL, - permissionService, - projectDAL + permissionService }: TPkiCollectionServiceFactoryDep) => { const createPkiCollection = async ({ name, description, - projectId: preSplitProjectId, + projectId, actorId, actorAuthMethod, actor, actorOrgId }: TCreatePkiCollectionDTO) => { - let projectId = preSplitProjectId; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } - const { permission } = await permissionService.getProjectPermission({ actor, actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -100,8 +87,7 @@ export const pkiCollectionServiceFactory = ({ actorId, projectId: pkiCollection.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections); @@ -125,8 +111,7 @@ export const pkiCollectionServiceFactory = ({ actorId, projectId: pkiCollection.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections); @@ -153,8 +138,7 @@ export const pkiCollectionServiceFactory = ({ actorId, projectId: pkiCollection.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -183,8 +167,7 @@ export const pkiCollectionServiceFactory = ({ actorId, projectId: pkiCollection.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections); @@ -227,8 +210,7 @@ export const pkiCollectionServiceFactory = ({ actorId, projectId: pkiCollection.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -315,8 +297,7 @@ export const pkiCollectionServiceFactory = ({ actorId, projectId: pkiCollection.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/services/pki-subscriber/pki-subscriber-service.ts b/backend/src/services/pki-subscriber/pki-subscriber-service.ts index a3e6ec78c..245337296 100644 --- a/backend/src/services/pki-subscriber/pki-subscriber-service.ts +++ b/backend/src/services/pki-subscriber/pki-subscriber-service.ts @@ -2,7 +2,6 @@ import { ForbiddenError, subject } from "@casl/ability"; 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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { @@ -120,8 +119,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -183,8 +181,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId: subscriber.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -237,8 +234,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId: subscriber.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -300,8 +296,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId: subscriber.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -337,8 +332,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId: subscriber.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -393,8 +387,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId: subscriber.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -440,8 +433,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -699,8 +691,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId: subscriber.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -747,8 +738,7 @@ export const pkiSubscriberServiceFactory = ({ actorId, projectId: subscriber.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/services/pki-templates/pki-templates-service.ts b/backend/src/services/pki-templates/pki-templates-service.ts index e648ab88f..98469c157 100644 --- a/backend/src/services/pki-templates/pki-templates-service.ts +++ b/backend/src/services/pki-templates/pki-templates-service.ts @@ -3,7 +3,6 @@ import { ForbiddenError, subject } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import RE2 from "re2"; -import { ActionProjectType } from "@app/db/schemas"; import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { @@ -119,8 +118,7 @@ export const pkiTemplatesServiceFactory = ({ actorId, projectId: ca.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -172,8 +170,7 @@ export const pkiTemplatesServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -236,8 +233,7 @@ export const pkiTemplatesServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -269,8 +265,7 @@ export const pkiTemplatesServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -295,8 +290,7 @@ export const pkiTemplatesServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); const certTemplate = await pkiTemplatesDAL.find({ projectId }, { limit, offset, count: true }); @@ -338,8 +332,7 @@ export const pkiTemplatesServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -385,8 +378,7 @@ export const pkiTemplatesServiceFactory = ({ actorId, projectId: certTemplate.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index 7dc5b058e..884d45ee1 100644 --- a/backend/src/services/project-bot/project-bot-service.ts +++ b/backend/src/services/project-bot/project-bot-service.ts @@ -1,6 +1,6 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, ProjectVersion } from "@app/db/schemas"; +import { ProjectVersion } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { generateAsymmetricKeyPair } from "@app/lib/crypto"; @@ -46,8 +46,7 @@ export const projectBotServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); @@ -113,8 +112,7 @@ export const projectBotServiceFactory = ({ actorId, projectId: bot.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); diff --git a/backend/src/services/project-env/project-env-service.ts b/backend/src/services/project-env/project-env-service.ts index 9a82a6bbe..6773ee600 100644 --- a/backend/src/services/project-env/project-env-service.ts +++ b/backend/src/services/project-env/project-env-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; @@ -47,8 +46,7 @@ export const projectEnvServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Environments); @@ -136,8 +134,7 @@ export const projectEnvServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments); @@ -200,8 +197,7 @@ export const projectEnvServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments); @@ -256,8 +252,7 @@ export const projectEnvServiceFactory = ({ actorId, projectId: environment.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Environments); diff --git a/backend/src/services/project-key/project-key-service.ts b/backend/src/services/project-key/project-key-service.ts index a884d25bc..c4eae9e2e 100644 --- a/backend/src/services/project-key/project-key-service.ts +++ b/backend/src/services/project-key/project-key-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError } from "@app/lib/errors"; @@ -37,8 +36,7 @@ export const projectKeyServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); @@ -67,8 +65,7 @@ export const projectKeyServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); const latestKey = await projectKeyDAL.findLatestProjectKey(actorId, projectId); return latestKey; @@ -86,8 +83,7 @@ export const projectKeyServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); return projectKeyDAL.findAllProjectUserPubKeys(projectId); diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index b9e502922..9cef4dabf 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas"; +import { ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { constructPermissionErrorMessage, @@ -90,8 +90,7 @@ export const projectMembershipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); @@ -134,8 +133,7 @@ export const projectMembershipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); @@ -157,8 +155,7 @@ export const projectMembershipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); @@ -184,8 +181,7 @@ export const projectMembershipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member); const orgMembers = await orgDAL.findMembership({ @@ -265,8 +261,7 @@ export const projectMembershipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); @@ -375,8 +370,7 @@ export const projectMembershipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member); @@ -418,8 +412,7 @@ export const projectMembershipServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member); diff --git a/backend/src/services/project-role/project-role-fns.ts b/backend/src/services/project-role/project-role-fns.ts index 4dfcf960b..bf5044f47 100644 --- a/backend/src/services/project-role/project-role-fns.ts +++ b/backend/src/services/project-role/project-role-fns.ts @@ -11,7 +11,7 @@ import { } from "@app/ee/services/permission/default-roles"; import { TGetPredefinedRolesDTO } from "@app/services/project-role/project-role-types"; -export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetPredefinedRolesDTO) => { +export const getPredefinedRoles = ({ projectId, roleFilter }: TGetPredefinedRolesDTO) => { return [ { id: uuidv4(), @@ -75,5 +75,5 @@ export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetP createdAt: new Date(), updatedAt: new Date() } - ].filter(({ slug, type }) => (type ? type === projectType : true) && (!roleFilter || roleFilter === slug)); + ].filter(({ slug }) => !roleFilter || roleFilter === slug); }; diff --git a/backend/src/services/project-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index dd0eecc68..76613805e 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; import { requestContext } from "@fastify/request-context"; -import { ActionProjectType, ProjectMembershipRole, ProjectType, TableName, TProjects } from "@app/db/schemas"; +import { ProjectMembershipRole, TableName, TProjects } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, @@ -71,8 +71,7 @@ export const projectRoleServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role); const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId }); @@ -112,14 +111,12 @@ export const projectRoleServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role); if (roleSlug !== "custom" && Object.values(ProjectMembershipRole).includes(roleSlug as ProjectMembershipRole)) { const [predefinedRole] = getPredefinedRoles({ projectId: project.id, - projectType: project.type as ProjectType, roleFilter: roleSlug as ProjectMembershipRole }); @@ -142,8 +139,7 @@ export const projectRoleServiceFactory = ({ actorId, projectId: projectRole.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role); @@ -173,8 +169,7 @@ export const projectRoleServiceFactory = ({ actorId, projectId: projectRole.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role); @@ -215,18 +210,14 @@ export const projectRoleServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role); const customRoles = await projectRoleDAL.find( { projectId: project.id }, { sort: [[`${TableName.ProjectRoles}.slug` as "slug", "asc"]] } ); - const roles = [ - ...getPredefinedRoles({ projectId: project.id, projectType: project.type as ProjectType }), - ...(customRoles || []) - ]; + const roles = [...getPredefinedRoles({ projectId: project.id }), ...(customRoles || [])]; return roles; }; @@ -242,8 +233,7 @@ export const projectRoleServiceFactory = ({ actorId: userId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); // just to satisfy ts if (!("roles" in membership)) throw new BadRequestError({ message: "Service token not allowed" }); diff --git a/backend/src/services/project-role/project-role-types.ts b/backend/src/services/project-role/project-role-types.ts index 508623a0c..37395a9a7 100644 --- a/backend/src/services/project-role/project-role-types.ts +++ b/backend/src/services/project-role/project-role-types.ts @@ -1,4 +1,4 @@ -import { ProjectMembershipRole, ProjectType, TOrgRolesUpdate, TProjectRolesInsert } from "@app/db/schemas"; +import { ProjectMembershipRole, TOrgRolesUpdate, TProjectRolesInsert } from "@app/db/schemas"; import { TProjectPermission } from "@app/lib/types"; export enum ProjectRoleServiceIdentifierType { @@ -37,6 +37,5 @@ export type TListRolesDTO = { export type TGetPredefinedRolesDTO = { projectId: string; - projectType: ProjectType; roleFilter?: ProjectMembershipRole; }; diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 7f733503c..bd008a5be 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -3,7 +3,6 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; import { ProjectsSchema, - ProjectType, ProjectUpgradeStatus, ProjectVersion, SortDirection, @@ -22,17 +21,12 @@ export type TProjectDALFactory = ReturnType; export const projectDALFactory = (db: TDbClient) => { const projectOrm = ormify(db, TableName.Project); - const findIdentityProjects = async (identityId: string, orgId: string, projectType: ProjectType | "all") => { + const findIdentityProjects = async (identityId: string, orgId: string) => { try { const workspaces = await db(TableName.IdentityProjectMembership) .where({ identityId }) .join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`) .where(`${TableName.Project}.orgId`, orgId) - .andWhere((qb) => { - if (projectType !== "all") { - void qb.where(`${TableName.Project}.type`, projectType); - } - }) .leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`) .select( selectAllTableCols(TableName.Project), @@ -72,18 +66,13 @@ export const projectDALFactory = (db: TDbClient) => { } }; - const findUserProjects = async (userId: string, orgId: string, projectType: ProjectType | "all") => { + const findUserProjects = async (userId: string, orgId: string) => { try { const workspaces = await db .replicaNode()(TableName.ProjectMembership) .where({ userId }) .join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`) .where(`${TableName.Project}.orgId`, orgId) - .andWhere((qb) => { - if (projectType !== "all") { - void qb.where(`${TableName.Project}.type`, projectType); - } - }) .leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`) .select( selectAllTableCols(TableName.Project), @@ -103,11 +92,6 @@ export const projectDALFactory = (db: TDbClient) => { .whereIn("groupId", groups) .join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`) .where(`${TableName.Project}.orgId`, orgId) - .andWhere((qb) => { - if (projectType !== "all") { - void qb.where(`${TableName.Project}.type`, projectType); - } - }) .whereNotIn( `${TableName.Project}.id`, workspaces.map(({ id }) => id) @@ -177,17 +161,12 @@ export const projectDALFactory = (db: TDbClient) => { } }; - const findAllProjectsByIdentity = async (identityId: string, projectType?: ProjectType) => { + const findAllProjectsByIdentity = async (identityId: string) => { try { const workspaces = await db .replicaNode()(TableName.IdentityProjectMembership) .where({ identityId }) .join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`) - .andWhere((qb) => { - if (projectType) { - void qb.where(`${TableName.Project}.type`, projectType); - } - }) .leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`) .select( selectAllTableCols(TableName.Project), @@ -389,27 +368,10 @@ export const projectDALFactory = (db: TDbClient) => { }; }; - const getProjectFromSplitId = async (projectId: string, projectType: ProjectType) => { - try { - const project = await db(TableName.ProjectSplitBackfillIds) - .where({ - sourceProjectId: projectId, - destinationProjectType: projectType - }) - .join(TableName.Project, `${TableName.Project}.id`, `${TableName.ProjectSplitBackfillIds}.destinationProjectId`) - .select(selectAllTableCols(TableName.Project)) - .first(); - return project; - } catch (error) { - throw new DatabaseError({ error, name: `Failed to find split project with id ${projectId}` }); - } - }; - const searchProjects = async (dto: { orgId: string; actor: ActorType; actorId: string; - type?: ProjectType; limit?: number; offset?: number; name?: string; @@ -464,9 +426,6 @@ export const projectDALFactory = (db: TDbClient) => { void query.orderBy([{ column: `${TableName.Project}.name`, order: sortDir }]); } - if (dto.type) { - void query.where(`${TableName.Project}.type`, dto.type); - } if (dto.name) { void query.whereILike(`${TableName.Project}.name`, `%${dto.name}%`); } @@ -512,7 +471,6 @@ export const projectDALFactory = (db: TDbClient) => { findProjectBySlug, findProjectWithOrg, checkProjectUpgradeStatus, - getProjectFromSplitId, searchProjects, findProjectByEnvId, countOfOrgProjects diff --git a/backend/src/services/project/project-fns.ts b/backend/src/services/project/project-fns.ts index 08652e348..4500d4b66 100644 --- a/backend/src/services/project/project-fns.ts +++ b/backend/src/services/project/project-fns.ts @@ -141,7 +141,7 @@ export const bootstrapSshProject = async ({ tx }); - await projectSshConfigDAL.create( + const sshConfig = await projectSshConfigDAL.create( { projectId, defaultHostSshCaId: hostSshCa.id, @@ -149,4 +149,5 @@ export const bootstrapSshProject = async ({ }, tx ); + return sshConfig; }; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 29774fcc8..0aaabbdf4 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -1,14 +1,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import slugify from "@sindresorhus/slugify"; -import { - ActionProjectType, - ProjectMembershipRole, - ProjectType, - ProjectVersion, - TableName, - TProjectEnvironments -} from "@app/db/schemas"; +import { ProjectMembershipRole, ProjectVersion, TableName, TProjectEnvironments } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; @@ -249,8 +242,7 @@ export const projectServiceFactory = ({ kmsKeyId, tx: trx, createDefaultEnvs = true, - template = InfisicalProjectTemplate.Default, - type = ProjectType.SecretManager + template = InfisicalProjectTemplate.Default }: TCreateProjectDTO) => { const organization = await orgDAL.findOne({ id: actorOrgId }); const { permission, membership: orgMembership } = await permissionService.getOrgPermission( @@ -266,11 +258,7 @@ export const projectServiceFactory = ({ await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.CreateProject(organization.id)]); const plan = await licenseService.getPlan(organization.id); - if ( - plan.workspaceLimit !== null && - plan.workspacesUsed >= plan.workspaceLimit && - type === ProjectType.SecretManager - ) { + if (plan.workspaceLimit !== null && plan.workspacesUsed >= plan.workspaceLimit) { // case: limit imposed on number of workspaces allowed // case: number of workspaces used exceeds the number of workspaces allowed throw new BadRequestError({ @@ -307,7 +295,6 @@ export const projectServiceFactory = ({ const project = await projectDAL.create( { name: workspaceName, - type, description: workspaceDescription, orgId: organization.id, slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), @@ -318,16 +305,14 @@ export const projectServiceFactory = ({ tx ); - if (type === ProjectType.SSH) { - await bootstrapSshProject({ - projectId: project.id, - sshCertificateAuthorityDAL, - sshCertificateAuthoritySecretDAL, - kmsService, - projectSshConfigDAL, - tx - }); - } + await bootstrapSshProject({ + projectId: project.id, + sshCertificateAuthorityDAL, + sshCertificateAuthoritySecretDAL, + kmsService, + projectSshConfigDAL, + tx + }); // set ghost user as admin of project const projectMembership = await projectMembershipDAL.create( @@ -524,8 +509,7 @@ export const projectServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); @@ -583,18 +567,11 @@ export const projectServiceFactory = ({ return deletedProject; }; - const getProjects = async ({ - actorId, - actor, - includeRoles, - actorAuthMethod, - actorOrgId, - type = ProjectType.SecretManager - }: TListProjectsDTO) => { + const getProjects = async ({ actorId, actor, includeRoles, actorAuthMethod, actorOrgId }: TListProjectsDTO) => { const workspaces = actor === ActorType.IDENTITY - ? await projectDAL.findIdentityProjects(actorId, actorOrgId, type) - : await projectDAL.findUserProjects(actorId, actorOrgId, type); + ? await projectDAL.findIdentityProjects(actorId, actorOrgId) + : await projectDAL.findUserProjects(actorId, actorOrgId); if (includeRoles) { const { permission } = await permissionService.getUserOrgPermission( @@ -618,10 +595,7 @@ export const projectServiceFactory = ({ workspaces.map(async (workspace) => { return { ...workspace, - roles: [ - ...(workspaceMappedToRoles[workspace.id] || []), - ...getPredefinedRoles({ projectId: workspace.id, projectType: workspace.type as ProjectType }) - ] + roles: [...(workspaceMappedToRoles[workspace.id] || []), ...getPredefinedRoles({ projectId: workspace.id })] }; }) ); @@ -640,8 +614,7 @@ export const projectServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); return project; }; @@ -654,8 +627,7 @@ export const projectServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -679,6 +651,7 @@ export const projectServiceFactory = ({ hasDeleteProtection: update.hasDeleteProtection, slug: update.slug, secretSharing: update.secretSharing, + defaultProduct: update.defaultProduct, showSnapshotsLegacy: update.showSnapshotsLegacy }); @@ -698,8 +671,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -724,8 +696,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -754,8 +725,7 @@ export const projectServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); if (!hasRole(ProjectMembershipRole.Admin)) @@ -786,8 +756,7 @@ export const projectServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); if (!hasRole(ProjectMembershipRole.Admin)) { @@ -819,8 +788,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -841,8 +809,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); @@ -912,8 +879,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); @@ -944,22 +910,14 @@ export const projectServiceFactory = ({ actor }: TListProjectCasDTO) => { const project = await projectDAL.findProjectByFilter(filter); - let projectId = project.id; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } + const projectId = project.id; const { permission } = await permissionService.getProjectPermission({ actor, actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -998,22 +956,14 @@ export const projectServiceFactory = ({ actor }: TListProjectCertsDTO) => { const project = await projectDAL.findProjectByFilter(filter); - let projectId = project.id; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } + const projectId = project.id; const { permission } = await permissionService.getProjectPermission({ actor, actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -1046,28 +996,18 @@ export const projectServiceFactory = ({ * Return list of (PKI) alerts configured for project */ const listProjectAlerts = async ({ - projectId: preSplitProjectId, + projectId, actor, actorId, actorAuthMethod, actorOrgId }: TListProjectAlertsDTO) => { - let projectId = preSplitProjectId; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } - const { permission } = await permissionService.getProjectPermission({ actor, actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts); @@ -1083,27 +1023,18 @@ export const projectServiceFactory = ({ * Return list of PKI collections for project */ const listProjectPkiCollections = async ({ - projectId: preSplitProjectId, + projectId, actor, actorId, actorAuthMethod, actorOrgId }: TListProjectAlertsDTO) => { - let projectId = preSplitProjectId; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } const { permission } = await permissionService.getProjectPermission({ actor, actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections); @@ -1130,8 +1061,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); const allowedSubscribers = []; @@ -1158,28 +1088,18 @@ export const projectServiceFactory = ({ * Return list of certificate templates for project */ const listProjectCertificateTemplates = async ({ - projectId: preSplitProjectId, + projectId, actorId, actorOrgId, actorAuthMethod, actor }: TListProjectCertificateTemplatesDTO) => { - let projectId = preSplitProjectId; - const certManagerProjectFromSplit = await projectDAL.getProjectFromSplitId( - projectId, - ProjectType.CertificateManager - ); - if (certManagerProjectFromSplit) { - projectId = certManagerProjectFromSplit.id; - } - const { permission } = await permissionService.getProjectPermission({ actor, actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + actorOrgId }); const certificateTemplates = await certificateTemplateDAL.getCertTemplatesByProjectId(projectId); @@ -1209,8 +1129,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -1243,8 +1162,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); const allowedHosts = []; @@ -1283,8 +1201,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups); @@ -1311,8 +1228,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates); @@ -1350,8 +1266,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -1385,8 +1300,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms); @@ -1413,8 +1327,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms); @@ -1443,8 +1356,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms); @@ -1466,8 +1378,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); if (!membership) { @@ -1499,8 +1410,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings); @@ -1539,8 +1449,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SSH + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -1623,8 +1532,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings); @@ -1696,8 +1604,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -1774,8 +1681,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); @@ -1898,8 +1804,7 @@ export const projectServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings); @@ -1927,15 +1832,7 @@ export const projectServiceFactory = ({ }); }; - const searchProjects = async ({ - name, - offset, - permission, - limit, - type, - orderBy, - orderDirection - }: TSearchProjectsDTO) => { + const searchProjects = async ({ name, offset, permission, limit, orderBy, orderDirection }: TSearchProjectsDTO) => { // check user belong to org await permissionService.getOrgPermission( permission.type, @@ -1949,7 +1846,6 @@ export const projectServiceFactory = ({ limit, offset, name, - type, orgId: permission.orgId, actor: permission.type, actorId: permission.id, @@ -1973,7 +1869,7 @@ export const projectServiceFactory = ({ actor: permission.type, actorId: permission.id, projectId, - actionProjectType: ActionProjectType.Any, + actorAuthMethod: permission.authMethod, actorOrgId: permission.orgId }) diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 8ef72492a..5d4578194 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -92,6 +92,7 @@ export type TUpdateProjectDTO = { description?: string; autoCapitalization?: boolean; hasDeleteProtection?: boolean; + defaultProduct?: ProjectType; slug?: string; secretSharing?: boolean; showSnapshotsLegacy?: boolean; diff --git a/backend/src/services/secret-blind-index/secret-blind-index-service.ts b/backend/src/services/secret-blind-index/secret-blind-index-service.ts index a19ce8b88..c8fed2a2b 100644 --- a/backend/src/services/secret-blind-index/secret-blind-index-service.ts +++ b/backend/src/services/secret-blind-index/secret-blind-index-service.ts @@ -1,4 +1,4 @@ -import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas"; +import { ProjectMembershipRole } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; @@ -36,8 +36,7 @@ export const secretBlindIndexServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId); @@ -56,8 +55,7 @@ export const secretBlindIndexServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!hasRole(ProjectMembershipRole.Admin)) { throw new ForbiddenRequestError({ message: "Insufficient privileges, user must be admin" }); @@ -80,8 +78,7 @@ export const secretBlindIndexServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!hasRole(ProjectMembershipRole.Admin)) { throw new ForbiddenRequestError({ message: "Insufficient privileges, user must be admin" }); diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index da29c0f36..37a595daf 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -2,9 +2,10 @@ import { ForbiddenError, subject } from "@casl/ability"; import path from "path"; import { v4 as uuidv4, validate as uuidValidate } from "uuid"; -import { ActionProjectType, TSecretFoldersInsert } from "@app/db/schemas"; +import { TSecretFolders, TSecretFoldersInsert } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { PgSqlLock } from "@app/keystore/keystore"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; @@ -14,6 +15,7 @@ import { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns"; import { ChangeType, CommitType, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; +import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretFolderDALFactory } from "./secret-folder-dal"; import { TCreateFolderDTO, @@ -34,6 +36,8 @@ type TSecretFolderServiceFactoryDep = { folderVersionDAL: Pick; folderCommitService: Pick; projectDAL: Pick; + secretApprovalPolicyService: Pick; + secretV2BridgeDAL: Pick; }; export type TSecretFolderServiceFactory = ReturnType; @@ -45,7 +49,9 @@ export const secretFolderServiceFactory = ({ projectEnvDAL, folderVersionDAL, folderCommitService, - projectDAL + projectDAL, + secretApprovalPolicyService, + secretV2BridgeDAL }: TSecretFolderServiceFactoryDep) => { const createFolder = async ({ projectId, @@ -63,8 +69,7 @@ export const secretFolderServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -245,8 +250,7 @@ export const secretFolderServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); folders.forEach(({ environment, path: secretPath }) => { @@ -377,8 +381,7 @@ export const secretFolderServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -464,6 +467,66 @@ export const secretFolderServiceFactory = ({ return { folder: newFolder, old: folder }; }; + const $checkFolderPolicy = async ({ + projectId, + environment, + parentId + }: { + projectId: string; + environment: string; + parentId: string; + }) => { + // get environment root folder (as it's needed to get all folders under it) + const rootFolder = await folderDAL.findBySecretPath(projectId, environment, "/"); + if (!rootFolder) throw new NotFoundError({ message: `Root folder not found` }); + // get all folders under environment root folder + const folderPaths = await folderDAL.findByEnvsDeep({ parentIds: [rootFolder.id] }); + + // create a map of folders by parent id + const normalizeKey = (key: string | null | undefined): string => key ?? "root"; + const folderMap = new Map(); + for (const folder of folderPaths) { + if (!folderMap.has(normalizeKey(folder.parentId))) { + folderMap.set(normalizeKey(folder.parentId), []); + } + folderMap.get(normalizeKey(folder.parentId))?.push(folder); + } + + // Recursively collect all folders under the given parentId + const collectDescendants = ( + id: string + ): (TSecretFolders & { path: string; depth: number; environment: string })[] => { + const children = folderMap.get(normalizeKey(id)) || []; + return [...children, ...children.flatMap((child) => collectDescendants(child.id))]; + }; + + const foldersUnderParent = collectDescendants(parentId); + + const folderPolicyPaths = foldersUnderParent.map((folder) => ({ + path: folder.path, + id: folder.id + })); + + // get secrets under the given folders + const secrets = await secretV2BridgeDAL.findByFolderIds({ folderIds: folderPolicyPaths.map((p) => p.id) }); + for await (const folderPolicyPath of folderPolicyPaths) { + // eslint-disable-next-line no-continue + if (!secrets.some((s) => s.folderId === folderPolicyPath.id)) continue; + const policy = await secretApprovalPolicyService.getSecretApprovalPolicy( + projectId, + environment, + folderPolicyPath.path + ); + // if there is a policy and there are secrets under the given folder, throw error + if (policy) { + throw new BadRequestError({ + message: `You cannot delete the selected folder because it contains one or more secrets that are protected by the change policy "${policy.name}" at folder path "${folderPolicyPath.path}". Please remove the secrets at folder path "${folderPolicyPath.path}" and try again.`, + name: "DeleteFolderProtectedByPolicy" + }); + } + } + }; + const deleteFolder = async ({ projectId, actor, @@ -479,8 +542,7 @@ export const secretFolderServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -498,6 +560,8 @@ export const secretFolderServiceFactory = ({ message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found` }); + await $checkFolderPolicy({ projectId, environment, parentId: parentFolder.id }); + const [doc] = await folderDAL.delete( { envId: env.id, @@ -562,8 +626,7 @@ export const secretFolderServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const env = await projectEnvDAL.findOne({ projectId, slug: environment }); @@ -631,8 +694,7 @@ export const secretFolderServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const envs = await projectEnvDAL.findBySlugs(projectId, environments); @@ -673,8 +735,7 @@ export const secretFolderServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const envs = await projectEnvDAL.findBySlugs(projectId, environments); @@ -709,8 +770,7 @@ export const secretFolderServiceFactory = ({ actorId, projectId: folder.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const [folderWithPath] = await folderDAL.findSecretPathByFolderIds(folder.projectId, [folder.id]); @@ -738,8 +798,7 @@ export const secretFolderServiceFactory = ({ actorId: actor.id, projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId: actor.orgId }); const envs = await projectEnvDAL.findBySlugs(projectId, environments); @@ -766,8 +825,7 @@ export const secretFolderServiceFactory = ({ actorId: actor.id, projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId: actor.orgId }); const environments = await projectEnvDAL.find({ projectId }); diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index 403484fc2..297c5d01f 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -2,7 +2,7 @@ import path from "node:path"; import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType, TableName } from "@app/db/schemas"; +import { TableName } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { hasSecretReadValueOrDescribePermission, @@ -87,8 +87,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); // check if user has permission to import into destination path @@ -205,8 +204,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -303,8 +301,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -378,8 +375,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); // check if user has permission to import into destination path @@ -455,8 +451,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, @@ -489,8 +484,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const filteredEnvironments = []; for (const environment of environments) { @@ -543,8 +537,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, @@ -593,8 +586,7 @@ export const secretImportServiceFactory = ({ actorId, projectId: folder.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -642,8 +634,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, @@ -678,8 +669,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, @@ -762,8 +752,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const filteredEnvironments = []; for (const environment of environments) { @@ -815,8 +804,7 @@ export const secretImportServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if ( permission.cannot( diff --git a/backend/src/services/secret-sync/1password/1password-sync-fns.ts b/backend/src/services/secret-sync/1password/1password-sync-fns.ts index 9305f2e3c..c2011c101 100644 --- a/backend/src/services/secret-sync/1password/1password-sync-fns.ts +++ b/backend/src/services/secret-sync/1password/1password-sync-fns.ts @@ -6,7 +6,6 @@ import { TOnePassListVariablesResponse, TOnePassSyncWithCredentials, TOnePassVariable, - TOnePassVariableDetails, TPostOnePassVariable, TPutOnePassVariable } from "@app/services/secret-sync/1password/1password-sync-types"; @@ -14,7 +13,10 @@ import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; -const listOnePassItems = async ({ instanceUrl, apiToken, vaultId }: TOnePassListVariables) => { +// This should not be changed or it may break existing logic +const VALUE_LABEL_DEFAULT = "value"; + +const listOnePassItems = async ({ instanceUrl, apiToken, vaultId, valueLabel }: TOnePassListVariables) => { const { data } = await request.get(`${instanceUrl}/v1/vaults/${vaultId}/items`, { headers: { Authorization: `Bearer ${apiToken}`, @@ -22,36 +24,49 @@ const listOnePassItems = async ({ instanceUrl, apiToken, vaultId }: TOnePassList } }); - const result: Record = {}; + const items: Record = {}; + const duplicates: Record = {}; for await (const s of data) { - const { data: secret } = await request.get( - `${instanceUrl}/v1/vaults/${vaultId}/items/${s.id}`, - { - headers: { - Authorization: `Bearer ${apiToken}`, - Accept: "application/json" - } - } - ); + // eslint-disable-next-line no-continue + if (s.category !== "API_CREDENTIAL") continue; - const value = secret.fields.find((f) => f.label === "value")?.value; - const fieldId = secret.fields.find((f) => f.label === "value")?.id; + if (items[s.title]) { + duplicates[s.id] = s.title; + // eslint-disable-next-line no-continue + continue; + } + + const { data: secret } = await request.get(`${instanceUrl}/v1/vaults/${vaultId}/items/${s.id}`, { + headers: { + Authorization: `Bearer ${apiToken}`, + Accept: "application/json" + } + }); + + const valueField = secret.fields.find((f) => f.label === valueLabel); // eslint-disable-next-line no-continue - if (!value || !fieldId) continue; + if (!valueField || !valueField.value || !valueField.id) continue; - result[s.title] = { + items[s.title] = { ...secret, - value, - fieldId + value: valueField.value, + fieldId: valueField.id }; } - return result; + return { items, duplicates }; }; -const createOnePassItem = async ({ instanceUrl, apiToken, vaultId, itemTitle, itemValue }: TPostOnePassVariable) => { +const createOnePassItem = async ({ + instanceUrl, + apiToken, + vaultId, + itemTitle, + itemValue, + valueLabel +}: TPostOnePassVariable) => { return request.post( `${instanceUrl}/v1/vaults/${vaultId}/items`, { @@ -63,7 +78,7 @@ const createOnePassItem = async ({ instanceUrl, apiToken, vaultId, itemTitle, it tags: ["synced-from-infisical"], fields: [ { - label: "value", + label: valueLabel, value: itemValue, type: "CONCEALED" } @@ -85,7 +100,9 @@ const updateOnePassItem = async ({ itemId, fieldId, itemTitle, - itemValue + itemValue, + valueLabel, + otherFields }: TPutOnePassVariable) => { return request.put( `${instanceUrl}/v1/vaults/${vaultId}/items/${itemId}`, @@ -98,9 +115,10 @@ const updateOnePassItem = async ({ }, tags: ["synced-from-infisical"], fields: [ + ...otherFields, { id: fieldId, - label: "value", + label: valueLabel, value: itemValue, type: "CONCEALED" } @@ -128,13 +146,18 @@ export const OnePassSyncFns = { const { connection, environment, - destinationConfig: { vaultId } + destinationConfig: { vaultId, valueLabel } } = secretSync; const instanceUrl = await getOnePassInstanceUrl(connection); const { apiToken } = connection.credentials; - const items = await listOnePassItems({ instanceUrl, apiToken, vaultId }); + const { items, duplicates } = await listOnePassItems({ + instanceUrl, + apiToken, + vaultId, + valueLabel: valueLabel || VALUE_LABEL_DEFAULT + }); for await (const entry of Object.entries(secretMap)) { const [key, { value }] = entry; @@ -148,10 +171,19 @@ export const OnePassSyncFns = { itemTitle: key, itemValue: value, itemId: items[key].id, - fieldId: items[key].fieldId + fieldId: items[key].fieldId, + valueLabel: valueLabel || VALUE_LABEL_DEFAULT, + otherFields: items[key].fields.filter((field) => field.label !== (valueLabel || VALUE_LABEL_DEFAULT)) }); } else { - await createOnePassItem({ instanceUrl, apiToken, vaultId, itemTitle: key, itemValue: value }); + await createOnePassItem({ + instanceUrl, + apiToken, + vaultId, + itemTitle: key, + itemValue: value, + valueLabel: valueLabel || VALUE_LABEL_DEFAULT + }); } } catch (error) { throw new SecretSyncError({ @@ -163,7 +195,28 @@ export const OnePassSyncFns = { if (secretSync.syncOptions.disableSecretDeletion) return; - for await (const [key, variable] of Object.entries(items)) { + // Delete duplicate item entries + for await (const [itemId, key] of Object.entries(duplicates)) { + // eslint-disable-next-line no-continue + if (!matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema)) continue; + + try { + await deleteOnePassItem({ + instanceUrl, + apiToken, + vaultId, + itemId + }); + } catch (error) { + throw new SecretSyncError({ + error, + secretKey: key + }); + } + } + + // Delete item entries that are not in secretMap + for await (const [key, item] of Object.entries(items)) { // eslint-disable-next-line no-continue if (!matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema)) continue; @@ -173,7 +226,7 @@ export const OnePassSyncFns = { instanceUrl, apiToken, vaultId, - itemId: variable.id + itemId: item.id }); } catch (error) { throw new SecretSyncError({ @@ -187,13 +240,18 @@ export const OnePassSyncFns = { removeSecrets: async (secretSync: TOnePassSyncWithCredentials, secretMap: TSecretMap) => { const { connection, - destinationConfig: { vaultId } + destinationConfig: { vaultId, valueLabel } } = secretSync; const instanceUrl = await getOnePassInstanceUrl(connection); const { apiToken } = connection.credentials; - const items = await listOnePassItems({ instanceUrl, apiToken, vaultId }); + const { items } = await listOnePassItems({ + instanceUrl, + apiToken, + vaultId, + valueLabel: valueLabel || VALUE_LABEL_DEFAULT + }); for await (const [key, item] of Object.entries(items)) { if (key in secretMap) { @@ -216,12 +274,19 @@ export const OnePassSyncFns = { getSecrets: async (secretSync: TOnePassSyncWithCredentials) => { const { connection, - destinationConfig: { vaultId } + destinationConfig: { vaultId, valueLabel } } = secretSync; const instanceUrl = await getOnePassInstanceUrl(connection); const { apiToken } = connection.credentials; - return listOnePassItems({ instanceUrl, apiToken, vaultId }); + const res = await listOnePassItems({ + instanceUrl, + apiToken, + vaultId, + valueLabel: valueLabel || VALUE_LABEL_DEFAULT + }); + + return Object.fromEntries(Object.entries(res.items).map(([key, item]) => [key, { value: item.value }])); } }; diff --git a/backend/src/services/secret-sync/1password/1password-sync-schemas.ts b/backend/src/services/secret-sync/1password/1password-sync-schemas.ts index 2f77a1dad..9ee4ca496 100644 --- a/backend/src/services/secret-sync/1password/1password-sync-schemas.ts +++ b/backend/src/services/secret-sync/1password/1password-sync-schemas.ts @@ -11,7 +11,8 @@ import { import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; const OnePassSyncDestinationConfigSchema = z.object({ - vaultId: z.string().trim().min(1, "Vault required").describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.vaultId) + vaultId: z.string().trim().min(1, "Vault required").describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.vaultId), + valueLabel: z.string().trim().optional().describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.valueLabel) }); const OnePassSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; diff --git a/backend/src/services/secret-sync/1password/1password-sync-types.ts b/backend/src/services/secret-sync/1password/1password-sync-types.ts index af4db7369..8e6c1da6a 100644 --- a/backend/src/services/secret-sync/1password/1password-sync-types.ts +++ b/backend/src/services/secret-sync/1password/1password-sync-types.ts @@ -14,29 +14,32 @@ export type TOnePassSyncWithCredentials = TOnePassSync & { connection: TOnePassConnection; }; +type Field = { + id: string; + type: string; // CONCEALED, STRING + label: string; + value: string; +}; + export type TOnePassVariable = { id: string; title: string; category: string; // API_CREDENTIAL, SECURE_NOTE, LOGIN, etc -}; - -export type TOnePassVariableDetails = TOnePassVariable & { - fields: { - id: string; - type: string; // CONCEALED, STRING - label: string; - value: string; - }[]; + fields: Field[]; }; export type TOnePassListVariablesResponse = TOnePassVariable[]; -export type TOnePassListVariables = { +type TOnePassBase = { apiToken: string; instanceUrl: string; vaultId: string; }; +export type TOnePassListVariables = TOnePassBase & { + valueLabel: string; +}; + export type TPostOnePassVariable = TOnePassListVariables & { itemTitle: string; itemValue: string; @@ -47,8 +50,9 @@ export type TPutOnePassVariable = TOnePassListVariables & { fieldId: string; itemTitle: string; itemValue: string; + otherFields: Field[]; }; -export type TDeleteOnePassVariable = TOnePassListVariables & { +export type TDeleteOnePassVariable = TOnePassBase & { itemId: string; }; diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index a8ff94e82..2acba91e5 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -868,7 +868,7 @@ export const secretSyncQueueFactory = ({ secretPath: folder?.path, environment: environment?.name, projectName: project.name, - syncUrl: `${appCfg.SITE_URL}/secret-manager/${projectId}/integrations/secret-syncs/${destination}/${secretSync.id}` + syncUrl: `${appCfg.SITE_URL}/projects/${projectId}/secret-manager/integrations/secret-syncs/${destination}/${secretSync.id}` } }); }; diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 3fdb7fea6..bd52c0b77 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; @@ -75,7 +74,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -111,7 +110,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -154,7 +153,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId: secretSync.projectId }); @@ -196,7 +195,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId: secretSync.projectId }); @@ -234,7 +233,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId }); @@ -314,7 +313,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId: secretSync.projectId }); @@ -430,7 +429,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId: secretSync.projectId }); @@ -507,7 +506,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId: secretSync.projectId }); @@ -579,7 +578,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId: secretSync.projectId }); @@ -645,7 +644,7 @@ export const secretSyncServiceFactory = ({ actorId: actor.id, actorAuthMethod: actor.authMethod, actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, + projectId: secretSync.projectId }); diff --git a/backend/src/services/secret-tag/secret-tag-service.ts b/backend/src/services/secret-tag/secret-tag-service.ts index 8a08c44dd..a4be06b4f 100644 --- a/backend/src/services/secret-tag/secret-tag-service.ts +++ b/backend/src/services/secret-tag/secret-tag-service.ts @@ -1,6 +1,5 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; @@ -29,8 +28,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Tags); @@ -61,8 +59,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe actorId, projectId: tag.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags); @@ -79,8 +76,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe actorId, projectId: tag.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags); @@ -97,8 +93,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe actorId, projectId: tag.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); @@ -114,8 +109,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe actorId, projectId: tag.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); @@ -128,8 +122,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index fc758b4f5..71180772f 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -2,14 +2,7 @@ import { ForbiddenError, MongoAbility, subject } from "@casl/ability"; import { Knex } from "knex"; import { z } from "zod"; -import { - ActionProjectType, - ProjectMembershipRole, - SecretsV2Schema, - SecretType, - TableName, - TSecretsV2 -} from "@app/db/schemas"; +import { ProjectMembershipRole, SecretsV2Schema, SecretType, TableName, TSecretsV2 } from "@app/db/schemas"; import { hasSecretReadValueOrDescribePermission, throwIfMissingSecretReadValueOrDescribePermission @@ -243,8 +236,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); @@ -387,8 +379,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (inputSecret.newSecretName === "") { @@ -615,8 +606,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); @@ -752,8 +742,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); } @@ -798,8 +787,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); @@ -898,8 +886,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!isInternal) { throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); @@ -952,8 +939,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); @@ -1221,8 +1207,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId: secret.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { @@ -1285,8 +1270,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environment, path); @@ -1497,8 +1481,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); @@ -1665,8 +1648,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const secretsToUpdateGroupByPath = groupBy(inputSecrets, (el) => el.secretPath || defaultSecretPath); @@ -2016,8 +1998,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); @@ -2173,8 +2154,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId: folder.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const canRead = @@ -2239,8 +2219,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!hasRole(ProjectMembershipRole.Admin)) @@ -2287,8 +2266,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const sourceFolder = await folderDAL.findBySecretPath(projectId, sourceEnvironment, sourceSecretPath); @@ -2674,8 +2652,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { @@ -2767,8 +2744,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment, @@ -2892,8 +2868,7 @@ export const secretV2BridgeServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const canRead = diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index 847ef17df..e12dc2ee3 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -3,7 +3,6 @@ import path from "path"; import RE2 from "re2"; import { - ActionProjectType, SecretEncryptionAlgo, SecretKeyEncoding, SecretType, @@ -185,8 +184,7 @@ export const recursivelyGetSecretPaths = ({ actorId: auth.actorId, projectId, actorAuthMethod: auth.actorAuthMethod, - actorOrgId: auth.actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId: auth.actorOrgId }); // Filter out paths that the user does not have permission to access, and paths that are not in the current path diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index 25b6e6572..d00ffa2f3 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -744,7 +744,7 @@ export const secretQueueFactory = ({ environment: jobPayload.environmentName, count: jobPayload.count, projectName: project.name, - integrationUrl: `${appCfg.SITE_URL}/secret-manager/${project.id}/integrations?selectedTab=native-integrations` + integrationUrl: `${appCfg.SITE_URL}/projects/${project.id}/secret-manager/integrations?selectedTab=native-integrations` } }); } diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 159bee8e3..ce03791f2 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -3,7 +3,6 @@ import { ForbiddenError, subject } from "@casl/ability"; import { - ActionProjectType, ProjectMembershipRole, ProjectUpgradeStatus, ProjectVersion, @@ -212,8 +211,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -330,8 +328,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -491,8 +488,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -607,8 +603,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); let paths: { folderId: string; path: string }[] = []; @@ -713,8 +708,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, @@ -819,8 +813,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretActions.Create, @@ -906,8 +899,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -1029,8 +1021,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretActions.Delete, @@ -2477,8 +2468,7 @@ export const secretServiceFactory = ({ actorId, projectId: folder.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const secretVersions = await secretVersionDAL.findBySecretId(secretId, { @@ -2568,8 +2558,7 @@ export const secretServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -2674,8 +2663,7 @@ export const secretServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan( @@ -2781,8 +2769,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!hasRole(ProjectMembershipRole.Admin)) @@ -2866,8 +2853,7 @@ export const secretServiceFactory = ({ actorId, projectId: project.id, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); const { botKey } = await projectBotService.getBotKey(project.id); @@ -3270,8 +3256,7 @@ export const secretServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); if (!hasRole(ProjectMembershipRole.Admin)) @@ -3299,8 +3284,7 @@ export const secretServiceFactory = ({ actorId: actor.id, projectId: params.projectId, actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId: actor.orgId }); const secrets = secretV2BridgeService.getSecretsByFolderMappings({ ...params, userId: actor.id }, permission); diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index d68b48d78..b2a27341d 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -3,7 +3,6 @@ import crypto from "node:crypto"; import { ForbiddenError, subject } from "@casl/ability"; import bcrypt from "bcrypt"; -import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, @@ -68,8 +67,7 @@ export const serviceTokenServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens); @@ -124,8 +122,7 @@ export const serviceTokenServiceFactory = ({ actorId, projectId: serviceToken.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens); @@ -159,8 +156,7 @@ export const serviceTokenServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens); @@ -216,7 +212,7 @@ export const serviceTokenServiceFactory = ({ substitutions: { tokenName: token.name, projectName: token.projectName, - url: `${appCfg.SITE_URL}/secret-manager/${token.projectId}/access-management?selectedTab=service-tokens` + url: `${appCfg.SITE_URL}/projects/${token.projectId}/secret-manager/access-management?selectedTab=service-tokens` } }); await serviceTokenDAL.update({ id: token.id }, { expiryNotificationSent: true }); diff --git a/backend/src/services/slack/slack-fns.ts b/backend/src/services/slack/slack-fns.ts index d4f5e29cc..bee414179 100644 --- a/backend/src/services/slack/slack-fns.ts +++ b/backend/src/services/slack/slack-fns.ts @@ -50,7 +50,7 @@ const buildSlackPayload = (notification: TNotification) => { *Secret path*: ${payload.secretPath || "/"} *Secret Key${payload.secretKeys.length > 1 ? "s" : ""}*: ${payload.secretKeys.join(", ")} -View the complete details <${appCfg.SITE_URL}/secret-manager/${payload.projectId}/approval?requestId=${ +View the complete details <${appCfg.SITE_URL}/projects/${payload.projectId}/secret-manager/approval?requestId=${ payload.requestId }|here>.`; diff --git a/backend/src/services/telemetry/telemetry-queue.ts b/backend/src/services/telemetry/telemetry-queue.ts index c3e5471b0..994ebbd38 100644 --- a/backend/src/services/telemetry/telemetry-queue.ts +++ b/backend/src/services/telemetry/telemetry-queue.ts @@ -7,13 +7,18 @@ import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { getServerCfg } from "../super-admin/super-admin-service"; import { TTelemetryDALFactory } from "./telemetry-dal"; -import { TELEMETRY_SECRET_OPERATIONS_KEY, TELEMETRY_SECRET_PROCESSED_KEY } from "./telemetry-service"; +import { + TELEMETRY_SECRET_OPERATIONS_KEY, + TELEMETRY_SECRET_PROCESSED_KEY, + TTelemetryServiceFactory +} from "./telemetry-service"; import { PostHogEventTypes } from "./telemetry-types"; type TTelemetryQueueServiceFactoryDep = { queueService: TQueueServiceFactory; keyStore: Pick; telemetryDAL: TTelemetryDALFactory; + telemetryService: TTelemetryServiceFactory; }; export type TTelemetryQueueServiceFactory = ReturnType; @@ -21,7 +26,8 @@ export type TTelemetryQueueServiceFactory = ReturnType { const appCfg = getConfig(); const postHog = @@ -48,6 +54,10 @@ export const telemetryQueueServiceFactory = ({ await keyStore.deleteItem(TELEMETRY_SECRET_OPERATIONS_KEY); }); + queueService.start(QueueName.TelemetryAggregatedEvents, async () => { + await telemetryService.processAggregatedEvents(); + }); + // every day at midnight a telemetry job executes on self-hosted instances // this sends some telemetry information like instance id secrets operated etc const startTelemetryCheck = async () => { @@ -60,11 +70,26 @@ export const telemetryQueueServiceFactory = ({ { pattern: "0 0 * * *", utc: true }, QueueName.TelemetryInstanceStats // just a job id ); + + // clear previous aggregated events job + await queueService.stopRepeatableJob( + QueueName.TelemetryAggregatedEvents, + QueueJobs.TelemetryAggregatedEvents, + { pattern: "*/5 * * * *", utc: true }, + QueueName.TelemetryAggregatedEvents // just a job id + ); + if (postHog) { await queueService.queue(QueueName.TelemetryInstanceStats, QueueJobs.TelemetryInstanceStats, undefined, { jobId: QueueName.TelemetryInstanceStats, repeat: { pattern: "0 0 * * *", utc: true } }); + + // Start aggregated events job (runs every five minutes) + await queueService.queue(QueueName.TelemetryAggregatedEvents, QueueJobs.TelemetryAggregatedEvents, undefined, { + jobId: QueueName.TelemetryAggregatedEvents, + repeat: { pattern: "*/5 * * * *", utc: true } + }); } }; @@ -72,6 +97,10 @@ export const telemetryQueueServiceFactory = ({ logger.error(err?.failedReason, `${QueueName.TelemetryInstanceStats}: failed`); }); + queueService.listen(QueueName.TelemetryAggregatedEvents, "failed", (err) => { + logger.error(err?.failedReason, `${QueueName.TelemetryAggregatedEvents}: failed`); + }); + return { startTelemetryCheck }; diff --git a/backend/src/services/telemetry/telemetry-service.ts b/backend/src/services/telemetry/telemetry-service.ts index e02cff8a4..eaab5bec6 100644 --- a/backend/src/services/telemetry/telemetry-service.ts +++ b/backend/src/services/telemetry/telemetry-service.ts @@ -1,3 +1,4 @@ +import { createHash, randomUUID } from "crypto"; import { PostHog } from "posthog-node"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; @@ -12,12 +13,49 @@ import { PostHogEventTypes, TPostHogEvent, TSecretModifiedEvent } from "./teleme export const TELEMETRY_SECRET_PROCESSED_KEY = "telemetry-secret-processed"; export const TELEMETRY_SECRET_OPERATIONS_KEY = "telemetry-secret-operations"; +export const POSTHOG_AGGREGATED_EVENTS = [PostHogEventTypes.SecretPulled]; +const TELEMETRY_AGGREGATED_KEY_EXP = 900; // 15mins + +// Bucket configuration +const TELEMETRY_BUCKET_COUNT = 30; +const TELEMETRY_BUCKET_NAMES = Array.from( + { length: TELEMETRY_BUCKET_COUNT }, + (_, i) => `bucket-${i.toString().padStart(2, "0")}` +); + +type AggregatedEventData = Record; +type SingleEventData = { + distinctId: string; + event: string; + properties: unknown; + organizationId: string; +}; + export type TTelemetryServiceFactory = ReturnType; export type TTelemetryServiceFactoryDep = { - keyStore: Pick; + keyStore: Pick< + TKeyStoreFactory, + "incrementBy" | "deleteItemsByKeyIn" | "setItemWithExpiry" | "getKeysByPattern" | "getItems" + >; licenseService: Pick; }; +const getBucketForDistinctId = (distinctId: string): string => { + // Use SHA-256 hash for consistent distribution + const hash = createHash("sha256").update(distinctId).digest("hex"); + + // Take first 8 characters and convert to number for better distribution + const hashNumber = parseInt(hash.substring(0, 8), 16); + const bucketIndex = hashNumber % TELEMETRY_BUCKET_COUNT; + + return TELEMETRY_BUCKET_NAMES[bucketIndex]; +}; + +export const createTelemetryEventKey = (event: string, distinctId: string): string => { + const bucketId = getBucketForDistinctId(distinctId); + return `telemetry-event-${event}-${bucketId}-${distinctId}-${randomUUID()}`; +}; + export const telemetryServiceFactory = ({ keyStore, licenseService }: TTelemetryServiceFactoryDep) => { const appCfg = getConfig(); @@ -64,11 +102,33 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme const instanceType = licenseService.getInstanceType(); // capture posthog only when its cloud or signup event happens in self-hosted if (instanceType === InstanceType.Cloud || event.event === PostHogEventTypes.UserSignedUp) { - postHog.capture({ - event: event.event, - distinctId: event.distinctId, - properties: event.properties - }); + if (event.organizationId) { + try { + postHog.groupIdentify({ groupType: "organization", groupKey: event.organizationId }); + } catch (error) { + logger.error(error, "Failed to identify PostHog organization"); + } + } + if (POSTHOG_AGGREGATED_EVENTS.includes(event.event)) { + const eventKey = createTelemetryEventKey(event.event, event.distinctId); + await keyStore.setItemWithExpiry( + eventKey, + TELEMETRY_AGGREGATED_KEY_EXP, + JSON.stringify({ + distinctId: event.distinctId, + event: event.event, + properties: event.properties, + organizationId: event.organizationId + }) + ); + } else { + postHog.capture({ + event: event.event, + distinctId: event.distinctId, + properties: event.properties, + ...(event.organizationId ? { groups: { organization: event.organizationId } } : {}) + }); + } return; } @@ -89,6 +149,160 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme } }; + const aggregateGroupProperties = (events: SingleEventData[]): AggregatedEventData => { + const aggregatedData: AggregatedEventData = {}; + + // Set the total count + aggregatedData.count = events.length; + + events.forEach((event) => { + if (!event.properties) return; + + Object.entries(event.properties as Record).forEach(([key, value]: [string, unknown]) => { + if (Array.isArray(value)) { + // For arrays, count occurrences of each item + const existingCounts = + aggregatedData[key] && + typeof aggregatedData[key] === "object" && + aggregatedData[key]?.constructor === Object + ? (aggregatedData[key] as Record) + : {}; + + value.forEach((item) => { + const itemKey = typeof item === "object" ? JSON.stringify(item) : String(item); + existingCounts[itemKey] = (existingCounts[itemKey] || 0) + 1; + }); + + aggregatedData[key] = existingCounts; + } else if (typeof value === "object" && value?.constructor === Object) { + // For objects, count occurrences of each field value + const existingCounts = + aggregatedData[key] && + typeof aggregatedData[key] === "object" && + aggregatedData[key]?.constructor === Object + ? (aggregatedData[key] as Record) + : {}; + + if (value) { + Object.values(value).forEach((fieldValue) => { + const valueKey = typeof fieldValue === "object" ? JSON.stringify(fieldValue) : String(fieldValue); + existingCounts[valueKey] = (existingCounts[valueKey] || 0) + 1; + }); + } + aggregatedData[key] = existingCounts; + } else if (typeof value === "number") { + // For numbers, add to existing sum + aggregatedData[key] = ((aggregatedData[key] as number) || 0) + value; + } else if (value !== undefined && value !== null) { + // For other types (strings, booleans, etc.), count occurrences + const stringValue = String(value); + const existingValue = aggregatedData[key]; + + if (!existingValue) { + aggregatedData[key] = { [stringValue]: 1 }; + } else if (existingValue && typeof existingValue === "object" && existingValue.constructor === Object) { + const countObject = existingValue as Record; + countObject[stringValue] = (countObject[stringValue] || 0) + 1; + } else { + const oldValue = String(existingValue); + aggregatedData[key] = { + [oldValue]: 1, + [stringValue]: 1 + }; + } + } + }); + }); + + return aggregatedData; + }; + + const processBucketEvents = async (eventType: string, bucketId: string) => { + if (!postHog) return 0; + + try { + const bucketPattern = `telemetry-event-${eventType}-${bucketId}-*`; + const bucketKeys = await keyStore.getKeysByPattern(bucketPattern); + + if (bucketKeys.length === 0) return 0; + + const bucketEvents = await keyStore.getItems(bucketKeys); + let bucketEventsParsed: SingleEventData[] = []; + + try { + bucketEventsParsed = bucketEvents + .filter((event) => event !== null) + .map((event) => JSON.parse(event as string) as SingleEventData); + } catch (error) { + logger.error(error, `Failed to parse bucket events for ${eventType} in ${bucketId}`); + return 0; + } + + const eventsGrouped = new Map(); + + bucketEventsParsed.forEach((event) => { + const key = JSON.stringify({ id: event.distinctId, org: event.organizationId }); + if (!eventsGrouped.has(key)) { + eventsGrouped.set(key, []); + } + eventsGrouped.get(key)!.push(event); + }); + + if (eventsGrouped.size === 0) return 0; + + for (const [eventsKey, events] of eventsGrouped) { + const key = JSON.parse(eventsKey) as { id: string; org?: string }; + if (key.org) { + try { + postHog.groupIdentify({ groupType: "organization", groupKey: key.org }); + } catch (error) { + logger.error(error, "Failed to identify PostHog organization"); + } + } + const properties = aggregateGroupProperties(events); + + postHog.capture({ + event: `${eventType} aggregated`, + distinctId: key.id, + properties, + ...(key.org ? { groups: { organization: key.org } } : {}) + }); + } + + // Clean up processed data for this bucket + await keyStore.deleteItemsByKeyIn(bucketKeys); + + logger.info(`Processed ${bucketEventsParsed.length} events from bucket ${bucketId} for ${eventType}`); + return bucketEventsParsed.length; + } catch (error) { + logger.error(error, `Failed to process bucket ${bucketId} for ${eventType}`); + return 0; + } + }; + + const processAggregatedEvents = async () => { + if (!postHog) return; + + for (const eventType of POSTHOG_AGGREGATED_EVENTS) { + let totalProcessed = 0; + + logger.info(`Starting bucket processing for ${eventType}`); + + // Process each bucket sequentially to control memory usage + for (const bucketId of TELEMETRY_BUCKET_NAMES) { + try { + // eslint-disable-next-line no-await-in-loop + const processed = await processBucketEvents(eventType, bucketId); + totalProcessed += processed; + } catch (error) { + logger.error(error, `Failed to process bucket ${bucketId} for ${eventType}`); + } + } + + logger.info(`Completed processing ${totalProcessed} total events for ${eventType}`); + } + }; + const flushAll = async () => { if (postHog) { await postHog.shutdownAsync(); @@ -98,6 +312,8 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme return { sendLoopsEvent, sendPostHogEvents, - flushAll + processAggregatedEvents, + flushAll, + getBucketForDistinctId }; }; diff --git a/backend/src/services/telemetry/telemetry-types.ts b/backend/src/services/telemetry/telemetry-types.ts index ab8fc51c5..7e2027f25 100644 --- a/backend/src/services/telemetry/telemetry-types.ts +++ b/backend/src/services/telemetry/telemetry-types.ts @@ -1,3 +1,13 @@ +import { + IdentityActor, + KmipClientActor, + PlatformActor, + ScimClientActor, + ServiceActor, + UnknownUserActor, + UserActor +} from "@app/ee/services/audit-log/audit-log-types"; + export enum PostHogEventTypes { SecretPush = "secrets pushed", SecretPulled = "secrets pulled", @@ -40,6 +50,14 @@ export type TSecretModifiedEvent = { secretPath: string; channel?: string; userAgent?: string; + actor?: + | UserActor + | IdentityActor + | ServiceActor + | ScimClientActor + | PlatformActor + | UnknownUserActor + | KmipClientActor; }; }; @@ -214,7 +232,7 @@ export type TInvalidateCacheEvent = { }; }; -export type TPostHogEvent = { distinctId: string } & ( +export type TPostHogEvent = { distinctId: string; organizationId?: string } & ( | TSecretModifiedEvent | TAdminInitEvent | TUserSignedUpEvent diff --git a/backend/src/services/webhook/webhook-service.ts b/backend/src/services/webhook/webhook-service.ts index eb58ee5bd..ba3f2170a 100644 --- a/backend/src/services/webhook/webhook-service.ts +++ b/backend/src/services/webhook/webhook-service.ts @@ -1,6 +1,6 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType, TWebhooksInsert } from "@app/db/schemas"; +import { TWebhooksInsert } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { NotFoundError } from "@app/lib/errors"; @@ -54,8 +54,7 @@ export const webhookServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks); const env = await projectEnvDAL.findOne({ projectId, slug: environment }); @@ -93,8 +92,7 @@ export const webhookServiceFactory = ({ actorId, projectId: webhook.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks); @@ -111,8 +109,7 @@ export const webhookServiceFactory = ({ actorId, projectId: webhook.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks); @@ -129,8 +126,7 @@ export const webhookServiceFactory = ({ actorId, projectId: webhook.projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); const project = await projectDAL.findById(webhook.projectId); @@ -181,8 +177,7 @@ export const webhookServiceFactory = ({ actorId, projectId, actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.Any + actorOrgId }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); diff --git a/cli/packages/cmd/user.go b/cli/packages/cmd/user.go index 6c7d54d46..3b0970403 100644 --- a/cli/packages/cmd/user.go +++ b/cli/packages/cmd/user.go @@ -114,6 +114,11 @@ var userGetTokenCmd = &cobra.Command{ loggedInUserDetails = util.EstablishUserLoginSession() } + plain, err := cmd.Flags().GetBool("plain") + if err != nil { + util.HandleError(err, "[infisical user get token]: Unable to get plain flag") + } + if err != nil { util.HandleError(err, "[infisical user get token]: Unable to get logged in user token") } @@ -135,8 +140,12 @@ var userGetTokenCmd = &cobra.Command{ util.HandleError(err, "[infisical user get token]: Unable to parse token payload") } - fmt.Println("Session ID:", tokenPayload.TokenVersionId) - fmt.Println("Token:", loggedInUserDetails.UserCredentials.JTWToken) + if plain { + fmt.Println(loggedInUserDetails.UserCredentials.JTWToken) + } else { + fmt.Println("Session ID:", tokenPayload.TokenVersionId) + fmt.Println("Token:", loggedInUserDetails.UserCredentials.JTWToken) + } }, } @@ -240,7 +249,10 @@ var domainCmd = &cobra.Command{ func init() { updateCmd.AddCommand(domainCmd) userCmd.AddCommand(updateCmd) + + userGetTokenCmd.Flags().Bool("plain", false, "print token without formatting") userGetCmd.AddCommand(userGetTokenCmd) + userCmd.AddCommand(userGetCmd) userCmd.AddCommand(switchCmd) rootCmd.AddCommand(userCmd) diff --git a/company/handbook/spending-money.mdx b/company/handbook/spending-money.mdx index 864984f10..e3a92b2f4 100644 --- a/company/handbook/spending-money.mdx +++ b/company/handbook/spending-money.mdx @@ -11,9 +11,9 @@ Fairly frequently, you might run into situations when you need to spend company As a perk of working at Infisical, we cover some of your meal expenses. -HQ team members: meals and unlimited snacks are provided on-site at no cost. +**HQ team members**: meals and unlimited snacks are provided **on-site** at no cost. -Remote team members: a food stipend is allocated based on location. +**Remote team members**: a food stipend is allocated based on location. # Trivial expenses @@ -27,21 +27,28 @@ This means expenses that are: Please spend money in a way that you think is in the best interest of the company. -## Saving receipts -Make sure you keep copies for all receipts. If you expense something on a company card and cannot provide a receipt, this may be deducted from your pay. +# Travel -You should default to using your company card in all cases - it has no transaction fees. If using your personal card is unavoidable, please reach out to Maidul to get it reimbursed manually. +If you need to travel on Infisical’s behalf for in-person onboarding, meeting customers, and offsites, again please spend money in the best interests of the company. -## Training +We do not pre-approve your travel expenses, and trust team members to make the right decisions here. Some guidance: + +- Please find a flight ticket that is reasonably priced. We all travel economy by default – we cannot afford for folks to fly premium or business class. Feel free to upgrade using your personal money/airmiles if you’d like to. +- Feel free to pay for the Uber/subway/bus to and from the airport with your Brex card. +- For business travel, Infisical will cover reasonable expenses for breakfast, lunch, and dinner. +- When traveling internationally, Infisical does not cover roaming charges for your phone. You can expense a reasonable eSIM, which usually is no more than $20. + + +Note that this only applies to business travel. It is not applicable for personal travel or day-to-day commuting. + -For engineers, you’re welcome to take an approved Udemy course. Please reach out to Maidul. For the GTM team, you may buy a book a month if it’s relevant to your work. # Equipment Infisical is a remote first company so we understand the importance of having a comfortable work setup. To support this, we provide allowances for essential office equipment. -### Desk & Chair +### 1. Desk & Chair Most people already have a comfortable desk and chair, but if you need an upgrade, we offer the following allowances. While we're not yet able to provide the latest and greatest, we strive to be reasonable given the stage of our company. @@ -50,10 +57,10 @@ While we're not yet able to provide the latest and greatest, we strive to be rea **Chair**: $150 USD -### Laptop +### 2. Laptop Each team member will receive a company-issued Macbook Pro before they start their first day. -### Notes +### 3. Notes 1. All equipment purchased using company allowances remains the property of Infisical. 2. Keep all receipts for equipment purchases and submit them for reimbursement. @@ -65,6 +72,28 @@ This is because we don't yet have a formal HR department to handle such logistic For any equipment related questions, please reach out to Maidul. -## Brex +# Brex We use Brex as our primary credit card provider. Don't have a company card yet? Reach out to Maidul. + +### Budgets + +You will generally have multiple budgets assigned to you. "General Company Expenses" primarily covers quick SaaS purchases (not food). Remote team members should have a "Lunch Stipend" budget that applies to food. + +If your position involves a lot of travel, you may also have a "Travel" budget that applies to expenses related to business travel (e.g., you can not use it for transportation or food during personal travel). + +### Saving receipts + +Make sure you keep copies for all receipts. If you expense something on a company card and cannot provide a receipt, this may be deducted from your pay. + +You should default to using your company card in all cases - it has no transaction fees. If using your personal card is unavoidable, please reach out to Maidul to get it reimbursed manually. + +### ​Need a one-off budget increase? + +You can do this directly within Brex - just request the amount and duration for the relevant budget in the app, and your hiring manager will automatically be notified for approval. + + +# Training + +For engineers, you’re welcome to take an approved Udemy course. Please reach out to Maidul. For the GTM team, you may buy a book a month if it’s relevant to your work. + diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 590e17763..209c6e62e 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -4,7 +4,7 @@ services: nginx: container_name: infisical-dev-nginx image: nginx - restart: always + restart: "always" ports: - 8080:80 - 8443:443 diff --git a/docs/api-reference/endpoints/tls-cert-auth/attach.mdx b/docs/api-reference/endpoints/tls-cert-auth/attach.mdx new file mode 100644 index 000000000..35c3b87e9 --- /dev/null +++ b/docs/api-reference/endpoints/tls-cert-auth/attach.mdx @@ -0,0 +1,4 @@ +--- +title: "Attach" +openapi: "POST /api/v1/auth/tls-cert-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/tls-cert-auth/login.mdx b/docs/api-reference/endpoints/tls-cert-auth/login.mdx new file mode 100644 index 000000000..0069ef1b7 --- /dev/null +++ b/docs/api-reference/endpoints/tls-cert-auth/login.mdx @@ -0,0 +1,4 @@ +--- +title: "Login" +openapi: "POST /api/v1/auth/tls-cert-auth/login" +--- diff --git a/docs/api-reference/endpoints/tls-cert-auth/retrieve.mdx b/docs/api-reference/endpoints/tls-cert-auth/retrieve.mdx new file mode 100644 index 000000000..d59b31d11 --- /dev/null +++ b/docs/api-reference/endpoints/tls-cert-auth/retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/auth/tls-cert-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/tls-cert-auth/revoke.mdx b/docs/api-reference/endpoints/tls-cert-auth/revoke.mdx new file mode 100644 index 000000000..0d3ccda65 --- /dev/null +++ b/docs/api-reference/endpoints/tls-cert-auth/revoke.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke" +openapi: "DELETE /api/v1/auth/tls-cert-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/tls-cert-auth/update.mdx b/docs/api-reference/endpoints/tls-cert-auth/update.mdx new file mode 100644 index 000000000..3bb8892ea --- /dev/null +++ b/docs/api-reference/endpoints/tls-cert-auth/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/auth/tls-cert-auth/identities/{identityId}" +--- diff --git a/docs/cli/commands/user.mdx b/docs/cli/commands/user.mdx index 43a6111e1..108b1f23b 100644 --- a/docs/cli/commands/user.mdx +++ b/docs/cli/commands/user.mdx @@ -35,19 +35,40 @@ infisical user update domain Use this command to get your current Infisical access token and session information. This command requires you to be logged in. - The command will display: +The command will display: - - Your session ID - - Your full JWT access token +- Your session ID +- Your full JWT access token - ```bash - infisical user get token - ``` +```bash +infisical user get token +``` - Example output: +Example output: + +```bash +Session ID: abc123-xyz-456 +Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +### Flags + + + Output only the JWT token without formatting (no session ID) + + Default value: `false` + + ```bash + # Example + infisical user get token --plain + ``` + + Example output: + + ```bash + eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ``` + + - ```bash - Session ID: abc123-xyz-456 - Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - ``` diff --git a/docs/docs.json b/docs/docs.json index b611e6522..015ecb3c4 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -288,6 +288,7 @@ "documentation/platform/identities/kubernetes-auth", "documentation/platform/identities/oci-auth", "documentation/platform/identities/token-auth", + "documentation/platform/identities/tls-cert-auth", "documentation/platform/identities/universal-auth", { "group": "OIDC Auth", @@ -754,6 +755,16 @@ "api-reference/endpoints/alicloud-auth/revoke" ] }, + { + "group": "TLS Certificate Auth", + "pages": [ + "api-reference/endpoints/tls-cert-auth/login", + "api-reference/endpoints/tls-cert-auth/attach", + "api-reference/endpoints/tls-cert-auth/retrieve", + "api-reference/endpoints/tls-cert-auth/update", + "api-reference/endpoints/tls-cert-auth/revoke" + ] + }, { "group": "AWS Auth", "pages": [ diff --git a/docs/documentation/platform/identities/tls-cert-auth.mdx b/docs/documentation/platform/identities/tls-cert-auth.mdx new file mode 100644 index 000000000..e11d0c06e --- /dev/null +++ b/docs/documentation/platform/identities/tls-cert-auth.mdx @@ -0,0 +1,176 @@ +--- +title: TLS Certificate Auth +description: "Learn how to authenticate with Infisical using TLS Certificate." +--- + +**TLS Certificate Auth** is an authentication method that verifies a user's TLS Client certificate using the provided CA Certificate, allowing secure access to Infisical resources. + +## Diagram + +The following sequence diagram illustrates the TLS Certificate Auth workflow for authenticating users with Infisical. + +```mermaid +sequenceDiagram + participant Client + participant Infisical + + Note over Client,Client: Step 1: Setup your TLS request with the client certificate + + Note over Client,Infisical: Step 2: Login Operation + Client->>Infisical: Send request to /api/v1/auth/tls-cert-auth/login + + Note over Infisical: Step 3: Request verification using CA Certificate + + Infisical->>Client: Return short-lived access token + + Note over Client,Infisical: Step 5: Access Infisical API with token + Client->>Infisical: Make authenticated requests using the short-lived access token +``` + +## Concept + +At a high level, Infisical authenticates the client's TLS Certificate by verifying its identity and checking that it meets specific requirements (e.g., it is bound to the allowed common names) at the `/api/v1/auth/tls-cert-auth/login` endpoint. If successful, Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API. + +To be more specific: + +1. The client sends a TLS request with the client certificate to Infisical at the `/api/v1/auth/tls-cert-auth/login` endpoint. +2. Infisical verifies the incoming request using the provided CA certificate. +3. Infisical checks the user's properties against set criteria such as Allowed Common Names. +4. If all checks pass, Infisical returns a short-lived access token that the client can use to make authenticated requests to the Infisical API. + + + Most of the time, the Infisical server will be behind a load balancer or + proxy. To propagate the TLS certificate from the load balancer to the + instance, you can configure the TLS to send the client certificate as a header + that is set as an [environment + variable](/self-hosting/configuration/envars#param-identity-tls-cert-auth-client-certificate-header-key). + + +## Guide + +In the following steps, we explore how to create and use identities for your workloads and applications on TLS Certificate to +access the Infisical API using request signing. + + + **Self-Hosted Users:** Before using TLS Certificate Auth, please review the + [Security Requirements for Self-Hosted + Deployments](#security-requirements-for-self-hosted-deployments) section below + to ensure proper configuration and avoid security vulnerabilities. + + +### Creating an identity + +To create an identity, head to your Organization Settings > Access Control > [Identities](https://app.infisical.com/organization/access-management?selectedTab=identities) and press **Create identity**. + +![identities organization](/images/platform/identities/identities-org.png) + +When creating an identity, you specify an organization-level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > [Organization Roles](https://app.infisical.com/organization/access-management?selectedTab=roles). + +![identities organization create](/images/platform/identities/identities-org-create.png) + +Input some details for your new identity: + +- **Name (required):** A friendly name for the identity. +- **Role (required):** A role from the [**Organization Roles**](https://app.infisical.com/organization/access-management?selectedTab=roles) tab for the identity to assume. The organization role assigned will determine what organization-level resources this identity can have access to. + +Once you've created an identity, you'll be redirected to a page where you can manage the identity. + +![identities page](/images/platform/identities/identities-page.png) + +Since the identity has been configured with [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) by default, you should reconfigure it to use TLS Certificate Auth instead. To do this, click the cog next to **Universal Auth** and then select **Delete** in the options dropdown. + +![identities press cog](/images/platform/identities/identities-press-cog.png) + +![identities page remove default auth](/images/platform/identities/identities-page-remove-default-auth.png) + +Now create a new TLS Certificate Auth Method. + +![identities create tls cert auth method](/images/platform/identities/identities-tls-cert-auth-create-auth.png) + +Here's some information about each field: + +- **CA Certificate:** A PEM encoded CA Certificate used to validate incoming TLS request client certificate. +- **Allowed Common Names:** A comma separated list of client certificate common names allowed. +- **Access Token TTL (default is `2592000` equivalent to 30 days):** The lifetime for an access token in seconds. This value will be referenced at renewal time. +- **Access Token Max TTL (default is `2592000` equivalent to 30 days):** The maximum lifetime for an access token in seconds. This value will be referenced at renewal time. +- **Access Token Max Number of Uses (default is `0`):** The maximum number of times that an access token can be used; a value of `0` implies an infinite number of uses. +- **Access Token Trusted IPs:** The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address. + +### Adding an identity to a project + +In order to allow an identity to access project-level resources such as secrets, you must add it to the relevant projects. + +To do this, head over to the project you want to add the identity to and navigate to Project Settings > Access Control > Machine Identities and press **Add Identity**. + +![identities project](/images/platform/identities/identities-project.png) + +Select the identity you want to add to the project and the project-level role you want it to assume. The project role given to the identity will determine what project-level resources this identity can access. + +![identities project create](/images/platform/identities/identities-project-create.png) + +### Accessing the Infisical API with the identity + +To access the Infisical API as the identity, you need to send a TLS request to `/api/v1/auth/tls-cert-auth/login` endpoint. + +Below is an example of how you can authenticate with Infisical using NodeJS. + +```javascript +const fs = require("fs"); +const https = require("https"); +const axios = require("axios"); + +try { + const clientCertificate = fs.readFileSync("client-cert.pem", "utf8"); + const clientKeyCertificate = fs.readFileSync("client-key.pem", "utf8"); + + const infisicalUrl = "https://app.infisical.com"; // or your self-hosted Infisical URL + const identityId = ""; + + // Create HTTPS agent with client certificate and key + const httpsAgent = new https.Agent({ + cert: clientCertificate, + key: clientKeyCertificate, + }); + + const { data } = await axios.post( + `${infisicalUrl}/api/v1/auth/tls-cert-auth/login`, + { + identityId, + }, + { + httpsAgent: httpsAgent, // Pass the HTTPS agent with client cert + } + ); + + console.log("result data: ", data); // access token here +} catch (err) { + console.error(err); +} +``` + + + Each identity access token has a time-to-live (TTL) which you can infer from the response of the login operation; the default TTL is `7200` seconds, which can be adjusted. + + If an identity access token expires, it can no longer access the Infisical API. A new access token should be obtained by performing another login operation. + + + +## Security Requirements for Self-Hosted Deployments + +ALL TLS cert [login](/api-reference/endpoints/tls-cert-auth/login) requests **MUST** go through a load balancer/proxy that verifies certificate ownership: + +- **REQUIRED:** Configure your load balancer/proxy to **require a proper TLS handshake with client certificate presentation** +- **REQUIRED:** Ensure the load balancer **verifies the client possesses the private key** corresponding to the certificate (standard TLS behavior) +- **NEVER** allow direct connections to Infisical for TLS cert auth - this enables header injection attacks +- **NEVER** forward certificate headers without requiring proper TLS certificate presentation + +### Load Balancer Configuration Examples + +- **AWS ALB:** Use mTLS listeners which require client certificate presentation during the TLS handshake +- **NGINX/HAProxy:** Configure SSL client certificate requirement with proper TLS handshake verification + + + Infisical will handle the actual certificate validation against the configured + CA certificate and determine authentication permissions. The load balancer's + role is to ensure certificate ownership, not certificate trust validation. + diff --git a/docs/favicon.png b/docs/favicon.png index 45c9b868e..2a2ec8d1c 100644 Binary files a/docs/favicon.png and b/docs/favicon.png differ diff --git a/docs/images/platform/identities/identities-tls-cert-auth-create-auth.png b/docs/images/platform/identities/identities-tls-cert-auth-create-auth.png new file mode 100644 index 000000000..2e66633e7 Binary files /dev/null and b/docs/images/platform/identities/identities-tls-cert-auth-create-auth.png differ diff --git a/docs/images/secret-syncs/1password/configure-destination.png b/docs/images/secret-syncs/1password/configure-destination.png index af5191486..786bd1501 100644 Binary files a/docs/images/secret-syncs/1password/configure-destination.png and b/docs/images/secret-syncs/1password/configure-destination.png differ diff --git a/docs/images/secret-syncs/gcp-secret-manager/enable-service-usage-api.png b/docs/images/secret-syncs/gcp-secret-manager/enable-service-usage-api.png new file mode 100644 index 000000000..ac71e0243 Binary files /dev/null and b/docs/images/secret-syncs/gcp-secret-manager/enable-service-usage-api.png differ diff --git a/docs/integrations/platforms/kubernetes-csi.mdx b/docs/integrations/platforms/kubernetes-csi.mdx index da1d4d019..2dfcb448a 100644 --- a/docs/integrations/platforms/kubernetes-csi.mdx +++ b/docs/integrations/platforms/kubernetes-csi.mdx @@ -44,8 +44,11 @@ Currently, the Infisical CSI provider only supports static secrets. ### Install Secrets Store CSI Driver -In order to use the Infisical CSI provider, you will first have to install the [Secrets Store CSI driver](https://secrets-store-csi-driver.sigs.k8s.io/getting-started/installation) to your cluster. It is important that you define -the audience value for token requests as demonstrated below. The Infisical CSI provider will **NOT WORK** if this is not set. +In order to use the Infisical CSI provider, you will first have to install the [Secrets Store CSI driver](https://secrets-store-csi-driver.sigs.k8s.io/getting-started/installation) to your cluster. + +#### Standard Installation + +For most Kubernetes clusters, use the following installation: ```bash helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts @@ -62,7 +65,7 @@ helm install csi secrets-store-csi-driver/secrets-store-csi-driver \ The flags configure the following: -- `tokenRequests[0].audience=infisical`: Sets the audience value for service account token authentication (required) +- `tokenRequests[0].audience=infisical`: Sets the audience value for service account token authentication (recommended for environments that support custom audiences) - `enableSecretRotation=true`: Enables automatic secret updates from Infisical - `rotationPollInterval=2m`: Checks for secret updates every 2 minutes - `syncSecret.enabled=true`: Enables syncing secrets to Kubernetes secrets @@ -76,6 +79,25 @@ The flags configure the following: for the CSI driver. +#### Installation for Environments Without Custom Audience Support + +Some Kubernetes environments (such as AWS EKS) don't support custom audiences and will reject tokens with non-default audiences. For these environments, use this installation instead: + +```bash +helm install csi secrets-store-csi-driver/secrets-store-csi-driver \ +--namespace=kube-system \ +--set enableSecretRotation=true \ +--set rotationPollInterval=2m \ +--set "syncSecret.enabled=true" \ +``` + + + **Environments without custom audience support**: Do not set a custom audience + when installing the CSI driver in environments that reject custom audiences. + Instead, use the installation above and set `useDefaultAudience: "true"` in + your SecretProviderClass configuration. + + ### Install Infisical CSI Provider You would then have to install the Infisical CSI provider to your cluster. @@ -107,9 +129,12 @@ a machine identity with [Kubernetes authentication](https://infisical.com/docs/d You can refer to the documentation for setting it up [here](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth#guide). - The allowed audience field of the Kubernetes authentication settings should - match the audience specified for the Secrets Store CSI driver during - installation. + **Important**: The "Allowed Audience" field in your machine identity's + Kubernetes authentication settings must match your CSI driver installation. If + you used the standard installation with `tokenRequests[0].audience=infisical`, + set the "Allowed Audience" field to `infisical`. If you used the installation + for environments without custom audience support, leave the "Allowed Audience" + field empty. ### Creating Secret Provider Class @@ -117,6 +142,8 @@ You can refer to the documentation for setting it up [here](https://infisical.co With the Secrets Store CSI driver and the Infisical CSI provider installed, create a Kubernetes [SecretProviderClass](https://secrets-store-csi-driver.sigs.k8s.io/concepts.html#secretproviderclass) resource to establish the connection between the CSI driver and the Infisical CSI provider for secret retrieval. You can create as many Secret Provider Classes as needed for your cluster. +#### Standard Configuration + ```yaml apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass @@ -139,6 +166,41 @@ spec: secretKey: "APP_SECRET" ``` +#### Configuration for Environments Without Custom Audience Support + +For environments that don't support custom audiences (such as AWS EKS), use this configuration instead: + +```yaml +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: my-infisical-app-csi-provider +spec: + provider: infisical + parameters: + infisicalUrl: "https://app.infisical.com" + authMethod: "kubernetes" + useDefaultAudience: "true" + identityId: "ad2f8c67-cbe2-417a-b5eb-1339776ec0b3" + projectId: "09eda1f8-85a3-47a9-8a6f-e27f133b2a36" + envSlug: "prod" + secrets: | + - secretPath: "/" + fileName: "dbPassword" + secretKey: "DB_PASSWORD" + - secretPath: "/app" + fileName: "appSecret" + secretKey: "APP_SECRET" +``` + + + **Key difference**: The only change from the standard configuration is the + addition of `useDefaultAudience: "true"`. This parameter tells the CSI + provider to use the default Kubernetes audience instead of a custom + "infisical" audience, which is required for environments that reject custom + audiences. + + The SecretProviderClass should be provisioned in the same namespace as the pod you intend to mount secrets to. @@ -189,6 +251,19 @@ spec: `infisical`. + + When set to `"true"`, the Infisical CSI provider will use the default + Kubernetes audience instead of a custom audience. This is required for + environments that don't support custom audiences (such as AWS EKS), which + reject tokens with non-default audiences. When using this option, do not set a + custom audience in the CSI driver installation. This defaults to `false`. + + When enabled, the CSI provider will dynamically create service account + tokens on-demand using the default Kubernetes audience, rather than using + pre-existing tokens from the CSI driver. + + + ### Using Secret Provider Class A pod can use the Secret Provider Class by mounting it as a CSI volume: @@ -252,6 +327,11 @@ kubectl logs csi-secrets-store-csi-driver-7h4jp -n=kube-system - Invalid machine identity configuration - Incorrect secret paths or keys +**Issues in environments without custom audience support:** + +- **Token authentication failed with custom audience**: If you're seeing authentication errors in environments that don't support custom audiences (such as AWS EKS), ensure you're using the installation without custom audience and have set `useDefaultAudience: "true"` in your SecretProviderClass +- **Audience not allowed errors**: Make sure the "Allowed Audience" field is left empty in your machine identity's Kubernetes authentication configuration when using environments that don't support custom audiences + ## Best Practices For additional guidance on setting this up for your production cluster, you can refer to the Secrets Store CSI driver documentation [here](https://secrets-store-csi-driver.sigs.k8s.io/topics/best-practices). diff --git a/docs/integrations/secret-syncs/1password.mdx b/docs/integrations/secret-syncs/1password.mdx index 6e2b96b4a..512db74e1 100644 --- a/docs/integrations/secret-syncs/1password.mdx +++ b/docs/integrations/secret-syncs/1password.mdx @@ -36,6 +36,7 @@ description: "Learn how to configure a 1Password Sync for Infisical." - **1Password Connection**: The 1Password Connection to authenticate with. - **Vault**: The 1Password vault to sync secrets to. + - **Value Label**: The label of the 1Password item field that will hold your secret value. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**. @@ -94,7 +95,8 @@ description: "Learn how to configure a 1Password Sync for Infisical." "initialSyncBehavior": "overwrite-destination" }, "destinationConfig": { - "vaultId": "..." + "vaultId": "...", + "valueLabel": "value" } }' ``` @@ -145,7 +147,8 @@ description: "Learn how to configure a 1Password Sync for Infisical." }, "destination": "1password", "destinationConfig": { - "vaultId": "..." + "vaultId": "...", + "valueLabel": "value" } } } @@ -160,4 +163,7 @@ description: "Learn how to configure a 1Password Sync for Infisical." Infisical can only perform CRUD operations on the following item types: - API Credentials + + It's the label of the 1Password item field which will hold your secret value. For example, if you were to sync Infisical secret 'foo: bar', the 1Password item equivalent would have an item title of 'foo', and a field on that item 'value: bar'. The field label 'value' is what gets changed by this option. + diff --git a/docs/integrations/secret-syncs/gcp-secret-manager.mdx b/docs/integrations/secret-syncs/gcp-secret-manager.mdx index 0be08a9a9..afce51a0c 100644 --- a/docs/integrations/secret-syncs/gcp-secret-manager.mdx +++ b/docs/integrations/secret-syncs/gcp-secret-manager.mdx @@ -7,9 +7,10 @@ description: "Learn how to configure a GCP Secret Manager Sync for Infisical." - Set up and add secrets to [Infisical Cloud](https://app.infisical.com) - Create a [GCP Connection](/integrations/app-connections/gcp) with the required **Secret Sync** permissions - - Enable **Cloud Resource Manager API** and **Secret Manager API** on your GCP project + - Enable **Cloud Resource Manager API**, **Secret Manager API**, and **Service Usage API** on your GCP project ![Secret Syncs Tab](/images/secret-syncs/gcp-secret-manager/enable-resource-manager-api.png) ![Secret Syncs Tab](/images/secret-syncs/gcp-secret-manager/enable-secret-manager-api.png) + ![Secret Syncs Tab](/images/secret-syncs/gcp-secret-manager/enable-service-usage-api.png) diff --git a/docs/logo/dark.svg b/docs/logo/dark.svg index f88594746..35da75049 100644 --- a/docs/logo/dark.svg +++ b/docs/logo/dark.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/docs/logo/light.svg b/docs/logo/light.svg index 16fc09e5e..0ab925e3b 100644 --- a/docs/logo/light.svg +++ b/docs/logo/light.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/docs/mint.json b/docs/mint.json deleted file mode 100644 index ed1dca7f5..000000000 --- a/docs/mint.json +++ /dev/null @@ -1,2269 +0,0 @@ -{ - "name": "Infisical", - "openapi": "https://app.infisical.com/api/docs/json", - "logo": { - "dark": "/logo/dark.svg", - "light": "/logo/light.svg", - "href": "https://infisical.com" - }, - "favicon": "/favicon.png", - "colors": { - "primary": "#26272b", - "light": "#97b31d", - "dark": "#A1B659", - "ultraLight": "#E7F256", - "ultraDark": "#8D9F4C", - "background": { - "light": "#ffffff", - "dark": "#0D1117" - }, - "anchors": { - "from": "#000000", - "to": "#707174" - } - }, - "modeToggle": { - "default": "light", - "isHidden": true - }, - "feedback": { - "suggestEdit": true, - "raiseIssue": true, - "thumbsRating": true - }, - "api": { - "baseUrl": ["https://app.infisical.com", "http://localhost:8080"] - }, - "topbarLinks": [ - { - "name": "Log In", - "url": "https://app.infisical.com/login" - } - ], - "topbarCtaButton": { - "name": "Start for Free", - "url": "https://app.infisical.com/signup" - }, - "tabs": [ - { - "name": "Integrations", - "url": "integrations" - }, - { - "name": "CLI", - "url": "cli" - }, - { - "name": "API Reference", - "url": "api-reference" - }, - { - "name": "SDKs", - "url": "sdks" - }, - { - "name": "Changelog", - "url": "changelog" - } - ], - "navigation": [ - { - "group": "Getting Started", - "pages": [ - "documentation/getting-started/introduction", - { - "group": "Quickstart", - "pages": ["documentation/guides/local-development"] - }, - { - "group": "Guides", - "pages": [ - "documentation/guides/introduction", - "documentation/guides/node", - "documentation/guides/python", - "documentation/guides/nextjs-vercel", - "documentation/guides/microsoft-power-apps", - "documentation/guides/organization-structure" - ] - }, - { - "group": "Setup", - "pages": ["documentation/setup/networking"] - } - ] - }, - { - "group": "Platform", - "pages": [ - "documentation/platform/organization", - "documentation/platform/project", - "documentation/platform/folder", - { - "group": "Secrets", - "pages": [ - "documentation/platform/secret-versioning", - "documentation/platform/pit-recovery", - "documentation/platform/secret-reference", - "documentation/platform/webhooks" - ] - }, - { - "group": "Internal PKI", - "pages": [ - "documentation/platform/pki/overview", - "documentation/platform/pki/private-ca", - "documentation/platform/pki/external-ca", - "documentation/platform/pki/subscribers", - "documentation/platform/pki/certificates", - "documentation/platform/pki/acme-ca", - "documentation/platform/pki/est", - "documentation/platform/pki/alerting", - { - "group": "Integrations", - "pages": [ - "documentation/platform/pki/pki-issuer", - "documentation/platform/pki/integration-guides/gloo-mesh" - ] - } - ] - }, - { - "group": "Infisical SSH", - "pages": [ - "documentation/platform/ssh/overview", - "documentation/platform/ssh/host-groups" - ] - }, - { - "group": "Key Management (KMS)", - "pages": [ - "documentation/platform/kms/overview", - "documentation/platform/kms/hsm-integration", - "documentation/platform/kms/kubernetes-encryption", - "documentation/platform/kms/kmip" - ] - }, - { - "group": "KMS Configuration", - "pages": [ - "documentation/platform/kms-configuration/overview", - "documentation/platform/kms-configuration/aws-kms", - "documentation/platform/kms-configuration/aws-hsm", - "documentation/platform/kms-configuration/gcp-kms" - ] - }, - { - "group": "Identities", - "pages": [ - "documentation/platform/identities/overview", - "documentation/platform/identities/user-identities", - "documentation/platform/identities/machine-identities" - ] - }, - { - "group": "Access Control", - "pages": [ - "documentation/platform/access-controls/overview", - "documentation/platform/access-controls/role-based-access-controls", - { - "group": "Attribute based access controls", - "pages": [ - "documentation/platform/access-controls/abac/overview", - "documentation/platform/access-controls/abac/managing-user-metadata", - "documentation/platform/access-controls/abac/managing-machine-identity-attributes" - ] - }, - "documentation/platform/access-controls/additional-privileges", - "documentation/platform/access-controls/temporary-access", - "documentation/platform/access-controls/assume-privilege", - "documentation/platform/access-controls/access-requests", - "documentation/platform/access-controls/project-access-requests", - "documentation/platform/pr-workflows", - "documentation/platform/groups" - ] - }, - { - "group": "Audit Logs", - "pages": [ - "documentation/platform/audit-logs", - "documentation/platform/audit-log-streams/audit-log-streams", - "documentation/platform/audit-log-streams/audit-log-streams-with-fluentbit" - ] - }, - { - "group": "Secret Rotation", - "pages": [ - "documentation/platform/secret-rotation/overview", - "documentation/platform/secret-rotation/auth0-client-secret", - "documentation/platform/secret-rotation/aws-iam-user-secret", - "documentation/platform/secret-rotation/azure-client-secret", - "documentation/platform/secret-rotation/ldap-password", - "documentation/platform/secret-rotation/mssql-credentials", - "documentation/platform/secret-rotation/mysql-credentials", - "documentation/platform/secret-rotation/oracledb-credentials", - "documentation/platform/secret-rotation/postgres-credentials" - ] - }, - { - "group": "Dynamic Secrets", - "pages": [ - "documentation/platform/dynamic-secrets/overview", - "documentation/platform/dynamic-secrets/aws-elasticache", - "documentation/platform/dynamic-secrets/aws-iam", - "documentation/platform/dynamic-secrets/azure-entra-id", - "documentation/platform/dynamic-secrets/cassandra", - "documentation/platform/dynamic-secrets/elastic-search", - "documentation/platform/dynamic-secrets/gcp-iam", - "documentation/platform/dynamic-secrets/ldap", - "documentation/platform/dynamic-secrets/mongo-atlas", - "documentation/platform/dynamic-secrets/mongo-db", - "documentation/platform/dynamic-secrets/mssql", - "documentation/platform/dynamic-secrets/mysql", - "documentation/platform/dynamic-secrets/oracle", - "documentation/platform/dynamic-secrets/postgresql", - "documentation/platform/dynamic-secrets/rabbit-mq", - "documentation/platform/dynamic-secrets/redis", - "documentation/platform/dynamic-secrets/sap-ase", - "documentation/platform/dynamic-secrets/sap-hana", - "documentation/platform/dynamic-secrets/snowflake", - "documentation/platform/dynamic-secrets/totp", - "documentation/platform/dynamic-secrets/kubernetes", - "documentation/platform/dynamic-secrets/vertica" - ] - }, - { - "group": "Gateway", - "pages": [ - "documentation/platform/gateways/overview", - "documentation/platform/gateways/gateway-security", - "documentation/platform/gateways/networking" - ] - }, - "documentation/platform/project-templates", - { - "group": "Workflow Integrations", - "pages": [ - "documentation/platform/workflow-integrations/slack-integration", - "documentation/platform/workflow-integrations/microsoft-teams-integration" - ] - }, - { - "group": "Admin Consoles", - "pages": [ - "documentation/platform/admin-panel/overview", - "documentation/platform/admin-panel/server-admin", - "documentation/platform/admin-panel/org-admin-console" - ] - }, - "documentation/platform/secret-sharing", - { - "group": "Secret Scanning", - "pages": [ - "documentation/platform/secret-scanning/overview", - "documentation/platform/secret-scanning/github" - ] - } - ] - }, - { - "group": "Authentication Methods", - "pages": [ - { - "group": "User Authentication", - "pages": [ - "documentation/platform/auth-methods/email-password", - { - "group": "SSO", - "pages": [ - "documentation/platform/sso/overview", - "documentation/platform/sso/google", - "documentation/platform/sso/github", - "documentation/platform/sso/gitlab", - "documentation/platform/sso/okta", - "documentation/platform/sso/azure", - "documentation/platform/sso/jumpcloud", - "documentation/platform/sso/keycloak-saml", - "documentation/platform/sso/google-saml", - "documentation/platform/sso/auth0-saml", - { - "group": "OIDC", - "pages": [ - { - "group": "Keycloak OIDC", - "pages": [ - "documentation/platform/sso/keycloak-oidc/overview", - "documentation/platform/sso/keycloak-oidc/group-membership-mapping" - ] - }, - "documentation/platform/sso/auth0-oidc", - { - "group": "General OIDC", - "pages": [ - "documentation/platform/sso/general-oidc/overview", - "documentation/platform/sso/general-oidc/group-membership-mapping" - ] - } - ] - } - ] - }, - { - "group": "LDAP", - "pages": [ - "documentation/platform/ldap/overview", - "documentation/platform/ldap/jumpcloud", - "documentation/platform/ldap/general" - ] - }, - { - "group": "SCIM", - "pages": [ - "documentation/platform/scim/overview", - "documentation/platform/scim/okta", - "documentation/platform/scim/azure", - "documentation/platform/scim/jumpcloud", - "documentation/platform/scim/group-mappings" - ] - } - ] - }, - - { - "group": "Machine Identities", - "pages": [ - "documentation/platform/identities/alicloud-auth", - "documentation/platform/identities/aws-auth", - "documentation/platform/identities/azure-auth", - "documentation/platform/identities/gcp-auth", - "documentation/platform/identities/jwt-auth", - "documentation/platform/identities/kubernetes-auth", - "documentation/platform/identities/oci-auth", - "documentation/platform/identities/token-auth", - "documentation/platform/identities/universal-auth", - { - "group": "OIDC Auth", - "pages": [ - "documentation/platform/identities/oidc-auth/general", - "documentation/platform/identities/oidc-auth/azure", - "documentation/platform/identities/oidc-auth/github", - "documentation/platform/identities/oidc-auth/circleci", - "documentation/platform/identities/oidc-auth/gitlab", - "documentation/platform/identities/oidc-auth/terraform-cloud", - "documentation/platform/identities/oidc-auth/spire" - ] - }, - - { - "group": "LDAP Auth", - "pages": [ - "documentation/platform/identities/ldap-auth/general", - "documentation/platform/identities/ldap-auth/jumpcloud" - ] - } - ] - }, - "documentation/platform/token", - "documentation/platform/mfa", - "documentation/platform/github-org-sync" - ] - }, - { - "group": "Self-host Infisical", - "pages": [ - "self-hosting/overview", - { - "group": "Installation methods", - "pages": [ - "self-hosting/deployment-options/standalone-infisical", - "self-hosting/deployment-options/docker-swarm", - "self-hosting/deployment-options/docker-compose", - "self-hosting/deployment-options/kubernetes-helm" - ] - }, - { - "group": "Linux Package", - "pages": [ - "self-hosting/deployment-options/native/linux-package/installation", - "self-hosting/deployment-options/native/linux-package/commands-configuration", - "self-hosting/deployment-options/linux-upgrade" - ] - }, - "self-hosting/guides/upgrading-infisical", - "self-hosting/configuration/envars", - "self-hosting/configuration/requirements", - { - "group": "Guides", - "pages": [ - "self-hosting/guides/mongo-to-postgres", - "self-hosting/guides/custom-certificates", - "self-hosting/guides/automated-bootstrapping", - "self-hosting/guides/production-hardening" - ] - }, - { - "group": "Reference architectures", - "pages": [ - "self-hosting/reference-architectures/aws-ecs", - "self-hosting/reference-architectures/linux-deployment-ha", - "self-hosting/reference-architectures/on-prem-k8s-ha", - "self-hosting/reference-architectures/google-cloud-run" - ] - }, - "self-hosting/ee", - "self-hosting/faq" - ] - }, - { - "group": "Command line", - "pages": [ - "cli/overview", - "cli/usage", - { - "group": "Core commands", - "pages": [ - "cli/commands/login", - "cli/commands/init", - "cli/commands/run", - "cli/commands/secrets", - "cli/commands/dynamic-secrets", - "cli/commands/ssh", - "cli/commands/gateway", - "cli/commands/bootstrap", - "cli/commands/export", - "cli/commands/token", - "cli/commands/service-token", - "cli/commands/vault", - "cli/commands/user", - "cli/commands/reset", - { - "group": "infisical scan", - "pages": [ - "cli/commands/scan", - "cli/commands/scan-git-changes", - "cli/commands/scan-install" - ] - } - ] - }, - "cli/scanning-overview", - "cli/project-config", - "cli/faq" - ] - }, - { - "group": "Infrastructure Integrations", - "pages": [ - "integrations/platforms/ansible", - "integrations/platforms/apache-airflow", - { - "group": "Container orchestrators", - "pages": [ - { - "group": "Kubernetes", - "pages": [ - "integrations/platforms/kubernetes/overview", - "integrations/platforms/kubernetes/infisical-secret-crd", - "integrations/platforms/kubernetes/infisical-push-secret-crd", - "integrations/platforms/kubernetes/infisical-dynamic-secret-crd" - ] - }, - "integrations/platforms/kubernetes-injector", - "integrations/platforms/kubernetes-csi", - "integrations/platforms/docker-swarm-with-agent", - "integrations/platforms/ecs-with-agent" - ] - }, - { - "group": "Docker", - "pages": [ - "integrations/platforms/docker-intro", - "integrations/platforms/docker", - "integrations/platforms/docker-pass-envs", - "integrations/platforms/docker-compose" - ] - }, - "integrations/platforms/infisical-agent", - "integrations/frameworks/packer", - "integrations/frameworks/pulumi", - "integrations/frameworks/terraform" - ] - }, - { - "group": "App Connections", - "pages": [ - "integrations/app-connections/overview", - { - "group": "Connections", - "pages": [ - "integrations/app-connections/1password", - "integrations/app-connections/auth0", - "integrations/app-connections/aws", - "integrations/app-connections/azure-app-configuration", - "integrations/app-connections/azure-client-secrets", - "integrations/app-connections/azure-devops", - "integrations/app-connections/azure-key-vault", - "integrations/app-connections/camunda", - "integrations/app-connections/databricks", - "integrations/app-connections/flyio", - "integrations/app-connections/gcp", - "integrations/app-connections/github", - "integrations/app-connections/github-radar", - "integrations/app-connections/gitlab", - "integrations/app-connections/hashicorp-vault", - "integrations/app-connections/heroku", - "integrations/app-connections/humanitec", - "integrations/app-connections/ldap", - "integrations/app-connections/mssql", - "integrations/app-connections/mysql", - "integrations/app-connections/oci", - "integrations/app-connections/oracledb", - "integrations/app-connections/postgres", - "integrations/app-connections/render", - "integrations/app-connections/teamcity", - "integrations/app-connections/terraform-cloud", - "integrations/app-connections/vercel", - "integrations/app-connections/windmill", - "integrations/app-connections/zabbix" - ] - } - ] - }, - { - "group": "Secret Syncs", - "pages": [ - "integrations/secret-syncs/overview", - { - "group": "Syncs", - "pages": [ - "integrations/secret-syncs/1password", - "integrations/secret-syncs/aws-parameter-store", - "integrations/secret-syncs/aws-secrets-manager", - "integrations/secret-syncs/azure-app-configuration", - "integrations/secret-syncs/azure-devops", - "integrations/secret-syncs/azure-key-vault", - "integrations/secret-syncs/camunda", - "integrations/secret-syncs/databricks", - "integrations/secret-syncs/flyio", - "integrations/secret-syncs/gcp-secret-manager", - "integrations/secret-syncs/github", - "integrations/secret-syncs/gitlab", - "integrations/secret-syncs/hashicorp-vault", - "integrations/secret-syncs/heroku", - "integrations/secret-syncs/humanitec", - "integrations/secret-syncs/oci-vault", - "integrations/secret-syncs/render", - "integrations/secret-syncs/teamcity", - "integrations/secret-syncs/terraform-cloud", - "integrations/secret-syncs/vercel", - "integrations/secret-syncs/windmill", - "integrations/secret-syncs/zabbix" - ] - } - ] - }, - { - "group": "Native Integrations", - "pages": [ - { - "group": "AWS", - "pages": [ - "integrations/cloud/aws-parameter-store", - "integrations/cloud/aws-secret-manager", - "integrations/cloud/aws-amplify" - ] - }, - "integrations/cloud/vercel", - "integrations/cloud/azure-key-vault", - "integrations/cloud/azure-app-configuration", - "integrations/cloud/azure-devops", - "integrations/cloud/gcp-secret-manager", - { - "group": "Cloudflare", - "pages": [ - "integrations/cloud/cloudflare-pages", - "integrations/cloud/cloudflare-workers" - ] - }, - "integrations/cloud/terraform-cloud", - "integrations/cloud/databricks", - { - "group": "View more", - "pages": [ - "integrations/cloud/digital-ocean-app-platform", - "integrations/cloud/heroku", - "integrations/cloud/netlify", - "integrations/cloud/railway", - "integrations/cloud/flyio", - "integrations/cloud/render", - "integrations/cloud/laravel-forge", - "integrations/cloud/supabase", - "integrations/cloud/northflank", - "integrations/cloud/hasura-cloud", - "integrations/cloud/qovery", - "integrations/cloud/hashicorp-vault", - "integrations/cloud/cloud-66", - "integrations/cloud/windmill" - ] - } - ] - }, - { - "group": "CI/CD Integrations", - "pages": [ - "integrations/cicd/jenkins", - "integrations/cicd/githubactions", - "integrations/cicd/gitlab", - "integrations/cicd/bitbucket", - "integrations/cloud/teamcity", - { - "group": "View more", - "pages": [ - "integrations/cicd/circleci", - "integrations/cicd/travisci", - "integrations/cicd/rundeck", - "integrations/cicd/codefresh", - "integrations/cloud/checkly", - "integrations/cicd/octopus-deploy" - ] - } - ] - }, - { - "group": "Framework Integrations", - "pages": [ - "integrations/frameworks/spring-boot-maven", - "integrations/frameworks/react", - "integrations/frameworks/vue", - "integrations/frameworks/express", - { - "group": "View more", - "pages": [ - "integrations/frameworks/nextjs", - "integrations/frameworks/nestjs", - "integrations/frameworks/sveltekit", - "integrations/frameworks/nuxt", - "integrations/frameworks/gatsby", - "integrations/frameworks/remix", - "integrations/frameworks/vite", - "integrations/frameworks/fiber", - "integrations/frameworks/django", - "integrations/frameworks/flask", - "integrations/frameworks/laravel", - "integrations/frameworks/rails", - "integrations/frameworks/dotnet", - "integrations/platforms/pm2", - "integrations/frameworks/ab-initio" - ] - } - ] - }, - { - "group": "Build Tool Integrations", - "pages": ["integrations/build-tools/gradle"] - }, - { - "group": "Others", - "pages": ["integrations/external/backstage"] - }, - { - "group": "", - "pages": ["sdks/overview"] - }, - { - "group": "SDK's", - "pages": [ - "sdks/languages/node", - "sdks/languages/python", - "sdks/languages/java", - "sdks/languages/csharp", - "sdks/languages/go", - "sdks/languages/ruby" - ] - }, - { - "group": "Overview", - "pages": [ - "api-reference/overview/introduction", - "api-reference/overview/authentication", - { - "group": "Examples", - "pages": ["api-reference/overview/examples/integration"] - } - ] - }, - { - "group": "Endpoints", - "pages": [ - { - "group": "Identities", - "pages": [ - "api-reference/endpoints/identities/create", - "api-reference/endpoints/identities/update", - "api-reference/endpoints/identities/delete", - "api-reference/endpoints/identities/get-by-id", - "api-reference/endpoints/identities/list", - "api-reference/endpoints/identities/search" - ] - }, - { - "group": "Token Auth", - "pages": [ - "api-reference/endpoints/token-auth/attach", - "api-reference/endpoints/token-auth/retrieve", - "api-reference/endpoints/token-auth/update", - "api-reference/endpoints/token-auth/revoke", - "api-reference/endpoints/token-auth/get-tokens", - "api-reference/endpoints/token-auth/create-token", - "api-reference/endpoints/token-auth/update-token", - "api-reference/endpoints/token-auth/revoke-token" - ] - }, - { - "group": "Universal Auth", - "pages": [ - "api-reference/endpoints/universal-auth/login", - "api-reference/endpoints/universal-auth/attach", - "api-reference/endpoints/universal-auth/retrieve", - "api-reference/endpoints/universal-auth/update", - "api-reference/endpoints/universal-auth/revoke", - "api-reference/endpoints/universal-auth/create-client-secret", - "api-reference/endpoints/universal-auth/list-client-secrets", - "api-reference/endpoints/universal-auth/revoke-client-secret", - "api-reference/endpoints/universal-auth/get-client-secret-by-id", - "api-reference/endpoints/universal-auth/renew-access-token", - "api-reference/endpoints/universal-auth/revoke-access-token" - ] - }, - { - "group": "GCP Auth", - "pages": [ - "api-reference/endpoints/gcp-auth/login", - "api-reference/endpoints/gcp-auth/attach", - "api-reference/endpoints/gcp-auth/retrieve", - "api-reference/endpoints/gcp-auth/update", - "api-reference/endpoints/gcp-auth/revoke" - ] - }, - { - "group": "Alibaba Cloud Auth", - "pages": [ - "api-reference/endpoints/alicloud-auth/login", - "api-reference/endpoints/alicloud-auth/attach", - "api-reference/endpoints/alicloud-auth/retrieve", - "api-reference/endpoints/alicloud-auth/update", - "api-reference/endpoints/alicloud-auth/revoke" - ] - }, - { - "group": "AWS Auth", - "pages": [ - "api-reference/endpoints/aws-auth/login", - "api-reference/endpoints/aws-auth/attach", - "api-reference/endpoints/aws-auth/retrieve", - "api-reference/endpoints/aws-auth/update", - "api-reference/endpoints/aws-auth/revoke" - ] - }, - { - "group": "OCI Auth", - "pages": [ - "api-reference/endpoints/oci-auth/login", - "api-reference/endpoints/oci-auth/attach", - "api-reference/endpoints/oci-auth/retrieve", - "api-reference/endpoints/oci-auth/update", - "api-reference/endpoints/oci-auth/revoke" - ] - }, - { - "group": "Azure Auth", - "pages": [ - "api-reference/endpoints/azure-auth/login", - "api-reference/endpoints/azure-auth/attach", - "api-reference/endpoints/azure-auth/retrieve", - "api-reference/endpoints/azure-auth/update", - "api-reference/endpoints/azure-auth/revoke" - ] - }, - { - "group": "Kubernetes Auth", - "pages": [ - "api-reference/endpoints/kubernetes-auth/login", - "api-reference/endpoints/kubernetes-auth/attach", - "api-reference/endpoints/kubernetes-auth/retrieve", - "api-reference/endpoints/kubernetes-auth/update", - "api-reference/endpoints/kubernetes-auth/revoke" - ] - }, - { - "group": "OIDC Auth", - "pages": [ - "api-reference/endpoints/oidc-auth/login", - "api-reference/endpoints/oidc-auth/attach", - "api-reference/endpoints/oidc-auth/retrieve", - "api-reference/endpoints/oidc-auth/update", - "api-reference/endpoints/oidc-auth/revoke" - ] - }, - { - "group": "JWT Auth", - "pages": [ - "api-reference/endpoints/jwt-auth/login", - "api-reference/endpoints/jwt-auth/attach", - "api-reference/endpoints/jwt-auth/retrieve", - "api-reference/endpoints/jwt-auth/update", - "api-reference/endpoints/jwt-auth/revoke" - ] - }, - { - "group": "LDAP Auth", - "pages": [ - "api-reference/endpoints/ldap-auth/login", - "api-reference/endpoints/ldap-auth/attach", - "api-reference/endpoints/ldap-auth/retrieve", - "api-reference/endpoints/ldap-auth/update", - "api-reference/endpoints/ldap-auth/revoke" - ] - }, - { - "group": "Groups", - "pages": [ - "api-reference/endpoints/groups/create", - "api-reference/endpoints/groups/update", - "api-reference/endpoints/groups/delete", - "api-reference/endpoints/groups/get", - "api-reference/endpoints/groups/get-by-id", - "api-reference/endpoints/groups/add-group-user", - "api-reference/endpoints/groups/remove-group-user", - "api-reference/endpoints/groups/list-group-users" - ] - }, - { - "group": "Organizations", - "pages": [ - "api-reference/endpoints/organizations/memberships", - "api-reference/endpoints/organizations/update-membership", - "api-reference/endpoints/organizations/delete-membership", - "api-reference/endpoints/organizations/list-identity-memberships", - "api-reference/endpoints/organizations/workspaces" - ] - }, - { - "group": "Projects", - "pages": [ - "api-reference/endpoints/workspaces/create-workspace", - "api-reference/endpoints/workspaces/delete-workspace", - "api-reference/endpoints/workspaces/get-workspace", - "api-reference/endpoints/workspaces/update-workspace", - "api-reference/endpoints/workspaces/secret-snapshots" - ] - }, - { - "group": "Project Users", - "pages": [ - "api-reference/endpoints/project-users/invite-member-to-workspace", - "api-reference/endpoints/project-users/remove-member-from-workspace", - "api-reference/endpoints/project-users/memberships", - "api-reference/endpoints/project-users/get-by-username", - "api-reference/endpoints/project-users/update-membership" - ] - }, - { - "group": "Project Groups", - "pages": [ - "api-reference/endpoints/project-groups/create", - "api-reference/endpoints/project-groups/delete", - "api-reference/endpoints/project-groups/get-by-id", - "api-reference/endpoints/project-groups/list", - "api-reference/endpoints/project-groups/update" - ] - }, - { - "group": "Project Identities", - "pages": [ - "api-reference/endpoints/project-identities/add-identity-membership", - "api-reference/endpoints/project-identities/list-identity-memberships", - "api-reference/endpoints/project-identities/get-by-id", - "api-reference/endpoints/project-identities/update-identity-membership", - "api-reference/endpoints/project-identities/delete-identity-membership" - ] - }, - { - "group": "Project Roles", - "pages": [ - "api-reference/endpoints/project-roles/create", - "api-reference/endpoints/project-roles/update", - "api-reference/endpoints/project-roles/delete", - "api-reference/endpoints/project-roles/get-by-slug", - "api-reference/endpoints/project-roles/list" - ] - }, - { - "group": "Project Templates", - "pages": [ - "api-reference/endpoints/project-templates/create", - "api-reference/endpoints/project-templates/update", - "api-reference/endpoints/project-templates/delete", - "api-reference/endpoints/project-templates/get-by-id", - "api-reference/endpoints/project-templates/list" - ] - }, - { - "group": "Environments", - "pages": [ - "api-reference/endpoints/environments/create", - "api-reference/endpoints/environments/update", - "api-reference/endpoints/environments/delete" - ] - }, - { - "group": "Folders", - "pages": [ - "api-reference/endpoints/folders/list", - "api-reference/endpoints/folders/get-by-id", - "api-reference/endpoints/folders/create", - "api-reference/endpoints/folders/update", - "api-reference/endpoints/folders/delete" - ] - }, - { - "group": "Secret Tags", - "pages": [ - "api-reference/endpoints/secret-tags/list", - "api-reference/endpoints/secret-tags/get-by-id", - "api-reference/endpoints/secret-tags/get-by-slug", - "api-reference/endpoints/secret-tags/create", - "api-reference/endpoints/secret-tags/update", - "api-reference/endpoints/secret-tags/delete" - ] - }, - { - "group": "Secrets", - "pages": [ - "api-reference/endpoints/secrets/list", - "api-reference/endpoints/secrets/create", - "api-reference/endpoints/secrets/read", - "api-reference/endpoints/secrets/update", - "api-reference/endpoints/secrets/delete", - "api-reference/endpoints/secrets/create-many", - "api-reference/endpoints/secrets/update-many", - "api-reference/endpoints/secrets/delete-many", - "api-reference/endpoints/secrets/attach-tags", - "api-reference/endpoints/secrets/detach-tags" - ] - }, - { - "group": "Dynamic Secrets", - "pages": [ - { - "group": "Kubernetes", - "pages": [ - "api-reference/endpoints/dynamic-secrets/kubernetes/create-lease" - ] - }, - "api-reference/endpoints/dynamic-secrets/create", - "api-reference/endpoints/dynamic-secrets/update", - "api-reference/endpoints/dynamic-secrets/delete", - "api-reference/endpoints/dynamic-secrets/get", - "api-reference/endpoints/dynamic-secrets/list", - "api-reference/endpoints/dynamic-secrets/list-leases", - "api-reference/endpoints/dynamic-secrets/create-lease", - "api-reference/endpoints/dynamic-secrets/delete-lease", - "api-reference/endpoints/dynamic-secrets/renew-lease", - "api-reference/endpoints/dynamic-secrets/get-lease" - ] - }, - { - "group": "Secret Imports", - "pages": [ - "api-reference/endpoints/secret-imports/list", - "api-reference/endpoints/secret-imports/create", - "api-reference/endpoints/secret-imports/update", - "api-reference/endpoints/secret-imports/delete" - ] - }, - { - "group": "Secret Rotations", - "pages": [ - "api-reference/endpoints/secret-rotations/list", - "api-reference/endpoints/secret-rotations/options", - { - "group": "Auth0 Client Secret", - "pages": [ - "api-reference/endpoints/secret-rotations/auth0-client-secret/create", - "api-reference/endpoints/secret-rotations/auth0-client-secret/delete", - "api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-id", - "api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-name", - "api-reference/endpoints/secret-rotations/auth0-client-secret/get-generated-credentials-by-id", - "api-reference/endpoints/secret-rotations/auth0-client-secret/list", - "api-reference/endpoints/secret-rotations/auth0-client-secret/rotate-secrets", - "api-reference/endpoints/secret-rotations/auth0-client-secret/update" - ] - }, - { - "group": "AWS IAM User Secret", - "pages": [ - "api-reference/endpoints/secret-rotations/aws-iam-user-secret/create", - "api-reference/endpoints/secret-rotations/aws-iam-user-secret/delete", - "api-reference/endpoints/secret-rotations/aws-iam-user-secret/get-by-id", - "api-reference/endpoints/secret-rotations/aws-iam-user-secret/get-by-name", - "api-reference/endpoints/secret-rotations/aws-iam-user-secret/get-generated-credentials-by-id", - "api-reference/endpoints/secret-rotations/aws-iam-user-secret/list", - "api-reference/endpoints/secret-rotations/aws-iam-user-secret/rotate-secrets", - "api-reference/endpoints/secret-rotations/aws-iam-user-secret/update" - ] - }, - { - "group": "Azure Client Secret", - "pages": [ - "api-reference/endpoints/secret-rotations/azure-client-secret/create", - "api-reference/endpoints/secret-rotations/azure-client-secret/delete", - "api-reference/endpoints/secret-rotations/azure-client-secret/get-by-id", - "api-reference/endpoints/secret-rotations/azure-client-secret/get-by-name", - "api-reference/endpoints/secret-rotations/azure-client-secret/get-generated-credentials-by-id", - "api-reference/endpoints/secret-rotations/azure-client-secret/list", - "api-reference/endpoints/secret-rotations/azure-client-secret/rotate-secrets", - "api-reference/endpoints/secret-rotations/azure-client-secret/update" - ] - }, - { - "group": "LDAP Password", - "pages": [ - "api-reference/endpoints/secret-rotations/ldap-password/create", - "api-reference/endpoints/secret-rotations/ldap-password/delete", - "api-reference/endpoints/secret-rotations/ldap-password/get-by-id", - "api-reference/endpoints/secret-rotations/ldap-password/get-by-name", - "api-reference/endpoints/secret-rotations/ldap-password/get-generated-credentials-by-id", - "api-reference/endpoints/secret-rotations/ldap-password/list", - "api-reference/endpoints/secret-rotations/ldap-password/rotate-secrets", - "api-reference/endpoints/secret-rotations/ldap-password/update" - ] - }, - { - "group": "Microsoft SQL Server Credentials", - "pages": [ - "api-reference/endpoints/secret-rotations/mssql-credentials/create", - "api-reference/endpoints/secret-rotations/mssql-credentials/delete", - "api-reference/endpoints/secret-rotations/mssql-credentials/get-by-id", - "api-reference/endpoints/secret-rotations/mssql-credentials/get-by-name", - "api-reference/endpoints/secret-rotations/mssql-credentials/get-generated-credentials-by-id", - "api-reference/endpoints/secret-rotations/mssql-credentials/list", - "api-reference/endpoints/secret-rotations/mssql-credentials/rotate-secrets", - "api-reference/endpoints/secret-rotations/mssql-credentials/update" - ] - }, - { - "group": "MySQL Credentials", - "pages": [ - "api-reference/endpoints/secret-rotations/mysql-credentials/create", - "api-reference/endpoints/secret-rotations/mysql-credentials/delete", - "api-reference/endpoints/secret-rotations/mysql-credentials/get-by-id", - "api-reference/endpoints/secret-rotations/mysql-credentials/get-by-name", - "api-reference/endpoints/secret-rotations/mysql-credentials/get-generated-credentials-by-id", - "api-reference/endpoints/secret-rotations/mysql-credentials/list", - "api-reference/endpoints/secret-rotations/mysql-credentials/rotate-secrets", - "api-reference/endpoints/secret-rotations/mysql-credentials/update" - ] - }, - { - "group": "OracleDB Credentials", - "pages": [ - "api-reference/endpoints/secret-rotations/oracledb-credentials/create", - "api-reference/endpoints/secret-rotations/oracledb-credentials/delete", - "api-reference/endpoints/secret-rotations/oracledb-credentials/get-by-id", - "api-reference/endpoints/secret-rotations/oracledb-credentials/get-by-name", - "api-reference/endpoints/secret-rotations/oracledb-credentials/get-generated-credentials-by-id", - "api-reference/endpoints/secret-rotations/oracledb-credentials/list", - "api-reference/endpoints/secret-rotations/oracledb-credentials/rotate-secrets", - "api-reference/endpoints/secret-rotations/oracledb-credentials/update" - ] - }, - { - "group": "PostgreSQL Credentials", - "pages": [ - "api-reference/endpoints/secret-rotations/postgres-credentials/create", - "api-reference/endpoints/secret-rotations/postgres-credentials/delete", - "api-reference/endpoints/secret-rotations/postgres-credentials/get-by-id", - "api-reference/endpoints/secret-rotations/postgres-credentials/get-by-name", - "api-reference/endpoints/secret-rotations/postgres-credentials/get-generated-credentials-by-id", - "api-reference/endpoints/secret-rotations/postgres-credentials/list", - "api-reference/endpoints/secret-rotations/postgres-credentials/rotate-secrets", - "api-reference/endpoints/secret-rotations/postgres-credentials/update" - ] - } - ] - }, - { - "group": "Secret Scanning", - "pages": [ - { - "group": "Data Sources", - "pages": [ - "api-reference/endpoints/secret-scanning/data-sources/list", - "api-reference/endpoints/secret-scanning/data-sources/options", - { - "group": "GitHub", - "pages": [ - "api-reference/endpoints/secret-scanning/data-sources/github/list", - "api-reference/endpoints/secret-scanning/data-sources/github/get-by-id", - "api-reference/endpoints/secret-scanning/data-sources/github/get-by-name", - "api-reference/endpoints/secret-scanning/data-sources/github/list-resources", - "api-reference/endpoints/secret-scanning/data-sources/github/list-scans", - "api-reference/endpoints/secret-scanning/data-sources/github/create", - "api-reference/endpoints/secret-scanning/data-sources/github/update", - "api-reference/endpoints/secret-scanning/data-sources/github/delete", - "api-reference/endpoints/secret-scanning/data-sources/github/scan", - "api-reference/endpoints/secret-scanning/data-sources/github/scan-resource" - ] - } - ] - }, - { - "group": "Findings", - "pages": [ - "api-reference/endpoints/secret-scanning/findings/list", - "api-reference/endpoints/secret-scanning/findings/update" - ] - }, - { - "group": "Configuration", - "pages": [ - "api-reference/endpoints/secret-scanning/config/get-by-project-id", - "api-reference/endpoints/secret-scanning/config/update" - ] - } - ] - }, - { - "group": "Identity Specific Privilege", - "pages": [ - { - "group": "V1 (Legacy)", - "pages": [ - "api-reference/endpoints/identity-specific-privilege/v1/create-permanent", - "api-reference/endpoints/identity-specific-privilege/v1/create-temporary", - "api-reference/endpoints/identity-specific-privilege/v1/update", - "api-reference/endpoints/identity-specific-privilege/v1/delete", - "api-reference/endpoints/identity-specific-privilege/v1/find-by-slug", - "api-reference/endpoints/identity-specific-privilege/v1/list" - ] - }, - { - "group": "V2", - "pages": [ - "api-reference/endpoints/identity-specific-privilege/v2/create", - "api-reference/endpoints/identity-specific-privilege/v2/update", - "api-reference/endpoints/identity-specific-privilege/v2/delete", - "api-reference/endpoints/identity-specific-privilege/v2/list", - "api-reference/endpoints/identity-specific-privilege/v2/find-by-id", - "api-reference/endpoints/identity-specific-privilege/v2/find-by-slug" - ] - } - ] - }, - { - "group": "App Connections", - "pages": [ - "api-reference/endpoints/app-connections/list", - "api-reference/endpoints/app-connections/options", - { - "group": "1Password", - "pages": [ - "api-reference/endpoints/app-connections/1password/list", - "api-reference/endpoints/app-connections/1password/available", - "api-reference/endpoints/app-connections/1password/get-by-id", - "api-reference/endpoints/app-connections/1password/get-by-name", - "api-reference/endpoints/app-connections/1password/create", - "api-reference/endpoints/app-connections/1password/update", - "api-reference/endpoints/app-connections/1password/delete" - ] - }, - { - "group": "Auth0", - "pages": [ - "api-reference/endpoints/app-connections/auth0/list", - "api-reference/endpoints/app-connections/auth0/available", - "api-reference/endpoints/app-connections/auth0/get-by-id", - "api-reference/endpoints/app-connections/auth0/get-by-name", - "api-reference/endpoints/app-connections/auth0/create", - "api-reference/endpoints/app-connections/auth0/update", - "api-reference/endpoints/app-connections/auth0/delete" - ] - }, - { - "group": "AWS", - "pages": [ - "api-reference/endpoints/app-connections/aws/list", - "api-reference/endpoints/app-connections/aws/available", - "api-reference/endpoints/app-connections/aws/get-by-id", - "api-reference/endpoints/app-connections/aws/get-by-name", - "api-reference/endpoints/app-connections/aws/create", - "api-reference/endpoints/app-connections/aws/update", - "api-reference/endpoints/app-connections/aws/delete" - ] - }, - { - "group": "Azure App Configuration", - "pages": [ - "api-reference/endpoints/app-connections/azure-app-configuration/list", - "api-reference/endpoints/app-connections/azure-app-configuration/available", - "api-reference/endpoints/app-connections/azure-app-configuration/get-by-id", - "api-reference/endpoints/app-connections/azure-app-configuration/get-by-name", - "api-reference/endpoints/app-connections/azure-app-configuration/create", - "api-reference/endpoints/app-connections/azure-app-configuration/update", - "api-reference/endpoints/app-connections/azure-app-configuration/delete" - ] - }, - { - "group": "Azure Client Secret", - "pages": [ - "api-reference/endpoints/app-connections/azure-client-secret/list", - "api-reference/endpoints/app-connections/azure-client-secret/available", - "api-reference/endpoints/app-connections/azure-client-secret/get-by-id", - "api-reference/endpoints/app-connections/azure-client-secret/get-by-name", - "api-reference/endpoints/app-connections/azure-client-secret/create", - "api-reference/endpoints/app-connections/azure-client-secret/update", - "api-reference/endpoints/app-connections/azure-client-secret/delete" - ] - }, - { - "group": "Azure DevOps", - "pages": [ - "api-reference/endpoints/app-connections/azure-devops/list", - "api-reference/endpoints/app-connections/azure-devops/available", - "api-reference/endpoints/app-connections/azure-devops/get-by-id", - "api-reference/endpoints/app-connections/azure-devops/get-by-name", - "api-reference/endpoints/app-connections/azure-devops/create", - "api-reference/endpoints/app-connections/azure-devops/update", - "api-reference/endpoints/app-connections/azure-devops/delete" - ] - }, - { - "group": "Azure Key Vault", - "pages": [ - "api-reference/endpoints/app-connections/azure-key-vault/list", - "api-reference/endpoints/app-connections/azure-key-vault/available", - "api-reference/endpoints/app-connections/azure-key-vault/get-by-id", - "api-reference/endpoints/app-connections/azure-key-vault/get-by-name", - "api-reference/endpoints/app-connections/azure-key-vault/create", - "api-reference/endpoints/app-connections/azure-key-vault/update", - "api-reference/endpoints/app-connections/azure-key-vault/delete" - ] - }, - { - "group": "Camunda", - "pages": [ - "api-reference/endpoints/app-connections/camunda/list", - "api-reference/endpoints/app-connections/camunda/available", - "api-reference/endpoints/app-connections/camunda/get-by-id", - "api-reference/endpoints/app-connections/camunda/get-by-name", - "api-reference/endpoints/app-connections/camunda/create", - "api-reference/endpoints/app-connections/camunda/update", - "api-reference/endpoints/app-connections/camunda/delete" - ] - }, - { - "group": "Databricks", - "pages": [ - "api-reference/endpoints/app-connections/databricks/list", - "api-reference/endpoints/app-connections/databricks/available", - "api-reference/endpoints/app-connections/databricks/get-by-id", - "api-reference/endpoints/app-connections/databricks/get-by-name", - "api-reference/endpoints/app-connections/databricks/create", - "api-reference/endpoints/app-connections/databricks/update", - "api-reference/endpoints/app-connections/databricks/delete" - ] - }, - { - "group": "Fly.io", - "pages": [ - "api-reference/endpoints/app-connections/flyio/list", - "api-reference/endpoints/app-connections/flyio/available", - "api-reference/endpoints/app-connections/flyio/get-by-id", - "api-reference/endpoints/app-connections/flyio/get-by-name", - "api-reference/endpoints/app-connections/flyio/create", - "api-reference/endpoints/app-connections/flyio/update", - "api-reference/endpoints/app-connections/flyio/delete" - ] - }, - { - "group": "GCP", - "pages": [ - "api-reference/endpoints/app-connections/gcp/list", - "api-reference/endpoints/app-connections/gcp/available", - "api-reference/endpoints/app-connections/gcp/get-by-id", - "api-reference/endpoints/app-connections/gcp/get-by-name", - "api-reference/endpoints/app-connections/gcp/create", - "api-reference/endpoints/app-connections/gcp/update", - "api-reference/endpoints/app-connections/gcp/delete" - ] - }, - { - "group": "GitHub", - "pages": [ - "api-reference/endpoints/app-connections/github/list", - "api-reference/endpoints/app-connections/github/available", - "api-reference/endpoints/app-connections/github/get-by-id", - "api-reference/endpoints/app-connections/github/get-by-name", - "api-reference/endpoints/app-connections/github/create", - "api-reference/endpoints/app-connections/github/update", - "api-reference/endpoints/app-connections/github/delete" - ] - }, - { - "group": "GitLab", - "pages": [ - "api-reference/endpoints/app-connections/gitlab/list", - "api-reference/endpoints/app-connections/gitlab/available", - "api-reference/endpoints/app-connections/gitlab/get-by-id", - "api-reference/endpoints/app-connections/gitlab/get-by-name", - "api-reference/endpoints/app-connections/gitlab/create", - "api-reference/endpoints/app-connections/gitlab/update", - "api-reference/endpoints/app-connections/gitlab/delete" - ] - }, - { - "group": "GitHub Radar", - "pages": [ - "api-reference/endpoints/app-connections/github-radar/list", - "api-reference/endpoints/app-connections/github-radar/available", - "api-reference/endpoints/app-connections/github-radar/get-by-id", - "api-reference/endpoints/app-connections/github-radar/get-by-name", - "api-reference/endpoints/app-connections/github-radar/create", - "api-reference/endpoints/app-connections/github-radar/update", - "api-reference/endpoints/app-connections/github-radar/delete" - ] - }, - { - "group": "Hashicorp Vault", - "pages": [ - "api-reference/endpoints/app-connections/hashicorp-vault/list", - "api-reference/endpoints/app-connections/hashicorp-vault/available", - "api-reference/endpoints/app-connections/hashicorp-vault/get-by-id", - "api-reference/endpoints/app-connections/hashicorp-vault/get-by-name", - "api-reference/endpoints/app-connections/hashicorp-vault/create", - "api-reference/endpoints/app-connections/hashicorp-vault/update", - "api-reference/endpoints/app-connections/hashicorp-vault/delete" - ] - }, - { - "group": "Heroku", - "pages": [ - "api-reference/endpoints/app-connections/heroku/list", - "api-reference/endpoints/app-connections/heroku/available", - "api-reference/endpoints/app-connections/heroku/get-by-id", - "api-reference/endpoints/app-connections/heroku/get-by-name", - "api-reference/endpoints/app-connections/heroku/create", - "api-reference/endpoints/app-connections/heroku/update", - "api-reference/endpoints/app-connections/heroku/delete" - ] - }, - { - "group": "Humanitec", - "pages": [ - "api-reference/endpoints/app-connections/humanitec/list", - "api-reference/endpoints/app-connections/humanitec/available", - "api-reference/endpoints/app-connections/humanitec/get-by-id", - "api-reference/endpoints/app-connections/humanitec/get-by-name", - "api-reference/endpoints/app-connections/humanitec/create", - "api-reference/endpoints/app-connections/humanitec/update", - "api-reference/endpoints/app-connections/humanitec/delete" - ] - }, - { - "group": "LDAP", - "pages": [ - "api-reference/endpoints/app-connections/ldap/list", - "api-reference/endpoints/app-connections/ldap/available", - "api-reference/endpoints/app-connections/ldap/get-by-id", - "api-reference/endpoints/app-connections/ldap/get-by-name", - "api-reference/endpoints/app-connections/ldap/create", - "api-reference/endpoints/app-connections/ldap/update", - "api-reference/endpoints/app-connections/ldap/delete" - ] - }, - { - "group": "Microsoft SQL Server", - "pages": [ - "api-reference/endpoints/app-connections/mssql/list", - "api-reference/endpoints/app-connections/mssql/available", - "api-reference/endpoints/app-connections/mssql/get-by-id", - "api-reference/endpoints/app-connections/mssql/get-by-name", - "api-reference/endpoints/app-connections/mssql/create", - "api-reference/endpoints/app-connections/mssql/update", - "api-reference/endpoints/app-connections/mssql/delete" - ] - }, - { - "group": "MySQL", - "pages": [ - "api-reference/endpoints/app-connections/mysql/list", - "api-reference/endpoints/app-connections/mysql/available", - "api-reference/endpoints/app-connections/mysql/get-by-id", - "api-reference/endpoints/app-connections/mysql/get-by-name", - "api-reference/endpoints/app-connections/mysql/create", - "api-reference/endpoints/app-connections/mysql/update", - "api-reference/endpoints/app-connections/mysql/delete" - ] - }, - { - "group": "OCI", - "pages": [ - "api-reference/endpoints/app-connections/oci/list", - "api-reference/endpoints/app-connections/oci/available", - "api-reference/endpoints/app-connections/oci/get-by-id", - "api-reference/endpoints/app-connections/oci/get-by-name", - "api-reference/endpoints/app-connections/oci/create", - "api-reference/endpoints/app-connections/oci/update", - "api-reference/endpoints/app-connections/oci/delete" - ] - }, - { - "group": "OracleDB", - "pages": [ - "api-reference/endpoints/app-connections/oracledb/list", - "api-reference/endpoints/app-connections/oracledb/available", - "api-reference/endpoints/app-connections/oracledb/get-by-id", - "api-reference/endpoints/app-connections/oracledb/get-by-name", - "api-reference/endpoints/app-connections/oracledb/create", - "api-reference/endpoints/app-connections/oracledb/update", - "api-reference/endpoints/app-connections/oracledb/delete" - ] - }, - { - "group": "PostgreSQL", - "pages": [ - "api-reference/endpoints/app-connections/postgres/list", - "api-reference/endpoints/app-connections/postgres/available", - "api-reference/endpoints/app-connections/postgres/get-by-id", - "api-reference/endpoints/app-connections/postgres/get-by-name", - "api-reference/endpoints/app-connections/postgres/create", - "api-reference/endpoints/app-connections/postgres/update", - "api-reference/endpoints/app-connections/postgres/delete" - ] - }, - { - "group": "Render", - "pages": [ - "api-reference/endpoints/app-connections/render/list", - "api-reference/endpoints/app-connections/render/available", - "api-reference/endpoints/app-connections/render/get-by-id", - "api-reference/endpoints/app-connections/render/get-by-name", - "api-reference/endpoints/app-connections/render/create", - "api-reference/endpoints/app-connections/render/update", - "api-reference/endpoints/app-connections/render/delete" - ] - }, - { - "group": "TeamCity", - "pages": [ - "api-reference/endpoints/app-connections/teamcity/list", - "api-reference/endpoints/app-connections/teamcity/available", - "api-reference/endpoints/app-connections/teamcity/get-by-id", - "api-reference/endpoints/app-connections/teamcity/get-by-name", - "api-reference/endpoints/app-connections/teamcity/create", - "api-reference/endpoints/app-connections/teamcity/update", - "api-reference/endpoints/app-connections/teamcity/delete" - ] - }, - { - "group": "Terraform Cloud", - "pages": [ - "api-reference/endpoints/app-connections/terraform-cloud/list", - "api-reference/endpoints/app-connections/terraform-cloud/available", - "api-reference/endpoints/app-connections/terraform-cloud/get-by-id", - "api-reference/endpoints/app-connections/terraform-cloud/get-by-name", - "api-reference/endpoints/app-connections/terraform-cloud/create", - "api-reference/endpoints/app-connections/terraform-cloud/update", - "api-reference/endpoints/app-connections/terraform-cloud/delete" - ] - }, - { - "group": "Vercel", - "pages": [ - "api-reference/endpoints/app-connections/vercel/list", - "api-reference/endpoints/app-connections/vercel/available", - "api-reference/endpoints/app-connections/vercel/get-by-id", - "api-reference/endpoints/app-connections/vercel/get-by-name", - "api-reference/endpoints/app-connections/vercel/create", - "api-reference/endpoints/app-connections/vercel/update", - "api-reference/endpoints/app-connections/vercel/delete" - ] - }, - { - "group": "Windmill", - "pages": [ - "api-reference/endpoints/app-connections/windmill/list", - "api-reference/endpoints/app-connections/windmill/available", - "api-reference/endpoints/app-connections/windmill/get-by-id", - "api-reference/endpoints/app-connections/windmill/get-by-name", - "api-reference/endpoints/app-connections/windmill/create", - "api-reference/endpoints/app-connections/windmill/update", - "api-reference/endpoints/app-connections/windmill/delete" - ] - }, - { - "group": "Zabbix", - "pages": [ - "api-reference/endpoints/app-connections/zabbix/list", - "api-reference/endpoints/app-connections/zabbix/available", - "api-reference/endpoints/app-connections/zabbix/get-by-id", - "api-reference/endpoints/app-connections/zabbix/get-by-name", - "api-reference/endpoints/app-connections/zabbix/create", - "api-reference/endpoints/app-connections/zabbix/update", - "api-reference/endpoints/app-connections/zabbix/delete" - ] - } - ] - }, - { - "group": "Secret Syncs", - "pages": [ - "api-reference/endpoints/secret-syncs/list", - "api-reference/endpoints/secret-syncs/options", - { - "group": "1Password", - "pages": [ - "api-reference/endpoints/secret-syncs/1password/list", - "api-reference/endpoints/secret-syncs/1password/get-by-id", - "api-reference/endpoints/secret-syncs/1password/get-by-name", - "api-reference/endpoints/secret-syncs/1password/create", - "api-reference/endpoints/secret-syncs/1password/update", - "api-reference/endpoints/secret-syncs/1password/delete", - "api-reference/endpoints/secret-syncs/1password/sync-secrets", - "api-reference/endpoints/secret-syncs/1password/import-secrets", - "api-reference/endpoints/secret-syncs/1password/remove-secrets" - ] - }, - { - "group": "AWS Parameter Store", - "pages": [ - "api-reference/endpoints/secret-syncs/aws-parameter-store/list", - "api-reference/endpoints/secret-syncs/aws-parameter-store/get-by-id", - "api-reference/endpoints/secret-syncs/aws-parameter-store/get-by-name", - "api-reference/endpoints/secret-syncs/aws-parameter-store/create", - "api-reference/endpoints/secret-syncs/aws-parameter-store/update", - "api-reference/endpoints/secret-syncs/aws-parameter-store/delete", - "api-reference/endpoints/secret-syncs/aws-parameter-store/sync-secrets", - "api-reference/endpoints/secret-syncs/aws-parameter-store/import-secrets", - "api-reference/endpoints/secret-syncs/aws-parameter-store/remove-secrets" - ] - }, - { - "group": "AWS Secrets Manager", - "pages": [ - "api-reference/endpoints/secret-syncs/aws-secrets-manager/list", - "api-reference/endpoints/secret-syncs/aws-secrets-manager/get-by-id", - "api-reference/endpoints/secret-syncs/aws-secrets-manager/get-by-name", - "api-reference/endpoints/secret-syncs/aws-secrets-manager/create", - "api-reference/endpoints/secret-syncs/aws-secrets-manager/update", - "api-reference/endpoints/secret-syncs/aws-secrets-manager/delete", - "api-reference/endpoints/secret-syncs/aws-secrets-manager/sync-secrets", - "api-reference/endpoints/secret-syncs/aws-secrets-manager/import-secrets", - "api-reference/endpoints/secret-syncs/aws-secrets-manager/remove-secrets" - ] - }, - { - "group": "Azure App Configuration", - "pages": [ - "api-reference/endpoints/secret-syncs/azure-app-configuration/list", - "api-reference/endpoints/secret-syncs/azure-app-configuration/get-by-id", - "api-reference/endpoints/secret-syncs/azure-app-configuration/get-by-name", - "api-reference/endpoints/secret-syncs/azure-app-configuration/create", - "api-reference/endpoints/secret-syncs/azure-app-configuration/update", - "api-reference/endpoints/secret-syncs/azure-app-configuration/delete", - "api-reference/endpoints/secret-syncs/azure-app-configuration/sync-secrets", - "api-reference/endpoints/secret-syncs/azure-app-configuration/import-secrets", - "api-reference/endpoints/secret-syncs/azure-app-configuration/remove-secrets" - ] - }, - { - "group": "Azure DevOps", - "pages": [ - "api-reference/endpoints/secret-syncs/azure-devops/list", - "api-reference/endpoints/secret-syncs/azure-devops/get-by-id", - "api-reference/endpoints/secret-syncs/azure-devops/get-by-name", - "api-reference/endpoints/secret-syncs/azure-devops/create", - "api-reference/endpoints/secret-syncs/azure-devops/update", - "api-reference/endpoints/secret-syncs/azure-devops/delete", - "api-reference/endpoints/secret-syncs/azure-devops/sync-secrets", - "api-reference/endpoints/secret-syncs/azure-devops/import-secrets", - "api-reference/endpoints/secret-syncs/azure-devops/remove-secrets" - ] - }, - { - "group": "Azure Key Vault", - "pages": [ - "api-reference/endpoints/secret-syncs/azure-key-vault/list", - "api-reference/endpoints/secret-syncs/azure-key-vault/get-by-id", - "api-reference/endpoints/secret-syncs/azure-key-vault/get-by-name", - "api-reference/endpoints/secret-syncs/azure-key-vault/create", - "api-reference/endpoints/secret-syncs/azure-key-vault/update", - "api-reference/endpoints/secret-syncs/azure-key-vault/delete", - "api-reference/endpoints/secret-syncs/azure-key-vault/sync-secrets", - "api-reference/endpoints/secret-syncs/azure-key-vault/import-secrets", - "api-reference/endpoints/secret-syncs/azure-key-vault/remove-secrets" - ] - }, - { - "group": "Camunda", - "pages": [ - "api-reference/endpoints/secret-syncs/camunda/list", - "api-reference/endpoints/secret-syncs/camunda/get-by-id", - "api-reference/endpoints/secret-syncs/camunda/get-by-name", - "api-reference/endpoints/secret-syncs/camunda/create", - "api-reference/endpoints/secret-syncs/camunda/update", - "api-reference/endpoints/secret-syncs/camunda/delete", - "api-reference/endpoints/secret-syncs/camunda/sync-secrets", - "api-reference/endpoints/secret-syncs/camunda/remove-secrets" - ] - }, - { - "group": "Databricks", - "pages": [ - "api-reference/endpoints/secret-syncs/databricks/list", - "api-reference/endpoints/secret-syncs/databricks/get-by-id", - "api-reference/endpoints/secret-syncs/databricks/get-by-name", - "api-reference/endpoints/secret-syncs/databricks/create", - "api-reference/endpoints/secret-syncs/databricks/update", - "api-reference/endpoints/secret-syncs/databricks/delete", - "api-reference/endpoints/secret-syncs/databricks/sync-secrets", - "api-reference/endpoints/secret-syncs/databricks/remove-secrets" - ] - }, - { - "group": "Fly.io", - "pages": [ - "api-reference/endpoints/secret-syncs/flyio/list", - "api-reference/endpoints/secret-syncs/flyio/get-by-id", - "api-reference/endpoints/secret-syncs/flyio/get-by-name", - "api-reference/endpoints/secret-syncs/flyio/create", - "api-reference/endpoints/secret-syncs/flyio/update", - "api-reference/endpoints/secret-syncs/flyio/delete", - "api-reference/endpoints/secret-syncs/flyio/sync-secrets", - "api-reference/endpoints/secret-syncs/flyio/remove-secrets" - ] - }, - { - "group": "GCP Secret Manager", - "pages": [ - "api-reference/endpoints/secret-syncs/gcp-secret-manager/list", - "api-reference/endpoints/secret-syncs/gcp-secret-manager/get-by-id", - "api-reference/endpoints/secret-syncs/gcp-secret-manager/get-by-name", - "api-reference/endpoints/secret-syncs/gcp-secret-manager/create", - "api-reference/endpoints/secret-syncs/gcp-secret-manager/update", - "api-reference/endpoints/secret-syncs/gcp-secret-manager/delete", - "api-reference/endpoints/secret-syncs/gcp-secret-manager/sync-secrets", - "api-reference/endpoints/secret-syncs/gcp-secret-manager/import-secrets", - "api-reference/endpoints/secret-syncs/gcp-secret-manager/remove-secrets" - ] - }, - { - "group": "GitHub", - "pages": [ - "api-reference/endpoints/secret-syncs/github/list", - "api-reference/endpoints/secret-syncs/github/get-by-id", - "api-reference/endpoints/secret-syncs/github/get-by-name", - "api-reference/endpoints/secret-syncs/github/create", - "api-reference/endpoints/secret-syncs/github/update", - "api-reference/endpoints/secret-syncs/github/delete", - "api-reference/endpoints/secret-syncs/github/sync-secrets", - "api-reference/endpoints/secret-syncs/github/remove-secrets" - ] - }, - { - "group": "GitLab", - "pages": [ - "api-reference/endpoints/secret-syncs/gitlab/list", - "api-reference/endpoints/secret-syncs/gitlab/get-by-id", - "api-reference/endpoints/secret-syncs/gitlab/get-by-name", - "api-reference/endpoints/secret-syncs/gitlab/create", - "api-reference/endpoints/secret-syncs/gitlab/update", - "api-reference/endpoints/secret-syncs/gitlab/delete", - "api-reference/endpoints/secret-syncs/gitlab/sync-secrets", - "api-reference/endpoints/secret-syncs/gitlab/remove-secrets" - ] - }, - { - "group": "Hashicorp Vault", - "pages": [ - "api-reference/endpoints/secret-syncs/hashicorp-vault/list", - "api-reference/endpoints/secret-syncs/hashicorp-vault/get-by-id", - "api-reference/endpoints/secret-syncs/hashicorp-vault/get-by-name", - "api-reference/endpoints/secret-syncs/hashicorp-vault/create", - "api-reference/endpoints/secret-syncs/hashicorp-vault/update", - "api-reference/endpoints/secret-syncs/hashicorp-vault/delete", - "api-reference/endpoints/secret-syncs/hashicorp-vault/sync-secrets", - "api-reference/endpoints/secret-syncs/hashicorp-vault/import-secrets", - "api-reference/endpoints/secret-syncs/hashicorp-vault/remove-secrets" - ] - }, - { - "group": "Heroku", - "pages": [ - "api-reference/endpoints/secret-syncs/heroku/list", - "api-reference/endpoints/secret-syncs/heroku/get-by-id", - "api-reference/endpoints/secret-syncs/heroku/get-by-name", - "api-reference/endpoints/secret-syncs/heroku/create", - "api-reference/endpoints/secret-syncs/heroku/update", - "api-reference/endpoints/secret-syncs/heroku/delete", - "api-reference/endpoints/secret-syncs/heroku/sync-secrets", - "api-reference/endpoints/secret-syncs/heroku/remove-secrets" - ] - }, - { - "group": "Humanitec", - "pages": [ - "api-reference/endpoints/secret-syncs/humanitec/list", - "api-reference/endpoints/secret-syncs/humanitec/get-by-id", - "api-reference/endpoints/secret-syncs/humanitec/get-by-name", - "api-reference/endpoints/secret-syncs/humanitec/create", - "api-reference/endpoints/secret-syncs/humanitec/update", - "api-reference/endpoints/secret-syncs/humanitec/delete", - "api-reference/endpoints/secret-syncs/humanitec/sync-secrets", - "api-reference/endpoints/secret-syncs/humanitec/remove-secrets" - ] - }, - { - "group": "OCI", - "pages": [ - "api-reference/endpoints/secret-syncs/oci-vault/list", - "api-reference/endpoints/secret-syncs/oci-vault/get-by-id", - "api-reference/endpoints/secret-syncs/oci-vault/get-by-name", - "api-reference/endpoints/secret-syncs/oci-vault/create", - "api-reference/endpoints/secret-syncs/oci-vault/update", - "api-reference/endpoints/secret-syncs/oci-vault/delete", - "api-reference/endpoints/secret-syncs/oci-vault/sync-secrets", - "api-reference/endpoints/secret-syncs/oci-vault/import-secrets", - "api-reference/endpoints/secret-syncs/oci-vault/remove-secrets" - ] - }, - { - "group": "Render", - "pages": [ - "api-reference/endpoints/secret-syncs/render/list", - "api-reference/endpoints/secret-syncs/render/get-by-id", - "api-reference/endpoints/secret-syncs/render/get-by-name", - "api-reference/endpoints/secret-syncs/render/create", - "api-reference/endpoints/secret-syncs/render/update", - "api-reference/endpoints/secret-syncs/render/delete", - "api-reference/endpoints/secret-syncs/render/sync-secrets", - "api-reference/endpoints/secret-syncs/render/import-secrets", - "api-reference/endpoints/secret-syncs/render/remove-secrets" - ] - }, - { - "group": "TeamCity", - "pages": [ - "api-reference/endpoints/secret-syncs/teamcity/list", - "api-reference/endpoints/secret-syncs/teamcity/get-by-id", - "api-reference/endpoints/secret-syncs/teamcity/get-by-name", - "api-reference/endpoints/secret-syncs/teamcity/create", - "api-reference/endpoints/secret-syncs/teamcity/update", - "api-reference/endpoints/secret-syncs/teamcity/delete", - "api-reference/endpoints/secret-syncs/teamcity/sync-secrets", - "api-reference/endpoints/secret-syncs/teamcity/import-secrets", - "api-reference/endpoints/secret-syncs/teamcity/remove-secrets" - ] - }, - { - "group": "Terraform Cloud", - "pages": [ - "api-reference/endpoints/secret-syncs/terraform-cloud/list", - "api-reference/endpoints/secret-syncs/terraform-cloud/get-by-id", - "api-reference/endpoints/secret-syncs/terraform-cloud/get-by-name", - "api-reference/endpoints/secret-syncs/terraform-cloud/create", - "api-reference/endpoints/secret-syncs/terraform-cloud/update", - "api-reference/endpoints/secret-syncs/terraform-cloud/delete", - "api-reference/endpoints/secret-syncs/terraform-cloud/sync-secrets", - "api-reference/endpoints/secret-syncs/terraform-cloud/remove-secrets" - ] - }, - { - "group": "Vercel", - "pages": [ - "api-reference/endpoints/secret-syncs/vercel/list", - "api-reference/endpoints/secret-syncs/vercel/get-by-id", - "api-reference/endpoints/secret-syncs/vercel/get-by-name", - "api-reference/endpoints/secret-syncs/vercel/create", - "api-reference/endpoints/secret-syncs/vercel/update", - "api-reference/endpoints/secret-syncs/vercel/delete", - "api-reference/endpoints/secret-syncs/vercel/sync-secrets", - "api-reference/endpoints/secret-syncs/vercel/import-secrets", - "api-reference/endpoints/secret-syncs/vercel/remove-secrets" - ] - }, - { - "group": "Windmill", - "pages": [ - "api-reference/endpoints/secret-syncs/windmill/list", - "api-reference/endpoints/secret-syncs/windmill/get-by-id", - "api-reference/endpoints/secret-syncs/windmill/get-by-name", - "api-reference/endpoints/secret-syncs/windmill/create", - "api-reference/endpoints/secret-syncs/windmill/update", - "api-reference/endpoints/secret-syncs/windmill/delete", - "api-reference/endpoints/secret-syncs/windmill/sync-secrets", - "api-reference/endpoints/secret-syncs/windmill/import-secrets", - "api-reference/endpoints/secret-syncs/windmill/remove-secrets" - ] - }, - { - "group": "Zabbix", - "pages": [ - "api-reference/endpoints/secret-syncs/zabbix/list", - "api-reference/endpoints/secret-syncs/zabbix/get-by-id", - "api-reference/endpoints/secret-syncs/zabbix/get-by-name", - "api-reference/endpoints/secret-syncs/zabbix/create", - "api-reference/endpoints/secret-syncs/zabbix/update", - "api-reference/endpoints/secret-syncs/zabbix/delete", - "api-reference/endpoints/secret-syncs/zabbix/sync-secrets", - "api-reference/endpoints/secret-syncs/zabbix/import-secrets", - "api-reference/endpoints/secret-syncs/zabbix/remove-secrets" - ] - } - ] - }, - { - "group": "Integrations", - "pages": [ - "api-reference/endpoints/integrations/create-auth", - "api-reference/endpoints/integrations/list-auth", - "api-reference/endpoints/integrations/find-auth", - "api-reference/endpoints/integrations/delete-auth", - "api-reference/endpoints/integrations/delete-auth-by-id", - "api-reference/endpoints/integrations/create", - "api-reference/endpoints/integrations/update", - "api-reference/endpoints/integrations/delete", - "api-reference/endpoints/integrations/list-project-integrations" - ] - }, - { - "group": "Service Tokens", - "pages": ["api-reference/endpoints/service-tokens/get"] - }, - { - "group": "Audit Logs", - "pages": ["api-reference/endpoints/audit-logs/export-audit-log"] - } - ] - }, - { - "group": "Infisical PKI", - "pages": [ - { - "group": "Subscribers", - "pages": [ - "api-reference/endpoints/pki/subscribers/list-certs", - "api-reference/endpoints/pki/subscribers/create", - "api-reference/endpoints/pki/subscribers/read", - "api-reference/endpoints/pki/subscribers/update", - "api-reference/endpoints/pki/subscribers/delete", - "api-reference/endpoints/pki/subscribers/issue-cert", - "api-reference/endpoints/pki/subscribers/sign-cert", - "api-reference/endpoints/pki/subscribers/order-cert", - "api-reference/endpoints/pki/subscribers/get-latest-cert-bundle" - ] - }, - { - "group": "Certificate Authorities", - "pages": [ - { - "group": "ACME", - "pages": [ - "api-reference/endpoints/certificate-authorities/acme/list", - "api-reference/endpoints/certificate-authorities/acme/create", - "api-reference/endpoints/certificate-authorities/acme/read", - "api-reference/endpoints/certificate-authorities/acme/update", - "api-reference/endpoints/certificate-authorities/acme/delete" - ] - }, - { - "group": "Internal", - "pages": [ - "api-reference/endpoints/certificate-authorities/internal/list", - "api-reference/endpoints/certificate-authorities/internal/create", - "api-reference/endpoints/certificate-authorities/internal/read", - "api-reference/endpoints/certificate-authorities/internal/update", - "api-reference/endpoints/certificate-authorities/internal/delete" - ] - }, - "api-reference/endpoints/certificate-authorities/list", - "api-reference/endpoints/certificate-authorities/create", - "api-reference/endpoints/certificate-authorities/read", - "api-reference/endpoints/certificate-authorities/update", - "api-reference/endpoints/certificate-authorities/delete", - "api-reference/endpoints/certificate-authorities/renew", - "api-reference/endpoints/certificate-authorities/list-ca-certs", - "api-reference/endpoints/certificate-authorities/csr", - "api-reference/endpoints/certificate-authorities/cert", - "api-reference/endpoints/certificate-authorities/sign-intermediate", - "api-reference/endpoints/certificate-authorities/import-cert", - "api-reference/endpoints/certificate-authorities/issue-cert", - "api-reference/endpoints/certificate-authorities/sign-cert", - "api-reference/endpoints/certificate-authorities/crl" - ] - }, - { - "group": "Certificates", - "pages": [ - "api-reference/endpoints/certificates/list", - "api-reference/endpoints/certificates/read", - "api-reference/endpoints/certificates/revoke", - "api-reference/endpoints/certificates/delete", - "api-reference/endpoints/certificates/cert-body", - "api-reference/endpoints/certificates/bundle", - "api-reference/endpoints/certificates/private-key", - "api-reference/endpoints/certificates/issue-certificate", - "api-reference/endpoints/certificates/sign-certificate" - ] - }, - { - "group": "Certificate Templates", - "pages": [ - "api-reference/endpoints/certificate-templates/create", - "api-reference/endpoints/certificate-templates/update", - "api-reference/endpoints/certificate-templates/get-by-id", - "api-reference/endpoints/certificate-templates/delete" - ] - }, - { - "group": "Certificate Collections", - "pages": [ - "api-reference/endpoints/pki-collections/create", - "api-reference/endpoints/pki-collections/read", - "api-reference/endpoints/pki-collections/update", - "api-reference/endpoints/pki-collections/delete", - "api-reference/endpoints/pki-collections/add-item", - "api-reference/endpoints/pki-collections/list-items", - "api-reference/endpoints/pki-collections/delete-item" - ] - }, - { - "group": "PKI Alerting", - "pages": [ - "api-reference/endpoints/pki-alerts/create", - "api-reference/endpoints/pki-alerts/read", - "api-reference/endpoints/pki-alerts/update", - "api-reference/endpoints/pki-alerts/delete" - ] - } - ] - }, - { - "group": "Infisical SSH", - "pages": [ - { - "group": "Hosts", - "pages": [ - "api-reference/endpoints/ssh/hosts/list-my", - "api-reference/endpoints/ssh/hosts/list", - "api-reference/endpoints/ssh/hosts/create", - "api-reference/endpoints/ssh/hosts/read", - "api-reference/endpoints/ssh/hosts/update", - "api-reference/endpoints/ssh/hosts/delete", - "api-reference/endpoints/ssh/hosts/issue-host-cert", - "api-reference/endpoints/ssh/hosts/issue-user-cert", - "api-reference/endpoints/ssh/hosts/read-user-ca-pk", - "api-reference/endpoints/ssh/hosts/read-host-ca-pk" - ] - }, - { - "group": "Host Groups", - "pages": [ - "api-reference/endpoints/ssh/groups/list", - "api-reference/endpoints/ssh/groups/create", - "api-reference/endpoints/ssh/groups/read", - "api-reference/endpoints/ssh/groups/update", - "api-reference/endpoints/ssh/groups/delete", - "api-reference/endpoints/ssh/groups/add-host", - "api-reference/endpoints/ssh/groups/list-hosts", - "api-reference/endpoints/ssh/groups/remove-host" - ] - }, - { - "group": "Certificates", - "pages": [ - "api-reference/endpoints/ssh/certificates/issue-credentials", - "api-reference/endpoints/ssh/certificates/sign-key" - ] - }, - { - "group": "Certificate Authorities", - "pages": [ - "api-reference/endpoints/ssh/ca/list", - "api-reference/endpoints/ssh/ca/create", - "api-reference/endpoints/ssh/ca/read", - "api-reference/endpoints/ssh/ca/update", - "api-reference/endpoints/ssh/ca/delete", - "api-reference/endpoints/ssh/ca/public-key", - "api-reference/endpoints/ssh/ca/list-certificate-templates" - ] - }, - { - "group": "Certificate Templates", - "pages": [ - "api-reference/endpoints/ssh/certificate-templates/list", - "api-reference/endpoints/ssh/certificate-templates/create", - "api-reference/endpoints/ssh/certificate-templates/read", - "api-reference/endpoints/ssh/certificate-templates/update", - "api-reference/endpoints/ssh/certificate-templates/delete" - ] - } - ] - }, - { - "group": "Infisical KMS", - "pages": [ - { - "group": "Keys", - "pages": [ - "api-reference/endpoints/kms/keys/list", - "api-reference/endpoints/kms/keys/get-by-id", - "api-reference/endpoints/kms/keys/get-by-name", - "api-reference/endpoints/kms/keys/create", - "api-reference/endpoints/kms/keys/update", - "api-reference/endpoints/kms/keys/delete" - ] - }, - { - "group": "Encryption", - "pages": [ - "api-reference/endpoints/kms/encryption/encrypt", - "api-reference/endpoints/kms/encryption/decrypt" - ] - }, - { - "group": "Signing", - "pages": [ - "api-reference/endpoints/kms/signing/sign", - "api-reference/endpoints/kms/signing/verify", - "api-reference/endpoints/kms/signing/public-key", - "api-reference/endpoints/kms/signing/signing-algorithms" - ] - } - ] - }, - { - "group": "Internals", - "pages": [ - "internals/overview", - { - "group": "Permissions", - "pages": [ - "internals/permissions/overview", - "internals/permissions/project-permissions", - "internals/permissions/organization-permissions", - "internals/permissions/migration" - ] - }, - "internals/components", - "internals/security", - "internals/service-tokens" - ] - }, - { - "group": "", - "pages": ["changelog/overview"] - }, - { - "group": "Contributing", - "pages": [ - { - "group": "Getting Started", - "pages": [ - "contributing/getting-started/overview", - "contributing/getting-started/code-of-conduct", - "contributing/getting-started/pull-requests", - "contributing/getting-started/faq" - ] - }, - { - "group": "Contributing to platform", - "pages": [ - "contributing/platform/developing", - "contributing/platform/backend/how-to-create-a-feature", - "contributing/platform/backend/folder-structure" - ] - }, - { - "group": "Contributing to SDK", - "pages": ["contributing/sdk/developing"] - } - ] - } - ], - "analytics": { - "koala": { - "publicApiKey": "pk_b50d7184e0e39ddd5cdb43cf6abeadd9b97d" - } - }, - "footer": { - "socials": { - "x": "https://www.twitter.com/infisical/", - "linkedin": "https://www.linkedin.com/company/infisical/", - "github": "https://github.com/Infisical/infisical-cli", - "slack": "https://infisical.com/slack" - }, - "links": [ - { - "title": "PRODUCT", - "links": [ - { - "label": "Secret Management", - "url": "https://infisical.com/" - }, - { - "label": "Secret Scanning", - "url": "https://infisical.com/radar" - }, - { - "label": "Share Secrets", - "url": "https://app.infisical.com/share-secret" - }, - { - "label": "Pricing", - "url": "https://infisical.com/pricing" - }, - { - "label": "Security", - "url": "https://infisical.com/docs/internals/security" - }, - { - "label": "Blog", - "url": "https://infisical.com/blog" - }, - { - "label": "Infisical vs Vault", - "url": "https://infisical.com/infisical-vs-hashicorp-vault" - }, - { - "label": "Forum", - "url": "https://questions.infisical.com/" - } - ] - }, - { - "title": "USE CASES", - "links": [ - { - "label": "Infisical Agent", - "url": "https://infisical.com/docs/documentation/getting-started/introduction" - }, - { - "label": "Kubernetes", - "url": "https://infisical.com/docs/integrations/platforms/kubernetes" - }, - { - "label": "Dynamic Secrets", - "url": "https://infisical.com/docs/documentation/platform/dynamic-secrets/overview" - }, - { - "label": "Terraform", - "url": "https://infisical.com/docs/integrations/frameworks/terraform" - }, - { - "label": "Ansible", - "url": "https://infisical.com/docs/integrations/platforms/ansible" - }, - { - "label": "Jenkins", - "url": "https://infisical.com/docs/integrations/cicd/jenkins" - }, - { - "label": "Docker", - "url": "https://infisical.com/docs/integrations/platforms/docker-intro" - }, - { - "label": "AWS ECS", - "url": "https://infisical.com/docs/integrations/platforms/ecs-with-agent" - }, - { - "label": "GitLab", - "url": "https://infisical.com/docs/integrations/cicd/gitlab" - }, - { - "label": "GitHub", - "url": "https://infisical.com/docs/integrations/cicd/githubactions" - }, - { - "label": "SDK", - "url": "https://infisical.com/docs/sdks/overview" - } - ] - }, - { - "title": "DEVELOPERS", - "links": [ - { - "label": "Changelog", - "url": "https://www.infisical.com/docs/changelog" - }, - { - "label": "Status", - "url": "https://status.infisical.com/" - }, - { - "label": "Feedback & Requests", - "url": "https://github.com/Infisical/infisical/issues" - }, - { - "label": "Trust of Center", - "url": "https://app.vanta.com/infisical.com/trust/hoop8cr78cuarxo9sztvs" - }, - { - "label": "Open Source Friends", - "url": "https://infisical.com/infisical-friends" - }, - { - "label": "How to contribute", - "url": "https://www.infisical.com/infisical-heroes" - } - ] - }, - { - "title": "OTHERS", - "links": [ - { - "label": "Customers", - "url": "https://infisical.com/customers/traba" - }, - { - "label": "Company Handbook", - "url": "https://infisical.com/wiki/handbook/overview" - }, - { - "label": "Careers", - "url": "https://infisical.com/careers" - }, - { - "label": "Terms of Service", - "url": "https://infisical.com/terms" - }, - { - "label": "Privacy Policy", - "url": "https://infisical.com/privacy" - }, - { - "label": "Subprocessors", - "url": "https://infisical.com/subprocessors" - }, - { - "label": "SLA", - "url": "https://infisical.com/sla" - }, - { - "label": "Team Email", - "url": "mailto:team@infisical.com" - }, - { - "label": "Sales", - "url": "mailto:sales@infisical.com" - }, - { - "label": "Support", - "url": "https://infisical.com/slack" - } - ] - } - ] - } -} diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index f5d1c58d0..90f3b2207 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -32,7 +32,7 @@ Used to configure platform-specific security and operational settings Specifies the network interface Infisical will bind to when accepting incoming connections. - By default, Infisical binds to `localhost`, which restricts access to connections from the same machine. +By default, Infisical binds to `localhost`, which restricts access to connections from the same machine. To make the application accessible externally (e.g., for self-hosted deployments), set this to `0.0.0.0`, which tells the server to listen on all network interfaces. @@ -122,6 +122,7 @@ DB_READ_REPLICAS=[{"DB_CONNECTION_URI":""}] ### Redis + Redis is used for caching and background tasks. You can use either a standalone Redis instance or a Redis Sentinel setup. @@ -199,8 +200,17 @@ Without email configuration, Infisical's core functions like sign-up/login and s connection can not be encrypted then message is not sent. - - If this is `true`, Infisical will validate the server's SSL/TLS certificate and reject the connection if the certificate is invalid or not trusted. If set to `false`, the client will accept the server's certificate regardless of its validity, which can be useful in development or testing environments but is not recommended for production use. + + If this is `true`, Infisical will validate the server's SSL/TLS certificate + and reject the connection if the certificate is invalid or not trusted. If set + to `false`, the client will accept the server's certificate regardless of its + validity, which can be useful in development or testing environments but is + not recommended for production use. @@ -211,6 +221,7 @@ Without email configuration, Infisical's core functions like sign-up/login and s Infisical highly encourages the following variables be used alongside this one for maximum security: - `SMTP_REQUIRE_TLS=true` - `SMTP_TLS_REJECT_UNAUTHORIZED=true` + @@ -577,6 +588,7 @@ You can configure third-party app connections for re-use across Infisical Projec The webhook secret configured for payload verification in the GitHub Radar App + @@ -771,3 +783,14 @@ If export type is set to `otlp`, you will have to configure a value for `OTEL_EX The password for authenticating with the telemetry collector. + +## Identity Auth Method + + + The TLS header used to propagate the client certificate from the load balancer + to the server. + diff --git a/frontend/public/lotties/infisical_loading.json b/frontend/public/lotties/infisical_loading.json new file mode 100644 index 000000000..f3ad78900 --- /dev/null +++ b/frontend/public/lotties/infisical_loading.json @@ -0,0 +1 @@ +{"v":"5.7.5","fr":100,"ip":0,"op":300,"w":800,"h":419,"nm":"Comp 1","ddd":0,"metadata":{},"assets":[{"id":"0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector (Stroke)","sr":1,"ks":{"p":{"a":0,"k":[-205,-431],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","nm":"Vector (Stroke)","it":[{"ty":"gr","nm":"Path 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[20.447,-130.148],[-77.721,-88.116],[-139.588,-16.199],[-148.936,-1.772],[-139.006,12.261],[-71.202,92.517],[20.447,130.148],[148.936,0],[20.447,-130.148]],"i":[[0,0],[29.018,-24.696],[16.327,-25.197],[0,0],[0,0],[-23.672,-20.706],[-35.524,0],[0,71.767],[71.074,0]],"o":[[-35.542,0],[-24.403,20.768],[0,0],[0,0],[22.425,31.688],[26.864,23.498],[71.074,0],[0,-71.768],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Path 2","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[-61.273,-68.789],[-77.721,-88.116],[-61.273,-68.789]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Path 3","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[-44.825,-49.463],[20.447,-79.392],[98.18,0],[20.447,79.392],[-37.785,54.313],[-87.278,-2.751],[-44.825,-49.463],[-44.825,-49.463]],"i":[[0,0],[-18.899,0],[0,-44.371],[42.411,0],[20.197,17.666],[17.78,24.244],[-15.565,13.246],[0,0]],"o":[[24.072,-20.487],[42.411,0],[0,44.371],[-20.186,0],[-15.87,-13.881],[12.425,-17.11],[0,0],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gf","o":{"a":0,"k":100,"ix":2},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.9215686274509803,0.9450980392156862,0.4235294117647059,0.5,0.19215686274509805,0.20392156862745098,0.09019607843137255,1,0,0,0,0,1,0.5,1,1,0],"ix":2}},"s":{"a":0,"k":[-84.7012710571289,-1.981994867324829],"ix":2},"e":{"a":0,"k":[550.908447265625,-0.7451161742210388],"ix":2},"t":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[747.4509887695312,615.6649780273438],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[140.5150055885315,140.5150055885315],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":301,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector (Stroke)","sr":1,"ks":{"p":{"a":0,"k":[-205,-431],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","nm":"Vector (Stroke)","it":[{"ty":"gr","nm":"Path 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[-98.468,0],[-20.444,-79.392],[46.888,-43.887],[46.888,-43.887],[87.062,-0.165],[48.675,41.472],[65.75,60.248],[48.675,41.472],[-20.444,79.392],[-98.468,0]],"i":[[0,0],[-42.675,0],[-27.224,-24.453],[0,0],[-11.056,-14.336],[14.986,-13.628],[0,0],[0,0],[15.764,0],[0,44.267]],"o":[[0,-44.266],[16.468,0],[0,0],[15.303,13.745],[-10.159,13.06],[0,0],[0,0],[-28.618,26.025],[-42.675,0],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Path 2","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[-20.444,-130.149],[-149.224,0],[-20.444,130.149],[82.824,79.024],[82.824,79.023],[139.462,14.105],[149.224,-0.255],[139.377,-14.556],[80.805,-81.647],[-20.444,-130.149]],"i":[[0,0],[0,-71.764],[-71.238,0],[-30.616,27.842],[0,0],[-12.465,18.334],[0,0],[0,0],[24.224,21.758],[35.661,0]],"o":[[-71.238,0],[0,71.764],[35.674,0],[0,0],[24.139,-21.952],[0,0],[0,0],[-13.86,-20.129],[-29.959,-26.91],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gf","o":{"a":0,"k":100,"ix":2},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.9215686274509803,0.9450980392156862,0.4235294117647059,0.5,0.19215686274509805,0.20392156862745098,0.09019607843137255,1,0,0,0,0,1,0.5,1,1,0],"ix":2}},"s":{"a":0,"k":[84.2059555053711,0.10817349702119827],"ix":2},"e":{"a":0,"k":[-540.4896240234375,-5.982279300689697],"ix":2},"t":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[414.8349914550781,614.260009765625],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[140.5150055885315,140.5150055885315],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":301,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","nm":"Shape Layer 2","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[64.219,60.955],[75.017,50.459],[64.706,38.974],[56.737,41.937],[52.529,45.908],[46.994,53.295],[43.203,57.056],[35.02,60.825],[24.169,49.937],[34.908,38.936],[42.707,42.246],[47.079,46.558],[64.219,60.955]],"i":[[0,0],[-0.155,3.832],[5.611,0.221],[2.488,-1.995],[1.254,-1.476],[2.012,-2.367],[1.531,-1.317],[2.721,0.003],[0,5.642],[-6.195,0.163],[-2.204,-1.755],[-1.435,-1.717],[-7.72,0.139]],"o":[[7.629,-0.137],[0.248,-6.111],[-2.688,-0.106],[-1.511,1.212],[-2.012,2.368],[-1.153,1.356],[-3.063,2.455],[-6.495,-0.007],[0,-5.642],[2.954,-0.078],[1.264,1.149],[4.489,5.371],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":20,"ix":2},"o":{"a":1,"k":[{"t":0,"s":[61.00000000000001],"i":{"x":[0.833],"y":[0.904]},"o":{"x":[0.167],"y":[0.167]}},{"t":50,"s":[149],"i":{"x":[0.833],"y":[0.715]},"o":{"x":[0.167],"y":[0.258]}},{"t":153,"s":[216.99999999999997],"i":{"x":[0.833],"y":[0.889]},"o":{"x":[0.167],"y":[0.092]}},{"t":207,"s":[325.00000000000006],"i":{"x":[0.833],"y":[0.81]},"o":{"x":[0.167],"y":[0.25]}},{"t":287,"s":[397.00000000000006],"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.095]}},{"t":300,"s":[421.00000000000006],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"m":1},{"ty":"gs","o":{"a":0,"k":100,"ix":2},"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.9215686274509803,0.9450980392156862,0.4235294117647059,0.5,0.19215686274509805,0.20392156862745098,0.09019607843137255,1,0,0,0,0,1,0.5,1,1,0],"ix":2}},"s":{"a":0,"k":[49.317,61.556],"ix":2},"e":{"a":1,"k":[{"t":0,"s":[131.06,-44.124],"i":{"x":[0.692],"y":[0.616]},"o":{"x":[0.362],"y":[0]}},{"t":67,"s":[76.234,-60.88],"i":{"x":[0.667],"y":[0.906]},"o":{"x":[0.359],"y":[0.608]}},{"t":157,"s":[23.388,-39.908],"i":{"x":[0.654],"y":[0.496]},"o":{"x":[0.328],"y":[0.076]}},{"t":227,"s":[75.71,-53.944],"i":{"x":[0.637],"y":[1]},"o":{"x":[0.31],"y":[0.455]}},{"t":300,"s":[131.06,-44.124],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"t":1,"w":{"a":0,"k":5.4,"ix":2},"lc":1,"lj":1,"ml":4},{"ty":"tr","p":{"a":0,"k":[400.33172607421875,209.90084838867188],"ix":2},"a":{"a":0,"k":[49.59747009478117,49.94493849689434],"ix":2},"s":{"a":0,"k":[1339.9999618530273,1339.9999618530273],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":301,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"","sr":1,"ks":{"p":{"a":0,"k":[400.0003662109375,209.50125122070312],"ix":2},"a":{"a":0,"k":[580.941,614.963],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"ip":0,"op":301,"st":0,"bm":0},{"ddd":0,"refId":"0","w":752,"h":368,"ind":5,"ty":0,"nm":"Vector (Stroke) :M","sr":1,"ks":{"p":{"a":0,"k":[205,431],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":55,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"ip":0,"op":301,"st":0,"bm":0,"parent":4}],"markers":[]} \ No newline at end of file diff --git a/frontend/public/lotties/infisical_loading_bw.json b/frontend/public/lotties/infisical_loading_bw.json new file mode 100644 index 000000000..3f9dda4a4 --- /dev/null +++ b/frontend/public/lotties/infisical_loading_bw.json @@ -0,0 +1 @@ +{"v":"5.7.5","fr":100,"ip":0,"op":300,"w":800,"h":420,"nm":"Comp 1","ddd":0,"metadata":{},"assets":[{"id":"0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector (Stroke)","sr":1,"ks":{"p":{"a":0,"k":[-205,-431],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","nm":"Vector (Stroke)","it":[{"ty":"gr","nm":"Path 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[20.447,-130.148],[-77.721,-88.116],[-139.588,-16.199],[-148.936,-1.772],[-139.006,12.261],[-71.202,92.517],[20.447,130.148],[148.936,0],[20.447,-130.148]],"i":[[0,0],[29.018,-24.696],[16.327,-25.197],[0,0],[0,0],[-23.672,-20.706],[-35.524,0],[0,71.767],[71.074,0]],"o":[[-35.542,0],[-24.403,20.768],[0,0],[0,0],[22.425,31.688],[26.864,23.498],[71.074,0],[0,-71.768],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Path 2","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[-61.273,-68.789],[-77.721,-88.116],[-61.273,-68.789]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Path 3","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[-44.825,-49.463],[20.447,-79.392],[98.18,0],[20.447,79.392],[-37.785,54.313],[-87.278,-2.751],[-44.825,-49.463],[-44.825,-49.463]],"i":[[0,0],[-18.899,0],[0,-44.371],[42.411,0],[20.197,17.666],[17.78,24.244],[-15.565,13.246],[0,0]],"o":[[24.072,-20.487],[42.411,0],[0,44.371],[-20.186,0],[-15.87,-13.881],[12.425,-17.11],[0,0],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gf","o":{"a":0,"k":100,"ix":2},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0,0,0,0.5,0,0,0,1,0,0,0,0,1,0.5,0.5,1,0],"ix":2}},"s":{"a":0,"k":[-86.28473663330078,1.4994840621948242],"ix":2},"e":{"a":0,"k":[462.44384765625,-5.23858642578125],"ix":2},"t":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[747.4509887695312,615.6649780273438],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[140.5150055885315,140.5150055885315],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":301,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector (Stroke)","sr":1,"ks":{"p":{"a":0,"k":[-205,-431],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","nm":"Vector (Stroke)","it":[{"ty":"gr","nm":"Path 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[-98.468,0],[-20.444,-79.392],[46.888,-43.887],[46.888,-43.887],[87.062,-0.165],[48.675,41.472],[65.75,60.248],[48.675,41.472],[-20.444,79.392],[-98.468,0]],"i":[[0,0],[-42.675,0],[-27.224,-24.453],[0,0],[-11.056,-14.336],[14.986,-13.628],[0,0],[0,0],[15.764,0],[0,44.267]],"o":[[0,-44.266],[16.468,0],[0,0],[15.303,13.745],[-10.159,13.06],[0,0],[0,0],[-28.618,26.025],[-42.675,0],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Path 2","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[-20.444,-130.149],[-149.224,0],[-20.444,130.149],[82.824,79.024],[82.824,79.023],[139.462,14.105],[149.224,-0.255],[139.377,-14.556],[80.805,-81.647],[-20.444,-130.149]],"i":[[0,0],[0,-71.764],[-71.238,0],[-30.616,27.842],[0,0],[-12.465,18.334],[0,0],[0,0],[24.224,21.758],[35.661,0]],"o":[[-71.238,0],[0,71.764],[35.674,0],[0,0],[24.139,-21.952],[0,0],[0,0],[-13.86,-20.129],[-29.959,-26.91],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gf","o":{"a":0,"k":100,"ix":2},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0,0,0,0.5,0,0,0,1,0,0,0,0,1,0.5,0.5,1,0],"ix":2}},"s":{"a":0,"k":[62.10511016845703,5.982990741729736],"ix":2},"e":{"a":0,"k":[-461.3863220214844,-3.3000035285949707],"ix":2},"t":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[414.8349914550781,614.260009765625],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[140.5150055885315,140.5150055885315],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":301,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","nm":"Shape Layer 2","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[64.219,60.955],[75.017,50.459],[64.706,38.974],[56.737,41.937],[52.529,45.908],[46.994,53.295],[43.203,57.056],[35.02,60.825],[24.169,49.937],[34.908,38.936],[42.707,42.246],[47.079,46.558],[64.219,60.955]],"i":[[0,0],[-0.155,3.832],[5.611,0.221],[2.488,-1.995],[1.254,-1.476],[2.012,-2.367],[1.531,-1.317],[2.721,0.003],[0,5.642],[-6.195,0.163],[-2.204,-1.755],[-1.435,-1.717],[-7.72,0.139]],"o":[[7.629,-0.137],[0.248,-6.111],[-2.688,-0.106],[-1.511,1.212],[-2.012,2.368],[-1.153,1.356],[-3.063,2.455],[-6.495,-0.007],[0,-5.642],[2.954,-0.078],[1.264,1.149],[4.489,5.371],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":20,"ix":2},"o":{"a":1,"k":[{"t":0,"s":[61.00000000000001],"i":{"x":[0.833],"y":[0.904]},"o":{"x":[0.167],"y":[0.167]}},{"t":50,"s":[149],"i":{"x":[0.833],"y":[0.715]},"o":{"x":[0.167],"y":[0.258]}},{"t":153,"s":[216.99999999999997],"i":{"x":[0.833],"y":[0.889]},"o":{"x":[0.167],"y":[0.092]}},{"t":207,"s":[325.00000000000006],"i":{"x":[0.833],"y":[0.81]},"o":{"x":[0.167],"y":[0.25]}},{"t":287,"s":[397.00000000000006],"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.095]}},{"t":300,"s":[421.00000000000006],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"m":1},{"ty":"gs","o":{"a":0,"k":100,"ix":2},"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0,0,0,0.5,0,0,0,1,0,0,0,0,1,0.5,0.5,1,0],"ix":2}},"s":{"a":0,"k":[49.759,50.221],"ix":2},"e":{"a":1,"k":[{"t":0,"s":[120.553,-22.385],"i":{"x":[0.692],"y":[0.616]},"o":{"x":[0.362],"y":[0]}},{"t":67,"s":[65.727,-39.14],"i":{"x":[0.682],"y":[1]},"o":{"x":[0.359],"y":[0.551]}},{"t":150,"s":[8.856,-27.571],"i":{"x":[0.653],"y":[0.47]},"o":{"x":[0.329],"y":[0]}},{"t":227,"s":[65.202,-32.204],"i":{"x":[0.637],"y":[1]},"o":{"x":[0.31],"y":[0.455]}},{"t":300,"s":[120.553,-22.385],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"t":1,"w":{"a":0,"k":5.4,"ix":2},"lc":1,"lj":1,"ml":4},{"ty":"tr","p":{"a":0,"k":[400.3317565917969,210.40078735351562],"ix":2},"a":{"a":0,"k":[49.59747009478117,49.94493849689434],"ix":2},"s":{"a":0,"k":[1339.9999618530273,1339.9999618530273],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":301,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"","sr":1,"ks":{"p":{"a":0,"k":[400.0003356933594,210.00125122070312],"ix":2},"a":{"a":0,"k":[580.941,614.963],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"ip":0,"op":301,"st":0,"bm":0},{"ddd":0,"refId":"0","w":752,"h":368,"ind":5,"ty":0,"nm":"Vector (Stroke) :M","sr":1,"ks":{"p":{"a":0,"k":[205,431],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":33,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"ip":0,"op":301,"st":0,"bm":0,"parent":4}],"markers":[]} \ No newline at end of file diff --git a/frontend/public/lotties/terminal.json b/frontend/public/lotties/terminal.json new file mode 100644 index 000000000..9a6228cfa --- /dev/null +++ b/frontend/public/lotties/terminal.json @@ -0,0 +1,365 @@ +{ + "v": "5.7.5", + "fr": 100, + "ip": 0, + "op": 100, + "w": 22, + "h": 20, + "nm": "Comp 1", + "ddd": 0, + "metadata": {}, + "assets": [ + { + "id": "0", + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Path 1", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [-6, -6], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "nm": "Path 1", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": false, + "v": [ + [9.25, 9.25], + [12.25, 9.25] + ], + "i": [ + [0, 0], + [0, 0] + ], + "o": [ + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "st", + "c": { "a": 0, "k": [1, 1, 1], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "w": { "a": 0, "k": 1.5, "ix": 2 }, + "lc": 2, + "lj": 2, + "ml": 4 + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + } + ], + "ip": 0, + "op": 101, + "st": 0, + "bm": 0 + } + ] + } + ], + "layers": [ + { + "ddd": 0, + "ind": 2, + "ty": 3, + "nm": "", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [0.7886505126953125, 0.7357635498046875], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "ip": 0, + "op": 101, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "refId": "0", + "w": 10, + "h": 7, + "ind": 3, + "ty": 0, + "nm": "10 Outlines 2", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [6, 6], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { + "a": 1, + "k": [ + { "t": 0, "s": [100], "i": { "x": [0.667], "y": [1] }, "o": { "x": [1], "y": [0] } }, + { "t": 50, "s": [0], "i": { "x": [0.667], "y": [1] }, "o": { "x": [1], "y": [0] } }, + { + "t": 100, + "s": [100], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "ip": 0, + "op": 101, + "st": 0, + "bm": 0, + "parent": 2 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "10 Outlines", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "gr", + "it": [ + { + "ty": "gr", + "nm": "Path 1", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": false, + "v": [ + [4.75, 4.75], + [7.75, 7], + [4.75, 9.25] + ], + "i": [ + [0, 0], + [0, 0], + [0, 0] + ], + "o": [ + [0, 0], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "st", + "c": { "a": 0, "k": [1, 1, 1], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "w": { "a": 0, "k": 1, "ix": 2 }, + "lc": 2, + "lj": 2, + "ml": 4 + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "it": [ + { + "ty": "gr", + "nm": "Group", + "it": [ + { + "ty": "gr", + "nm": "Path 2", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [3.25, 17.5], + [16.75, 17.5], + [18.341, 16.841], + [19, 15.25], + [19, 3.25], + [18.341, 1.659], + [16.75, 1], + [3.25, 1], + [1.659, 1.659], + [1, 3.25], + [1, 15.25], + [1.659, 16.841], + [3.25, 17.5] + ], + "i": [ + [0, 0], + [0, 0], + [-0.422, 0.422], + [0, 0.597], + [0, 0], + [0.422, 0.422], + [0.597, 0], + [0, 0], + [0.422, -0.422], + [0, -0.597], + [0, 0], + [-0.422, -0.422], + [-0.597, 0] + ], + "o": [ + [0, 0], + [0.597, 0], + [0.422, -0.422], + [0, 0], + [0, -0.597], + [-0.422, -0.422], + [0, 0], + [-0.597, 0], + [-0.422, 0.422], + [0, 0], + [0, 0.597], + [0.422, 0.422], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "st", + "c": { "a": 0, "k": [1, 1, 1], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "w": { "a": 0, "k": 1.5, "ix": 2 }, + "lc": 2, + "lj": 2, + "ml": 4 + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0.7886505126953125, 0.7357635498046875], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + } + ], + "ip": 0, + "op": 101, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} + diff --git a/frontend/public/lotties/vault.json b/frontend/public/lotties/vault.json new file mode 100644 index 000000000..13f1ab507 --- /dev/null +++ b/frontend/public/lotties/vault.json @@ -0,0 +1,992 @@ +{ + "v": "5.7.5", + "fr": 100, + "ip": 0, + "op": 200, + "w": 500, + "h": 500, + "nm": "Comp 1", + "ddd": 0, + "metadata": {}, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 2", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "nm": "Shape Layer 2", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [49.3180325191623, 68.7788286222386], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 } + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { "a": 0, "k": [36.65843963623047, 374.4748840332031], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + } + ], + "ip": 0, + "op": 201, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Shape Layer 1", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "nm": "Shape Layer 1", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [49.3180325191623, 68.7788286222386], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 } + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { "a": 0, "k": [36.65843963623047, 152], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + } + ], + "ip": 0, + "op": 201, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "bank-safe-box-svgrepo-com.svg 1", + "sr": 1, + "ks": { + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "gr", + "nm": "bank-safe-box-svgrepo-com.svg 1", + "it": [ + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [33.06575776130341, 49.665937523501114], + [20.000300594090934, 36.60048035628864], + [33.06575776130341, 23.530797074065426], + [46.12614359050298, 36.60048035628864], + [33.06575776130341, 49.665937523501114], + [33.06575776130341, 49.665937523501114] + ], + "i": [ + [0, 0], + [0, 7.20383564731219], + [-7.208906985325083, 0], + [0, -7.208061762322929], + [7.1996095323014515, 0], + [0, 0] + ], + "o": [ + [-7.20383564731219, 0], + [0, -7.20383564731219], + [7.1996095323014515, 0], + [0, 7.1996095323014515], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-45.124942779541016, -76.91899108886719], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 1.36079623004602e-7, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [56.59403564377824, 2.417355107683346], + [49.427568347046794, 2.417355107683346], + [38.852034318840836, 13.001059012587813], + [29.055609441248567, 10.541183410268967], + [19.84890111807693, 12.677977524964414], + [8.650971028656253, 1.4845037319247485], + [1.4845037319248013, 1.4845037319247485], + [1.4845037319248013, 8.650971028656201], + [11.935261461462396, 19.101728758193797], + [7.4351448327033705, 32.15793443849666], + [11.935261461462396, 45.210426538482], + [1.5253531154174096, 55.624791180908], + [1.5253531154174096, 62.78680218125844], + [5.108215405751383, 64.27371974038958], + [8.691077696085358, 62.78680218125844], + [19.84444482169591, 51.63343505564789], + [29.051153144867545, 53.76651559002584], + [39.33182889586317, 51.04371850122726], + [49.17653031758308, 60.89213350326467], + [52.75939260791704, 62.3745947660148], + [56.34225489825104, 60.89213350326467], + [56.34225489825104, 53.72566620653322], + [46.913474472092304, 44.296885780374474], + [50.66196244458722, 32.15347814211567], + [46.56959693469084, 19.59786308861321], + [56.58363761888923, 9.583822404414821], + [56.59403564377824, 2.417355107683346], + [56.59403564377824, 2.417355107683346] + ], + "i": [ + [0, 0], + [1.979338309233005, -1.9756247289154982], + [3.5251780094019862, -3.527901301634823], + [3.5427556229048607, 0], + [2.8037531397203104, -1.333175333986213], + [3.732643363140226, 3.731157931013225], + [1.9793383092330157, -1.9793383092330104], + [-1.9793383092330157, -1.9793383092330157], + [-3.483585909845862, -3.483585909845862], + [0, -4.9204939207012], + [-2.779243509624741, -3.6363378469061254], + [3.469969448681659, -3.4714548808086705], + [-1.9793383092330157, -1.979338309233005], + [-1.296782246874607, 0], + [-0.9892977965847497, 0.9900405126482554], + [-3.7177890418701867, 3.717789041870177], + [-3.308800062901711, 0], + [-3.0696454904540436, 1.6807664517051837], + [-3.281567140573295, -3.2828050006791307], + [-1.2967822468746175, 0], + [-0.9892977965847708, 0.9892977965847708], + [1.979338309233005, 1.979338309233005], + [3.142926808719575, 3.1429268087195856], + [0, 4.496403048441521], + [2.5490015299390887, 3.5472119192858735], + [-3.3380135613994684, 3.3380135613994586], + [1.979338309233005, 1.983794605614028], + [0, 0] + ], + "o": [ + [-1.979338309233005, -1.9756247289154982], + [-3.5251780094019756, 3.527901301634823], + [-2.9530390684842214, -1.5233106462427428], + [-3.3043437665206987, 0], + [-3.732643363140226, -3.731157931013225], + [-1.9793383092330157, -1.9793383092330104], + [-1.9793383092330157, 1.9793383092330157], + [3.483585909845862, 3.483585909845862], + [-2.779243509624741, 3.6355951308426193], + [0, 4.9204939207012], + [-3.469969448681659, 3.47145488080866], + [-1.9793383092330157, 1.9756247289154982], + [0.9900405126482554, 0.9900405126482554], + [1.296782246874607, 0], + [3.7177890418701867, -3.717789041870177], + [2.7992968433392984, 1.3287190376051898], + [3.741060811859921, 0], + [3.2815671405733053, 3.2828050006791307], + [0.9900405126482554, 0.9900405126482554], + [1.2967822468746175, 0], + [1.979338309233005, -1.979338309233005], + [-3.142926808719575, -3.142926808719575], + [2.3588662176825586, -3.46179957198314], + [0, -4.693965521333064], + [3.3380135613994684, -3.3380135613994586], + [1.9897363341220202, -1.9756247289155193], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-41.346412658691406, -72.31075286865234], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 1.36079623004602e-7, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [563.65673828125, 513.2783813476562], "ix": 2 }, + "a": { "a": 0, "k": [-12.307790756225586, -40.173892974853516], "ix": 2 }, + "s": { "a": 0, "k": [366.3825750350952, 366.3825750350952], "ix": 2 }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [0], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + }, + { + "t": 100, + "s": [180], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [16.204001519577492, 10.809684651948828], + [107.78162573450534, 10.809684651948828], + [107.78162573450534, 103.16527938802837], + [16.204001519577492, 103.16527938802837], + [16.204001519577492, 10.809684651948828], + [16.204001519577492, 10.809684651948828] + ], + "i": [ + [0, 0], + [-30.52587473830928, 0], + [0, -30.785198245359847], + [30.52587473830929, 0], + [0, 30.785198245359847], + [0, 0] + ], + "o": [ + [30.52587473830928, 0], + [0, 30.785198245359833], + [-30.52587473830928, 0], + [0, -30.785198245359847], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-33.81110763549805, -68.36682891845703], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [5.187128475098463, 75.41686707022457], + [0.11734912644122932, 75.41686707022457], + [0.11734912644122932, 85.55642576753904], + [5.187128475098463, 85.55642576753904], + [5.187128475098463, 101.79962447177776], + [10.253194243805027, 106.869403820435], + [106.98749566286637, 106.869403820435], + [112.05356143157292, 101.79962447177776], + [112.05356143157292, 5.066065768706561], + [106.98749566286637, 0], + [10.2569078237557, 0], + [5.190842055049137, 5.066065768706561], + [5.190842055049137, 21.277327685369535], + [0, 21.277327685369535], + [0, 31.413172802733335], + [5.190842055049137, 31.413172802733335], + [5.190842055049137, 75.4176097862147], + [5.187128475098463, 75.4176097862147], + [5.187128475098463, 75.41686707022457] + ], + "i": [ + [0, 0], + [1.6899264495524133, 0], + [0, -3.3798528991048267], + [-1.6899264495524133, 0], + [0, -5.414399568079578], + [-2.795582986865779, 0], + [-32.24476713968711, 0], + [0, 2.799296566816458], + [0, 32.244519567690396], + [2.7955829868657895, 0], + [32.243529279703566, 0], + [0, -2.800039282806585], + [0, -5.40375397222099], + [1.7302806850163799, 0], + [0, -3.3786150391212706], + [-1.7302806850163799, 0], + [0, -14.66814566116046], + [0.0012378599835561636, 0], + [0, 0.0002475719967196764] + ], + "o": [ + [-1.6899264495524133, 0], + [0, 3.3798528991048267], + [1.6899264495524107, 0], + [0, 5.414399568079578], + [0, 2.799296566816458], + [32.24476713968713, 0], + [2.799296566816458, 0], + [0, -32.2445195676904], + [0, -2.799296566816452], + [-32.24352927970354, 0], + [-2.799296566816452, 0], + [0, 5.40375397222099], + [-1.7302806850163799, 0], + [0, 3.3786150391212706], + [1.7302806850163799, 0], + [0, 14.66814566116045], + [-0.0012378599835561636, 0], + [0, -0.0002475719967196764], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-30.517711639404297, -64.98767852783203], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [738.9981689453125, 819], "ix": 2 }, + "a": { "a": 0, "k": [75.43499755859375, 71.94499969482422], "ix": 2 }, + "s": { "a": 0, "k": [366.3825750350952, 366.3825750350952], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "tr", + "p": { "a": 0, "k": [360.7227783203125, 392.2637023925781], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [454.6020030975342, 454.6020030975342], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "fl", + "c": { "a": 0, "k": [1, 1, 1], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0 + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "it": [ + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [33.06575776130341, 49.665937523501114], + [20.000300594090934, 36.60048035628864], + [33.06575776130341, 23.530797074065426], + [46.12614359050298, 36.60048035628864], + [33.06575776130341, 49.665937523501114], + [33.06575776130341, 49.665937523501114] + ], + "i": [ + [0, 0], + [0, 7.20383564731219], + [-7.208906985325083, 0], + [0, -7.208061762322929], + [7.1996095323014515, 0], + [0, 0] + ], + "o": [ + [-7.20383564731219, 0], + [0, -7.20383564731219], + [7.1996095323014515, 0], + [0, 7.1996095323014515], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-45.124942779541016, -76.91899108886719], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 1.36079623004602e-7, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [56.59403564377824, 2.417355107683346], + [49.427568347046794, 2.417355107683346], + [38.852034318840836, 13.001059012587813], + [29.055609441248567, 10.541183410268967], + [19.84890111807693, 12.677977524964414], + [8.650971028656253, 1.4845037319247485], + [1.4845037319248013, 1.4845037319247485], + [1.4845037319248013, 8.650971028656201], + [11.935261461462396, 19.101728758193797], + [7.4351448327033705, 32.15793443849666], + [11.935261461462396, 45.210426538482], + [1.5253531154174096, 55.624791180908], + [1.5253531154174096, 62.78680218125844], + [5.108215405751383, 64.27371974038958], + [8.691077696085358, 62.78680218125844], + [19.84444482169591, 51.63343505564789], + [29.051153144867545, 53.76651559002584], + [39.33182889586317, 51.04371850122726], + [49.17653031758308, 60.89213350326467], + [52.75939260791704, 62.3745947660148], + [56.34225489825104, 60.89213350326467], + [56.34225489825104, 53.72566620653322], + [46.913474472092304, 44.296885780374474], + [50.66196244458722, 32.15347814211567], + [46.56959693469084, 19.59786308861321], + [56.58363761888923, 9.583822404414821], + [56.59403564377824, 2.417355107683346], + [56.59403564377824, 2.417355107683346] + ], + "i": [ + [0, 0], + [1.979338309233005, -1.9756247289154982], + [3.5251780094019862, -3.527901301634823], + [3.5427556229048607, 0], + [2.8037531397203104, -1.333175333986213], + [3.732643363140226, 3.731157931013225], + [1.9793383092330157, -1.9793383092330104], + [-1.9793383092330157, -1.9793383092330157], + [-3.483585909845862, -3.483585909845862], + [0, -4.9204939207012], + [-2.779243509624741, -3.6363378469061254], + [3.469969448681659, -3.4714548808086705], + [-1.9793383092330157, -1.979338309233005], + [-1.296782246874607, 0], + [-0.9892977965847497, 0.9900405126482554], + [-3.7177890418701867, 3.717789041870177], + [-3.308800062901711, 0], + [-3.0696454904540436, 1.6807664517051837], + [-3.281567140573295, -3.2828050006791307], + [-1.2967822468746175, 0], + [-0.9892977965847708, 0.9892977965847708], + [1.979338309233005, 1.979338309233005], + [3.142926808719575, 3.1429268087195856], + [0, 4.496403048441521], + [2.5490015299390887, 3.5472119192858735], + [-3.3380135613994684, 3.3380135613994586], + [1.979338309233005, 1.983794605614028], + [0, 0] + ], + "o": [ + [-1.979338309233005, -1.9756247289154982], + [-3.5251780094019756, 3.527901301634823], + [-2.9530390684842214, -1.5233106462427428], + [-3.3043437665206987, 0], + [-3.732643363140226, -3.731157931013225], + [-1.9793383092330157, -1.9793383092330104], + [-1.9793383092330157, 1.9793383092330157], + [3.483585909845862, 3.483585909845862], + [-2.779243509624741, 3.6355951308426193], + [0, 4.9204939207012], + [-3.469969448681659, 3.47145488080866], + [-1.9793383092330157, 1.9756247289154982], + [0.9900405126482554, 0.9900405126482554], + [1.296782246874607, 0], + [3.7177890418701867, -3.717789041870177], + [2.7992968433392984, 1.3287190376051898], + [3.741060811859921, 0], + [3.2815671405733053, 3.2828050006791307], + [0.9900405126482554, 0.9900405126482554], + [1.2967822468746175, 0], + [1.979338309233005, -1.979338309233005], + [-3.142926808719575, -3.142926808719575], + [2.3588662176825586, -3.46179957198314], + [0, -4.693965521333064], + [3.3380135613994684, -3.3380135613994586], + [1.9897363341220202, -1.9756247289155193], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-41.346412658691406, -72.31075286865234], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 1.36079623004602e-7, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "fl", + "c": { "a": 0, "k": [0.00392156862745098, 0, 0.00784313725490196], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0, + "hd": true + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [563.65673828125, 513.2783813476562], "ix": 2 }, + "a": { "a": 0, "k": [-12.307790756225586, -40.173892974853516], "ix": 2 }, + "s": { "a": 0, "k": [366.3825750350952, 366.3825750350952], "ix": 2 }, + "r": { + "a": 1, + "k": [ + { + "t": 0, + "s": [0], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + }, + { + "t": 100, + "s": [180], + "i": { "x": [0.75], "y": [0.75] }, + "o": { "x": [0.25], "y": [0.25] } + } + ], + "ix": 2 + }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "it": [ + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [16.204001519577492, 10.809684651948828], + [107.78162573450534, 10.809684651948828], + [107.78162573450534, 103.16527938802837], + [16.204001519577492, 103.16527938802837], + [16.204001519577492, 10.809684651948828], + [16.204001519577492, 10.809684651948828] + ], + "i": [ + [0, 0], + [-30.52587473830928, 0], + [0, -30.785198245359847], + [30.52587473830929, 0], + [0, 30.785198245359847], + [0, 0] + ], + "o": [ + [30.52587473830928, 0], + [0, 30.785198245359833], + [-30.52587473830928, 0], + [0, -30.785198245359847], + [0, 0], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-33.81110763549805, -68.36682891845703], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "sh", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "v": [ + [5.187128475098463, 75.41686707022457], + [0.11734912644122932, 75.41686707022457], + [0.11734912644122932, 85.55642576753904], + [5.187128475098463, 85.55642576753904], + [5.187128475098463, 101.79962447177776], + [10.253194243805027, 106.869403820435], + [106.98749566286637, 106.869403820435], + [112.05356143157292, 101.79962447177776], + [112.05356143157292, 5.066065768706561], + [106.98749566286637, 0], + [10.2569078237557, 0], + [5.190842055049137, 5.066065768706561], + [5.190842055049137, 21.277327685369535], + [0, 21.277327685369535], + [0, 31.413172802733335], + [5.190842055049137, 31.413172802733335], + [5.190842055049137, 75.4176097862147], + [5.187128475098463, 75.4176097862147], + [5.187128475098463, 75.41686707022457] + ], + "i": [ + [0, 0], + [1.6899264495524133, 0], + [0, -3.3798528991048267], + [-1.6899264495524133, 0], + [0, -5.414399568079578], + [-2.795582986865779, 0], + [-32.24476713968711, 0], + [0, 2.799296566816458], + [0, 32.244519567690396], + [2.7955829868657895, 0], + [32.243529279703566, 0], + [0, -2.800039282806585], + [0, -5.40375397222099], + [1.7302806850163799, 0], + [0, -3.3786150391212706], + [-1.7302806850163799, 0], + [0, -14.66814566116046], + [0.0012378599835561636, 0], + [0, 0.0002475719967196764] + ], + "o": [ + [-1.6899264495524133, 0], + [0, 3.3798528991048267], + [1.6899264495524107, 0], + [0, 5.414399568079578], + [0, 2.799296566816458], + [32.24476713968713, 0], + [2.799296566816458, 0], + [0, -32.2445195676904], + [0, -2.799296566816452], + [-32.24352927970354, 0], + [-2.799296566816452, 0], + [0, 5.40375397222099], + [-1.7302806850163799, 0], + [0, 3.3786150391212706], + [1.7302806850163799, 0], + [0, 14.66814566116045], + [-0.0012378599835561636, 0], + [0, -0.0002475719967196764], + [0, 0] + ] + } + } + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-30.517711639404297, -64.98767852783203], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "fl", + "c": { "a": 0, "k": [0.00392156862745098, 0, 0.00784313725490196], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0, + "hd": true + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [738.9981689453125, 819], "ix": 2 }, + "a": { "a": 0, "k": [75.43499755859375, 71.94499969482422], "ix": 2 }, + "s": { "a": 0, "k": [366.3825750350952, 366.3825750350952], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "gr", + "it": [ + { + "ty": "gr", + "nm": "Path", + "it": [ + { + "ty": "fl", + "c": { "a": 0, "k": [0.00392156862745098, 0, 0.00784313725490196], "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "r": 1, + "bm": 0, + "hd": true + }, + { + "ty": "tr", + "p": { "a": 0, "k": [0, 0], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [100, 100], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [360.7227783203125, 392.2637023925781], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 2 }, + "s": { "a": 0, "k": [454.6020030975342, 454.6020030975342], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + }, + { + "ty": "tr", + "p": { "a": 0, "k": [80.75597381591797, 122.56097412109375], "ix": 2 }, + "a": { "a": 0, "k": [399.99816274642944, 399.99998253828016], "ix": 2 }, + "s": { "a": 0, "k": [112.70011646566, 112.70011646566], "ix": 2 }, + "r": { "a": 0, "k": 0, "ix": 2 }, + "o": { "a": 0, "k": 100, "ix": 2 }, + "sk": { "a": 0, "k": 0, "ix": 2 }, + "sa": { "a": 0, "k": 0, "ix": 2 } + } + ] + } + ], + "ip": 0, + "op": 201, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} + diff --git a/frontend/src/components/auth/TeamInviteStep.tsx b/frontend/src/components/auth/TeamInviteStep.tsx index 5a9f11bde..b0a5618d9 100644 --- a/frontend/src/components/auth/TeamInviteStep.tsx +++ b/frontend/src/components/auth/TeamInviteStep.tsx @@ -4,7 +4,6 @@ import { useNavigate } from "@tanstack/react-router"; import { useAddUsersToOrg } from "@app/hooks/api"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; -import { ProjectType } from "@app/hooks/api/workspace/types"; import { usePopUp } from "@app/hooks/usePopUp"; import { Button, EmailServiceSetupModal } from "../v2"; @@ -23,7 +22,7 @@ export default function TeamInviteStep(): JSX.Element { // Redirect user to the getting started page const redirectToHome = async () => { - navigate({ to: `/organization/${ProjectType.SecretManager}/overview` as const }); + navigate({ to: "/organization/projects" as const }); }; const inviteUsers = async ({ emails: inviteEmails }: { emails: string }) => { diff --git a/frontend/src/components/navigation/NavHeader.tsx b/frontend/src/components/navigation/NavHeader.tsx index 4386818dd..ba25fd847 100644 --- a/frontend/src/components/navigation/NavHeader.tsx +++ b/frontend/src/components/navigation/NavHeader.tsx @@ -71,7 +71,7 @@ export default function NavHeader({ {currentOrg?.name?.charAt(0)} {currentOrg?.name} @@ -93,7 +93,7 @@ export default function NavHeader({ {pageName === "Secrets" ? ( @@ -129,7 +129,7 @@ export default function NavHeader({
@@ -191,7 +191,7 @@ export default function NavHeader({
) : ( ) : ( = ({ isOpen, onClose }) => }); navigate({ - to: `/organization/${ProjectType.SecretManager}/overview` as const, - params: { - organizationId: organization.id - } + to: "/organization/projects" }); localStorage.setItem("orgData.id", organization.id); diff --git a/frontend/src/components/project/ProjectOverviewChangeSection.tsx b/frontend/src/components/project/ProjectOverviewChangeSection.tsx index 0f88ec2e9..85a4ae46f 100644 --- a/frontend/src/components/project/ProjectOverviewChangeSection.tsx +++ b/frontend/src/components/project/ProjectOverviewChangeSection.tsx @@ -5,12 +5,14 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; -import { Button, FormControl, Input, TextArea } from "@app/components/v2"; +import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { useUpdateProject } from "@app/hooks/api"; +import { ProjectType } from "@app/hooks/api/workspace/types"; const baseFormSchema = z.object({ name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"), + defaultProduct: z.nativeEnum(ProjectType).default(ProjectType.SecretManager), description: z .string() .trim() @@ -50,6 +52,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) = reset({ name: currentWorkspace.name, description: currentWorkspace.description ?? "", + defaultProduct: currentWorkspace.defaultProduct, ...(showSlugField && { slug: currentWorkspace.slug }) }); } @@ -63,6 +66,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) = projectID: currentWorkspace.id, newProjectName: data.name, newProjectDescription: data.description, + defaultProduct: data.defaultProduct, ...(showSlugField && "slug" in data && { newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined @@ -212,6 +216,31 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) = + ( + + + + )} + />
; interface NewProjectModalProps { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; - projectType: ProjectType; } -type NewProjectFormProps = Pick; +type NewProjectFormProps = Pick; -const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => { +const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => { const navigate = useNavigate(); const { currentOrg } = useOrganization(); const { permission } = useOrgPermission(); @@ -72,7 +70,7 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => { OrgPermissionSubjects.ProjectTemplates ); - const { data: projectTemplates = [] } = useListProjectTemplates(projectType, { + const { data: projectTemplates = [] } = useListProjectTemplates({ enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates) }); @@ -115,15 +113,17 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => { projectName: name, projectDescription: description, kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined, - template, - type: projectType + template }); await refetchWorkspaces(); createNotification({ text: "Project created", type: "success" }); reset(); onOpenChange(false); - navigate({ to: getProjectHomePage(project), params: { projectId: project.id } }); + navigate({ + to: getProjectHomePage(project.defaultProduct), + params: { projectId: project.id } + }); } catch (err) { console.error(err); createNotification({ text: "Failed to create project", type: "error" }); @@ -270,18 +270,14 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => { ); }; -export const NewProjectModal: FC = ({ - isOpen, - onOpenChange, - projectType -}) => { +export const NewProjectModal: FC = ({ isOpen, onOpenChange }) => { return ( - + ); diff --git a/frontend/src/components/projects/ProjectSettings/ProjectSettings.tsx b/frontend/src/components/projects/ProjectSettings/ProjectSettings.tsx deleted file mode 100644 index 3919ad86c..000000000 --- a/frontend/src/components/projects/ProjectSettings/ProjectSettings.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useState } from "react"; - -import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; - -import { ProjectTemplatesTab } from "./components"; - -const tabs = [ - { name: "Project Templates", key: "project-templates", component: ProjectTemplatesTab } -]; - -export const ProjectSettings = () => { - const [selectedTab, setSelectedTab] = useState(tabs[0].key); - - return ( - - - {tabs.map((tab) => ( - - {tab.name} - - ))} - - {tabs.map(({ key, component: Component }) => ( - - - - ))} - - ); -}; diff --git a/frontend/src/components/projects/ProjectSettings/components/index.tsx b/frontend/src/components/projects/ProjectSettings/components/index.tsx deleted file mode 100644 index 35ad65ee0..000000000 --- a/frontend/src/components/projects/ProjectSettings/components/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from "./ProjectTemplatesTab"; diff --git a/frontend/src/components/projects/ProjectSettings/index.tsx b/frontend/src/components/projects/ProjectSettings/index.tsx deleted file mode 100644 index e2f0626f6..000000000 --- a/frontend/src/components/projects/ProjectSettings/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from "./ProjectSettings"; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/1PasswordSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/1PasswordSyncFields.tsx index 1202ec875..dd54dbf63 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/1PasswordSyncFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/1PasswordSyncFields.tsx @@ -4,7 +4,7 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField"; -import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2"; +import { FilterableSelect, FormControl, Input, Tooltip } from "@app/components/v2"; import { TOnePassVault, useOnePassConnectionListVaults @@ -32,6 +32,7 @@ export const OnePassSyncFields = () => { { setValue("destinationConfig.vaultId", ""); + setValue("destinationConfig.valueLabel", ""); }} /> @@ -69,6 +70,22 @@ export const OnePassSyncFields = () => { )} /> + + ( + + + + )} + control={control} + name="destinationConfig.valueLabel" + /> ); }; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/GcpSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/GcpSyncFields.tsx index 3361b8384..e7d988f46 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/GcpSyncFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/GcpSyncFields.tsx @@ -76,7 +76,7 @@ export const GcpSyncFields = () => { helperText={
Don't see the project you're looking for?{" "} diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/OnePassSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/OnePassSyncReviewFields.tsx index 1c31fb6c3..e311fc5ec 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/OnePassSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/OnePassSyncReviewFields.tsx @@ -6,7 +6,15 @@ import { SecretSync } from "@app/hooks/api/secretSyncs"; export const OnePassSyncReviewFields = () => { const { watch } = useFormContext(); - const vaultId = watch("destinationConfig.vaultId"); + const [vaultId, valueLabel] = watch([ + "destinationConfig.vaultId", + "destinationConfig.valueLabel" + ]); - return {vaultId}; + return ( + <> + {vaultId} + {valueLabel || "value"} + + ); }; diff --git a/frontend/src/components/secret-syncs/forms/schemas/1password-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/1password-sync-destination-schema.ts index 36b144776..a4cddd7b6 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/1password-sync-destination-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/1password-sync-destination-schema.ts @@ -7,7 +7,8 @@ export const OnePassSyncDestinationSchema = BaseSecretSyncSchema().merge( z.object({ destination: z.literal(SecretSync.OnePass), destinationConfig: z.object({ - vaultId: z.string().trim().min(1, "Vault ID required") + vaultId: z.string().trim().min(1, "Vault ID required"), + valueLabel: z.string().trim().optional() }) }) ); diff --git a/frontend/src/components/utilities/ShouldWrapComponent.tsx b/frontend/src/components/utilities/ShouldWrapComponent.tsx new file mode 100644 index 000000000..25f5d1659 --- /dev/null +++ b/frontend/src/components/utilities/ShouldWrapComponent.tsx @@ -0,0 +1,20 @@ +import { ComponentType, ReactNode } from "react"; + +type ShouldWrapProps> = { + children: ReactNode; + wrapper: ComponentType; + isWrapped?: boolean; +} & T; + +export const ShouldWrap = >({ + children, + wrapper: Wrapper, + isWrapped = false, + ...wrapperProps +}: ShouldWrapProps) => { + if (isWrapped) { + return {children}; + } + + return children; +}; diff --git a/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx b/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx index 0afd1bb1f..0b97a58c4 100644 --- a/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx +++ b/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/prop-types */ import React from "react"; -import { faCaretDown, faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons"; +import { faEllipsis, faSort } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, ReactNode } from "@tanstack/react-router"; import { LinkComponentProps } from "node_modules/@tanstack/react-router/dist/esm/link"; @@ -40,7 +40,7 @@ const BreadcrumbItem = React.forwardRef (
  • ) @@ -78,13 +78,8 @@ const BreadcrumbPage = React.forwardRef) => ( -
  • ); BreadcrumbSeparator.displayName = "BreadcrumbSeparator"; @@ -139,12 +134,12 @@ const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[] - - {el.label} + + {el.label} - + {el?.dropdownTitle && {el.dropdownTitle}} {el.links.map((i, dropIndex) => ( ( {...props} > {isLoading && ( - loading animation )} {leftIcon && ( diff --git a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx index 91937ff63..3254dd9ff 100644 --- a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx +++ b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx @@ -5,6 +5,8 @@ import { useEffect, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { twMerge } from "tailwind-merge"; +import { Lottie } from "../Lottie"; + type Props = { text?: string | string[]; frequency?: number; @@ -31,14 +33,7 @@ export const ContentLoader = ({ text, frequency = 2000, className }: Props) => { className )} > - loading animation + {text && isTextArray && ( { const [inputData, setInputData] = useState(""); @@ -70,7 +72,7 @@ export const DeleteActionModal = ({ - - ); - })} -
    - } - onClick={logOutUser} - > - Log Out - - - -
    -
    - {currentOrg.secretsProductEnabled && ( - - {({ isActive }) => ( - - Secrets - - )} - - )} - {currentOrg.pkiProductEnabled && ( - - {({ isActive }) => ( - - PKI - - )} - - )} - {currentOrg.kmsProductEnabled && ( - - {({ isActive }) => ( - - KMS - - )} - - )} - {currentOrg.sshProductEnabled && ( - - {({ isActive }) => ( - - SSH - - )} - - )} - {currentOrg.scannerProductEnabled && ( - - {({ isActive }) => ( - - Scanner - - )} - - )} - {(currentOrg.scannerProductEnabled || currentOrg.shareSecretsProductEnabled) && ( -
    - )} - {currentOrg.shareSecretsProductEnabled && ( - - {({ isActive }) => ( - - Share - - )} - - )} -
    - - setOpen(true)} - onMouseLeave={() => setOpen(false)} - asChild - > -
    - - Admin - -
    -
    - setOpen(true)} - onMouseLeave={() => setOpen(false)} - align="start" - side="right" - className="p-1" - > - Organization Options - - }> - Access Control - - - - }> - App Connections - - - - } - > - Gateways - - - - }> - Usage & Billing - - - - }> - Audit Logs - - - - } - > - SSO Settings - - - - }> - Organization Settings - - - Admin Panels - {user?.superAdmin && ( - - }> - Server Admin Console - - - )} - - }> - Organization Admin Console - - - -
    -
    -
    -
    - - setOpenSupport(true)} - onMouseLeave={() => setOpenSupport(false)} - className="w-full" - > - - - Support - - - setOpenSupport(true)} - onMouseLeave={() => setOpenSupport(false)} - align="end" - side="right" - className="p-1" - > - {INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => { - if (url === "server-admins" && isInfisicalCloud()) { - return null; - } - return ( - - {url === "server-admins" ? ( - - ) : ( - -
    - {icon} -
    {text}
    -
    -
    - )} -
    - ); - })} - {envConfig.PLATFORM_VERSION && ( -
    - - Version: {envConfig.PLATFORM_VERSION} -
    - )} -
    -
    - {subscription && subscription.slug === "starter" && !subscription.has_used_trial && ( - - - - )} - - setOpenUser(true)} - onMouseLeave={() => setOpenUser(false)} - className="w-full" - asChild - > -
    - User -
    -
    - setOpenUser(true)} - onMouseLeave={() => setOpenUser(false)} - side="right" - align="end" - className="p-1" - > -
    -
    -
    - -
    -
    -
    - {user?.firstName} {user?.lastName} -
    -
    {user.email}
    -
    -
    -
    - - Personal Settings - - - - Documentation - - - - - - Join Slack Community - - - -
    - - Copy Token - - - - -
    - }> - Log Out - - - -
    - - - - -
    - -
    -
    -
    - handlePopUpToggle("createOrg", false)} - /> - - ); -}; diff --git a/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/index.tsx b/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/index.tsx deleted file mode 100644 index f8df49034..000000000 --- a/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { MinimizedOrgSidebar } from "./MinimizedOrgSidebar"; diff --git a/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx b/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx new file mode 100644 index 000000000..4e9184789 --- /dev/null +++ b/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx @@ -0,0 +1,378 @@ +import { useState } from "react"; +import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; +import { faCircleQuestion, faUserCircle } from "@fortawesome/free-regular-svg-icons"; +import { + faArrowUpRightFromSquare, + faBook, + faCheck, + faCubes, + faEnvelope, + faInfo, + faInfoCircle, + faSignOut, + faSort, + faUser, + faUsers +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useQueryClient } from "@tanstack/react-query"; +import { Link, useNavigate, useRouter, useRouterState } from "@tanstack/react-router"; + +import { Mfa } from "@app/components/auth/Mfa"; +import { createNotification } from "@app/components/notifications"; +import SecurityClient from "@app/components/utilities/SecurityClient"; +import { + BreadcrumbContainer, + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + IconButton, + Modal, + ModalContent, + TBreadcrumbFormat, + Tooltip +} from "@app/components/v2"; +import { envConfig } from "@app/config/env"; +import { useOrganization, useSubscription, useUser } from "@app/context"; +import { isInfisicalCloud } from "@app/helpers/platform"; +import { useToggle } from "@app/hooks"; +import { useGetOrganizations, useLogoutUser, workspaceKeys } from "@app/hooks/api"; +import { authKeys, selectOrganization } from "@app/hooks/api/auth/queries"; +import { MfaMethod } from "@app/hooks/api/auth/types"; +import { getAuthToken } from "@app/hooks/api/reactQuery"; +import { SubscriptionPlan } from "@app/hooks/api/types"; +import { AuthMethod } from "@app/hooks/api/users/types"; +import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils"; + +import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel"; + +const getPlan = (subscription: SubscriptionPlan) => { + if (subscription.groups) return "Enterprise"; + if (subscription.pitRecovery) return "Pro"; + return "Free"; +}; + +export const INFISICAL_SUPPORT_OPTIONS = [ + [ + , + "Support Forum", + "https://infisical.com/slack" + ], + [ + , + "Read Docs", + "https://infisical.com/docs/documentation/getting-started/introduction" + ], + [ + , + "GitHub Issues", + "https://github.com/Infisical/infisical/issues" + ], + [ + , + "Email Support", + "mailto:support@infisical.com" + ], + [ + , + "Instance Admins", + "server-admins" + ] +]; + +export const Navbar = () => { + const { user } = useUser(); + const { subscription } = useSubscription(); + const { currentOrg } = useOrganization(); + const [showAdminsModal, setShowAdminsModal] = useState(false); + + const { data: orgs } = useGetOrganizations(); + const navigate = useNavigate(); + const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL); + const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {}); + const [shouldShowMfa, toggleShowMfa] = useToggle(false); + const router = useRouter(); + const queryClient = useQueryClient(); + + const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context }); + const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined; + + const handleOrgChange = async (orgId: string) => { + queryClient.removeQueries({ queryKey: authKeys.getAuthToken }); + queryClient.removeQueries({ queryKey: workspaceKeys.getAllUserWorkspace() }); + + const { token, isMfaEnabled, mfaMethod } = await selectOrganization({ + organizationId: orgId + }); + + if (isMfaEnabled) { + SecurityClient.setMfaToken(token); + if (mfaMethod) { + setRequiredMfaMethod(mfaMethod); + } + toggleShowMfa.on(); + setMfaSuccessCallback(() => () => handleOrgChange(orgId)); + return; + } + await router.invalidate(); + await navigateUserToOrg(navigate, orgId); + }; + + const logout = useLogoutUser(); + const logOutUser = async () => { + try { + console.log("Logging out..."); + await logout.mutateAsync(); + navigate({ to: "/login" }); + } catch (error) { + console.error(error); + } + }; + + const handleCopyToken = async () => { + try { + await window.navigator.clipboard.writeText(getAuthToken()); + createNotification({ + type: "success", + text: "Copied current login session token to clipboard" + }); + } catch (error) { + console.log(error); + createNotification({ type: "error", text: "Failed to copy user token to clipboard" }); + } + }; + + if (shouldShowMfa) { + return ( +
    + toggleShowMfa.off()} + /> +
    + ); + } + + return ( +
    +
    + + infisical logo + +
    +
    +

    /

    + + +
    +
    + +
    +
    {currentOrg?.name}
    +
    + {getPlan(subscription)} +
    +
    + + +
    + + + +
    +
    + +
    organizations
    + {orgs?.map((org) => { + return ( + + + + ); + })} +
    + } onClick={logOutUser}> + Log Out + + + +

    /

    +
    +
    + {breadcrumbs ? ( + + ) : null} +
    +
    + + +
    + +
    +
    + + {INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => { + if (url === "server-admins" && isInfisicalCloud()) { + return null; + } + return ( + + {url === "server-admins" ? ( + + ) : ( + +
    + {icon} +
    {text}
    +
    +
    + )} +
    + ); + })} + {envConfig.PLATFORM_VERSION && ( +
    + + Version: {envConfig.PLATFORM_VERSION} +
    + )} +
    +
    + + +
    + +
    +
    + +
    +
    +
    + +
    +
    +
    + {user?.firstName} {user?.lastName} +
    +
    {user.email}
    +
    +
    +
    + + Personal Settings + + + + Documentation + + + + + + Join Slack Community + + + +
    + + Copy Token + + + + +
    + }> + Log Out + + + + + +
    + +
    +
    +
    +
    + ); +}; diff --git a/frontend/src/layouts/OrganizationLayout/components/NavBar/index.tsx b/frontend/src/layouts/OrganizationLayout/components/NavBar/index.tsx new file mode 100644 index 000000000..d97ce393e --- /dev/null +++ b/frontend/src/layouts/OrganizationLayout/components/NavBar/index.tsx @@ -0,0 +1 @@ +export { Navbar } from "./Navbar"; diff --git a/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/OrgSidebar.tsx b/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/OrgSidebar.tsx new file mode 100644 index 000000000..89da0e2de --- /dev/null +++ b/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/OrgSidebar.tsx @@ -0,0 +1,237 @@ +import { + faBook, + faCheckCircle, + faCog, + faCubes, + faDoorClosed, + faInfinity, + faMoneyBill, + faPlug, + faShare, + faUserCog, + faUsers, + faUserTie +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link } from "@tanstack/react-router"; +import { AnimatePresence, motion } from "framer-motion"; + +import { CreateOrgModal } from "@app/components/organization/CreateOrgModal"; +import { Menu, MenuGroup, MenuItem, Tooltip } from "@app/components/v2"; +import { useOrganization, useSubscription, useUser } from "@app/context"; +import { usePopUp } from "@app/hooks"; +import { useGetOrgTrialUrl } from "@app/hooks/api"; + +type Props = { + isHidden?: boolean; +}; + +export const OrgSidebar = ({ isHidden }: Props) => { + const { subscription } = useSubscription(); + + const { user } = useUser(); + const { mutateAsync } = useGetOrgTrialUrl(); + + const { currentOrg } = useOrganization(); + + const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const); + + return ( + <> + + {!isHidden && ( + +
    + } + > + Organization Admin + + + {user.superAdmin && ( + + + +
    + } + > + Server Console + + + )} + + + + )} + + handlePopUpToggle("createOrg", false)} + /> + + ); +}; diff --git a/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/index.tsx b/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/index.tsx new file mode 100644 index 000000000..315d7ffab --- /dev/null +++ b/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/index.tsx @@ -0,0 +1 @@ +export { OrgSidebar } from "./OrgSidebar"; diff --git a/frontend/src/layouts/PersonalSettingsLayout/PersonalSettingsLayout.tsx b/frontend/src/layouts/PersonalSettingsLayout/PersonalSettingsLayout.tsx index 5ae5edd37..c2835996a 100644 --- a/frontend/src/layouts/PersonalSettingsLayout/PersonalSettingsLayout.tsx +++ b/frontend/src/layouts/PersonalSettingsLayout/PersonalSettingsLayout.tsx @@ -11,10 +11,9 @@ import { DropdownMenuTrigger } from "@app/components/v2"; import { envConfig } from "@app/config/env"; -import { ProjectType } from "@app/hooks/api/workspace/types"; import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; -import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar"; +import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/NavBar/Navbar"; export const PersonalSettingsLayout = () => { const { t } = useTranslation(); @@ -27,7 +26,7 @@ export const PersonalSettingsLayout = () => {