Compare commits

..

34 Commits

Author SHA1 Message Date
221de8beb4 improvements: revise commit history and commit details UI 2025-07-11 20:07:53 -07:00
6df6f44b50 Merge pull request #4008 from Infisical/ENG-3156
Use non root user for docs Dockerfile
2025-07-11 18:12:57 -04:00
2f6c79beb6 Use non root user for docs Dockerfile 2025-07-11 14:47:22 -04:00
Sid
b67fcad252 feat: migrate github app connection to env override (#4004)
* feat: migrate github app connection to env override

* fix: remove usage of github app integration

* chore: lint fix

* fix: migration cleanup

* fix: refactor integrations tab

* fix: content

* fix: remove integrations tab

---------

Co-authored-by: sidwebworks <xodeveloper@gmail.com>
2025-07-11 23:56:55 +05:30
5a41862dc9 Merge pull request #4002 from Infisical/create-policy-secret-path-input
improvement(frontend): use secret path input for create policy modal
2025-07-11 11:14:36 -07:00
9fd0189dbb Merge pull request #4007 from Infisical/move-sso-settings-to-org-settings
improvement(frontend): Move sso/provision settings back to org settings tabs
2025-07-11 11:07:34 -07:00
af26323f3b improvement: address feedback 2025-07-11 11:06:42 -07:00
74fae78c31 Merge pull request #3988 from Infisical/ENG-2932
feat(secret-sync): Cloudflare Workers
2025-07-11 14:04:54 -04:00
1aa9be203e improvement: move sso/provision settings back to org settings tabs 2025-07-11 10:58:35 -07:00
f9ef5cf930 Remove concurrency to avoid rate limit 2025-07-11 13:47:43 -04:00
16c89c6dbd Reviews 2025-07-11 13:38:17 -04:00
e35ac599f8 Merge pull request #3997 from Infisical/fix-approval-requests-blocking-deletion
fix(approval-workflows): allow null committer on secret approval request and cascade delete on access request
2025-07-11 10:05:19 -07:00
782b6fce4a Merge branch 'main' into ENG-2932 2025-07-11 12:54:27 -04:00
6d91297ca9 Merge pull request #4005 from Infisical/fix/billingPageIdentityLimit
fix(billing): fix feature flags to only use identityLimit
2025-07-11 12:14:58 -03:00
db369b8f51 fix(billing): fix feature flags to only use identityLimit and minor fix invalidate plan query result 2025-07-11 11:36:25 -03:00
a50a95ad6e Merge pull request #3923 from Infisical/daniel/approval-policy-improvements
fix(approval-policies): improve policies handling
2025-07-11 11:44:09 +04:00
4ec0031c42 Merge pull request #4003 from Infisical/offline-docs-dockerfile-update
Allow docs to run fully offline
2025-07-10 21:22:40 -04:00
a6edb67f58 Allow docs to run fully offline 2025-07-10 20:34:56 -04:00
1567239fc2 improvement: use secret path input for create policy modal 2025-07-10 16:05:37 -07:00
aae5831f35 Merge pull request #4001 from Infisical/server-admin-sidebar-improvements
improvement(frontend): Server admin sidebar improvements
2025-07-10 15:44:25 -07:00
6f78a6b4c1 Merge pull request #4000 from Infisical/fix-remove-jim-as-sole-author-of-secret-leaks
fix(secret-scanning-v2): Remove Jim as sole author of all secret leaks
2025-07-10 15:41:24 -07:00
c2e326b95a fix: remove jim as sole author of all secret leaks 2025-07-10 15:02:38 -07:00
97c96acea5 Update secret-approval-policy-service.ts 2025-07-11 00:59:28 +04:00
5e24015f2a requested changes 2025-07-11 00:54:28 +04:00
f17e1f6699 fix: update approval request user delettion behavior 2025-07-10 10:37:37 -07:00
e71b136859 requested changes 2025-07-10 16:14:40 +04:00
79d80fad08 Fix greptile reviews 2025-07-09 22:27:42 -04:00
f58de53995 CF Workers Sync Docs 2025-07-09 22:05:36 -04:00
f85c045b09 Fix endpoints 2025-07-09 20:16:55 -04:00
6477a9f095 Merge branch 'main' into ENG-2932 2025-07-09 20:02:15 -04:00
e3a7478acb Merge branch 'main' into ENG-2932 2025-07-09 18:13:48 -04:00
4f348316e7 feat(secret-sync): Cloudflare Workers 2025-07-09 17:03:18 -04:00
7d2d69fc7d requested changes 2025-07-05 01:56:35 +04:00
0569c7e692 fix(approval-policies): improve policies handling 2025-07-04 03:14:43 +04:00
128 changed files with 1374 additions and 787 deletions

View File

@ -0,0 +1,55 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy)
.whereNull("secretPath")
.orWhere("secretPath", "");
const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy)
.whereNull("secretPath")
.orWhere("secretPath", "");
// update all the secret approval policies secretPath to be "/**"
if (existingSecretApprovalPolicies.length) {
await knex(TableName.SecretApprovalPolicy)
.whereIn(
"id",
existingSecretApprovalPolicies.map((el) => el.id)
)
.update({
secretPath: "/**"
});
}
// update all the access approval policies secretPath to be "/**"
if (existingAccessApprovalPolicies.length) {
await knex(TableName.AccessApprovalPolicy)
.whereIn(
"id",
existingAccessApprovalPolicies.map((el) => el.id)
)
.update({
secretPath: "/**"
});
}
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (table) => {
table.string("secretPath").notNullable().alter();
});
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (table) => {
table.string("secretPath").notNullable().alter();
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (table) => {
table.string("secretPath").nullable().alter();
});
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (table) => {
table.string("secretPath").nullable().alter();
});
}

View File

@ -0,0 +1,35 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
const hasCommitterCol = await knex.schema.hasColumn(TableName.SecretApprovalRequest, "committerUserId");
if (hasCommitterCol) {
await knex.schema.alterTable(TableName.SecretApprovalRequest, (tb) => {
tb.uuid("committerUserId").nullable().alter();
});
}
const hasRequesterCol = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "requestedByUserId");
if (hasRequesterCol) {
await knex.schema.alterTable(TableName.AccessApprovalRequest, (tb) => {
tb.dropForeign("requestedByUserId");
tb.foreign("requestedByUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
// can't undo committer nullable
const hasRequesterCol = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "requestedByUserId");
if (hasRequesterCol) {
await knex.schema.alterTable(TableName.AccessApprovalRequest, (tb) => {
tb.dropForeign("requestedByUserId");
tb.foreign("requestedByUserId").references("id").inTable(TableName.Users).onDelete("SET NULL");
});
}
}

View File

@ -0,0 +1,66 @@
import { Knex } from "knex";
import { inMemoryKeyStore } from "@app/keystore/memory";
import { selectAllTableCols } from "@app/lib/knex";
import { TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
import { getMigrationEncryptionServices } from "./utils/services";
export async function up(knex: Knex) {
const existingSuperAdminsWithGithubConnection = await knex(TableName.SuperAdmin)
.select(selectAllTableCols(TableName.SuperAdmin))
.whereNotNull(`${TableName.SuperAdmin}.encryptedGitHubAppConnectionClientId`);
const envConfig = getMigrationEnvConfig();
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const decryptor = kmsService.decryptWithRootKey();
const encryptor = kmsService.encryptWithRootKey();
const tasks = existingSuperAdminsWithGithubConnection.map(async (admin) => {
const overrides = (
admin.encryptedEnvOverrides ? JSON.parse(decryptor(Buffer.from(admin.encryptedEnvOverrides)).toString()) : {}
) as Record<string, string>;
if (admin.encryptedGitHubAppConnectionClientId) {
overrides.INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID = decryptor(
admin.encryptedGitHubAppConnectionClientId
).toString();
}
if (admin.encryptedGitHubAppConnectionClientSecret) {
overrides.INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET = decryptor(
admin.encryptedGitHubAppConnectionClientSecret
).toString();
}
if (admin.encryptedGitHubAppConnectionPrivateKey) {
overrides.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY = decryptor(
admin.encryptedGitHubAppConnectionPrivateKey
).toString();
}
if (admin.encryptedGitHubAppConnectionSlug) {
overrides.INF_APP_CONNECTION_GITHUB_APP_SLUG = decryptor(admin.encryptedGitHubAppConnectionSlug).toString();
}
if (admin.encryptedGitHubAppConnectionId) {
overrides.INF_APP_CONNECTION_GITHUB_APP_ID = decryptor(admin.encryptedGitHubAppConnectionId).toString();
}
const encryptedEnvOverrides = encryptor(Buffer.from(JSON.stringify(overrides)));
await knex(TableName.SuperAdmin).where({ id: admin.id }).update({
encryptedEnvOverrides
});
});
await Promise.all(tasks);
}
export async function down() {
// No down migration needed as this migration is only for data transformation
// and does not change the schema.
}

View File

@ -14,8 +14,8 @@ export const AccessApprovalPoliciesApproversSchema = z.object({
updatedAt: z.date(), updatedAt: z.date(),
approverUserId: z.string().uuid().nullable().optional(), approverUserId: z.string().uuid().nullable().optional(),
approverGroupId: z.string().uuid().nullable().optional(), approverGroupId: z.string().uuid().nullable().optional(),
sequence: z.number().default(0).nullable().optional(), sequence: z.number().default(1).nullable().optional(),
approvalsRequired: z.number().default(1).nullable().optional() approvalsRequired: z.number().nullable().optional()
}); });
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>; export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;

View File

@ -11,7 +11,7 @@ export const AccessApprovalPoliciesSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
name: z.string(), name: z.string(),
approvals: z.number().default(1), approvals: z.number().default(1),
secretPath: z.string().nullable().optional(), secretPath: z.string(),
envId: z.string().uuid(), envId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),

View File

@ -12,8 +12,8 @@ export const CertificateAuthoritiesSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
projectId: z.string(), projectId: z.string(),
enableDirectIssuance: z.boolean().default(true),
status: z.string(), status: z.string(),
enableDirectIssuance: z.boolean().default(true),
name: z.string() name: z.string()
}); });

View File

@ -25,8 +25,8 @@ export const CertificatesSchema = z.object({
certificateTemplateId: z.string().uuid().nullable().optional(), certificateTemplateId: z.string().uuid().nullable().optional(),
keyUsages: z.string().array().nullable().optional(), keyUsages: z.string().array().nullable().optional(),
extendedKeyUsages: z.string().array().nullable().optional(), extendedKeyUsages: z.string().array().nullable().optional(),
pkiSubscriberId: z.string().uuid().nullable().optional(), projectId: z.string(),
projectId: z.string() pkiSubscriberId: z.string().uuid().nullable().optional()
}); });
export type TCertificates = z.infer<typeof CertificatesSchema>; export type TCertificates = z.infer<typeof CertificatesSchema>;

View File

@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
export const SecretApprovalPoliciesSchema = z.object({ export const SecretApprovalPoliciesSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
name: z.string(), name: z.string(),
secretPath: z.string().nullable().optional(), secretPath: z.string(),
approvals: z.number().default(1), approvals: z.number().default(1),
envId: z.string().uuid(), envId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),

View File

@ -18,7 +18,7 @@ export const SecretApprovalRequestsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
isReplicated: z.boolean().nullable().optional(), isReplicated: z.boolean().nullable().optional(),
committerUserId: z.string().uuid(), committerUserId: z.string().uuid().nullable().optional(),
statusChangedByUserId: z.string().uuid().nullable().optional(), statusChangedByUserId: z.string().uuid().nullable().optional(),
bypassReason: z.string().nullable().optional() bypassReason: z.string().nullable().optional()
}); });

View File

@ -2,6 +2,7 @@ import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types"; import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
import { removeTrailingSlash } from "@app/lib/fn";
import { EnforcementLevel } from "@app/lib/types"; import { EnforcementLevel } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -19,7 +20,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
body: z.object({ body: z.object({
projectSlug: z.string().trim(), projectSlug: z.string().trim(),
name: z.string().optional(), name: z.string().optional(),
secretPath: z.string().trim().default("/"), secretPath: z.string().trim().min(1, { message: "Secret path cannot be empty" }).transform(removeTrailingSlash),
environment: z.string(), environment: z.string(),
approvers: z approvers: z
.discriminatedUnion("type", [ .discriminatedUnion("type", [
@ -174,8 +175,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
secretPath: z secretPath: z
.string() .string()
.trim() .trim()
.min(1, { message: "Secret path cannot be empty" })
.optional() .optional()
.transform((val) => (val === "" ? "/" : val)), .transform((val) => (val ? removeTrailingSlash(val) : val)),
approvers: z approvers: z
.discriminatedUnion("type", [ .discriminatedUnion("type", [
z.object({ z.object({

View File

@ -23,10 +23,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
environment: z.string(), environment: z.string(),
secretPath: z secretPath: z
.string() .string()
.optional() .min(1, { message: "Secret path cannot be empty" })
.nullable() .transform((val) => removeTrailingSlash(val)),
.default("/")
.transform((val) => (val ? removeTrailingSlash(val) : val)),
approvers: z approvers: z
.discriminatedUnion("type", [ .discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }), z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
@ -100,10 +98,10 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
approvals: z.number().min(1).default(1), approvals: z.number().min(1).default(1),
secretPath: z secretPath: z
.string() .string()
.trim()
.min(1, { message: "Secret path cannot be empty" })
.optional() .optional()
.nullable() .transform((val) => (val ? removeTrailingSlash(val) : undefined)),
.transform((val) => (val ? removeTrailingSlash(val) : val))
.transform((val) => (val === "" ? "/" : val)),
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(), enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
allowedSelfApprovals: z.boolean().default(true) allowedSelfApprovals: z.boolean().default(true)
}), }),

View File

@ -58,7 +58,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
deletedAt: z.date().nullish(), deletedAt: z.date().nullish(),
allowedSelfApprovals: z.boolean() allowedSelfApprovals: z.boolean()
}), }),
committerUser: approvalRequestUser, committerUser: approvalRequestUser.nullish(),
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(), commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
environment: z.string(), environment: z.string(),
reviewers: z.object({ userId: z.string(), status: z.string() }).array(), reviewers: z.object({ userId: z.string(), status: z.string() }).array(),
@ -308,7 +308,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
}), }),
environment: z.string(), environment: z.string(),
statusChangedByUser: approvalRequestUser.optional(), statusChangedByUser: approvalRequestUser.optional(),
committerUser: approvalRequestUser, committerUser: approvalRequestUser.nullish(),
reviewers: approvalRequestUser.extend({ status: z.string(), comment: z.string().optional() }).array(), reviewers: approvalRequestUser.extend({ status: z.string(), comment: z.string().optional() }).array(),
secretPath: z.string(), secretPath: z.string(),
commits: secretRawSchema commits: secretRawSchema

View File

@ -53,7 +53,7 @@ export interface TAccessApprovalPolicyDALFactory
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environment: {
id: string; id: string;
@ -93,7 +93,7 @@ export interface TAccessApprovalPolicyDALFactory
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environment: {
id: string; id: string;
@ -116,7 +116,7 @@ export interface TAccessApprovalPolicyDALFactory
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
}>; }>;
findLastValidPolicy: ( findLastValidPolicy: (
@ -138,7 +138,7 @@ export interface TAccessApprovalPolicyDALFactory
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
} }
| undefined | undefined
@ -190,7 +190,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
}>; }>;
deleteAccessApprovalPolicy: ({ deleteAccessApprovalPolicy: ({
@ -214,7 +214,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environment: {
id: string; id: string;
@ -252,7 +252,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
}>; }>;
getAccessApprovalPolicyByProjectSlug: ({ getAccessApprovalPolicyByProjectSlug: ({
@ -286,7 +286,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environment: {
id: string; id: string;
@ -337,7 +337,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environment: {
id: string; id: string;

View File

@ -60,6 +60,26 @@ export const accessApprovalPolicyServiceFactory = ({
accessApprovalRequestReviewerDAL, accessApprovalRequestReviewerDAL,
orgMembershipDAL orgMembershipDAL
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => { }: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
const $policyExists = async ({
envId,
secretPath,
policyId
}: {
envId: string;
secretPath: string;
policyId?: string;
}) => {
const policy = await accessApprovalPolicyDAL
.findOne({
envId,
secretPath,
deletedAt: null
})
.catch(() => null);
return policyId ? policy && policy.id !== policyId : Boolean(policy);
};
const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({ const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({
name, name,
actor, actor,
@ -106,6 +126,12 @@ export const accessApprovalPolicyServiceFactory = ({
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id }); const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` }); if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
if (await $policyExists({ envId: env.id, secretPath })) {
throw new BadRequestError({
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
});
}
let approverUserIds = userApprovers; let approverUserIds = userApprovers;
if (userApproverNames.length) { if (userApproverNames.length) {
const approverUsersInDB = await userDAL.find({ const approverUsersInDB = await userDAL.find({
@ -279,7 +305,11 @@ export const accessApprovalPolicyServiceFactory = ({
) as { username: string; sequence?: number }[]; ) as { username: string; sequence?: number }[];
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId); const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Approval policy not found" }); if (!accessApprovalPolicy) {
throw new NotFoundError({
message: `Access approval policy with ID '${policyId}' not found`
});
}
const currentApprovals = approvals || accessApprovalPolicy.approvals; const currentApprovals = approvals || accessApprovalPolicy.approvals;
if ( if (
@ -290,9 +320,18 @@ export const accessApprovalPolicyServiceFactory = ({
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" }); throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
} }
if (!accessApprovalPolicy) { if (
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` }); await $policyExists({
envId: accessApprovalPolicy.envId,
secretPath: secretPath || accessApprovalPolicy.secretPath,
policyId: accessApprovalPolicy.id
})
) {
throw new BadRequestError({
message: `A policy for secret path '${secretPath}' already exists in environment '${accessApprovalPolicy.environment.slug}'`
});
} }
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,

View File

@ -122,7 +122,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
}>; }>;
deleteAccessApprovalPolicy: ({ deleteAccessApprovalPolicy: ({
@ -146,7 +146,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environment: {
id: string; id: string;
@ -218,7 +218,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environment: {
id: string; id: string;
@ -269,7 +269,7 @@ export interface TAccessApprovalPolicyServiceFactory {
envId: string; envId: string;
enforcementLevel: string; enforcementLevel: string;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath?: string | null | undefined; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environment: {
id: string; id: string;

View File

@ -1711,7 +1711,7 @@ interface SecretApprovalReopened {
interface SecretApprovalRequest { interface SecretApprovalRequest {
type: EventType.SECRET_APPROVAL_REQUEST; type: EventType.SECRET_APPROVAL_REQUEST;
metadata: { metadata: {
committedBy: string; committedBy?: string | null;
secretApprovalRequestSlug: string; secretApprovalRequestSlug: string;
secretApprovalRequestId: string; secretApprovalRequestId: string;
eventType: SecretApprovalEvent; eventType: SecretApprovalEvent;

View File

@ -361,13 +361,6 @@ export const ldapConfigServiceFactory = ({
}); });
} else { } else {
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
throw new BadRequestError({
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
});
}
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) { if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed // limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
throw new BadRequestError({ throw new BadRequestError({

View File

@ -1,5 +1,4 @@
export const BillingPlanRows = { export const BillingPlanRows = {
MemberLimit: { name: "Organization member limit", field: "memberLimit" },
IdentityLimit: { name: "Organization identity limit", field: "identityLimit" }, IdentityLimit: { name: "Organization identity limit", field: "identityLimit" },
WorkspaceLimit: { name: "Project limit", field: "workspaceLimit" }, WorkspaceLimit: { name: "Project limit", field: "workspaceLimit" },
EnvironmentLimit: { name: "Environment limit", field: "environmentLimit" }, EnvironmentLimit: { name: "Environment limit", field: "environmentLimit" },

View File

@ -442,9 +442,7 @@ export const licenseServiceFactory = ({
rows: data.rows.map((el) => { rows: data.rows.map((el) => {
let used = "-"; let used = "-";
if (el.name === BillingPlanRows.MemberLimit.name) { if (el.name === BillingPlanRows.WorkspaceLimit.name) {
used = orgMembersUsed.toString();
} else if (el.name === BillingPlanRows.WorkspaceLimit.name) {
used = projectCount.toString(); used = projectCount.toString();
} else if (el.name === BillingPlanRows.IdentityLimit.name) { } else if (el.name === BillingPlanRows.IdentityLimit.name) {
used = (identityUsed + orgMembersUsed).toString(); used = (identityUsed + orgMembersUsed).toString();
@ -464,12 +462,10 @@ export const licenseServiceFactory = ({
const allowed = onPremFeatures[field as keyof TFeatureSet]; const allowed = onPremFeatures[field as keyof TFeatureSet];
let used = "-"; let used = "-";
if (field === BillingPlanRows.MemberLimit.field) { if (field === BillingPlanRows.WorkspaceLimit.field) {
used = orgMembersUsed.toString();
} else if (field === BillingPlanRows.WorkspaceLimit.field) {
used = projectCount.toString(); used = projectCount.toString();
} else if (field === BillingPlanRows.IdentityLimit.field) { } else if (field === BillingPlanRows.IdentityLimit.field) {
used = identityUsed.toString(); used = (identityUsed + orgMembersUsed).toString();
} }
return { return {

View File

@ -311,13 +311,6 @@ export const samlConfigServiceFactory = ({
}); });
} else { } else {
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
throw new BadRequestError({
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
});
}
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) { if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed // limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
throw new BadRequestError({ throw new BadRequestError({

View File

@ -55,6 +55,26 @@ export const secretApprovalPolicyServiceFactory = ({
licenseService, licenseService,
secretApprovalRequestDAL secretApprovalRequestDAL
}: TSecretApprovalPolicyServiceFactoryDep) => { }: TSecretApprovalPolicyServiceFactoryDep) => {
const $policyExists = async ({
envId,
secretPath,
policyId
}: {
envId: string;
secretPath: string;
policyId?: string;
}) => {
const policy = await secretApprovalPolicyDAL
.findOne({
envId,
secretPath,
deletedAt: null
})
.catch(() => null);
return policyId ? policy && policy.id !== policyId : Boolean(policy);
};
const createSecretApprovalPolicy = async ({ const createSecretApprovalPolicy = async ({
name, name,
actor, actor,
@ -106,10 +126,17 @@ export const secretApprovalPolicyServiceFactory = ({
} }
const env = await projectEnvDAL.findOne({ slug: environment, projectId }); const env = await projectEnvDAL.findOne({ slug: environment, projectId });
if (!env) if (!env) {
throw new NotFoundError({ throw new NotFoundError({
message: `Environment with slug '${environment}' not found in project with ID ${projectId}` message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
}); });
}
if (await $policyExists({ envId: env.id, secretPath })) {
throw new BadRequestError({
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
});
}
let groupBypassers: string[] = []; let groupBypassers: string[] = [];
let bypasserUserIds: string[] = []; let bypasserUserIds: string[] = [];
@ -260,6 +287,18 @@ export const secretApprovalPolicyServiceFactory = ({
}); });
} }
if (
await $policyExists({
envId: secretApprovalPolicy.envId,
secretPath: secretPath || secretApprovalPolicy.secretPath,
policyId: secretApprovalPolicy.id
})
) {
throw new BadRequestError({
message: `A policy for secret path '${secretPath}' already exists in environment '${secretApprovalPolicy.environment.slug}'`
});
}
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,

View File

@ -4,7 +4,7 @@ import { ApproverType, BypasserType } from "../access-approval-policy/access-app
export type TCreateSapDTO = { export type TCreateSapDTO = {
approvals: number; approvals: number;
secretPath?: string | null; secretPath: string;
environment: string; environment: string;
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[]; approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
bypassers?: ( bypassers?: (
@ -20,7 +20,7 @@ export type TCreateSapDTO = {
export type TUpdateSapDTO = { export type TUpdateSapDTO = {
secretPolicyId: string; secretPolicyId: string;
approvals?: number; approvals?: number;
secretPath?: string | null; secretPath?: string;
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[]; approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
bypassers?: ( bypassers?: (
| { type: BypasserType.Group; id: string } | { type: BypasserType.Group; id: string }

View File

@ -45,7 +45,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalRequest}.statusChangedByUserId`, `${TableName.SecretApprovalRequest}.statusChangedByUserId`,
`statusChangedByUser.id` `statusChangedByUser.id`
) )
.join<TUsers>( .leftJoin<TUsers>(
db(TableName.Users).as("committerUser"), db(TableName.Users).as("committerUser"),
`${TableName.SecretApprovalRequest}.committerUserId`, `${TableName.SecretApprovalRequest}.committerUserId`,
`committerUser.id` `committerUser.id`
@ -173,13 +173,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
username: el.statusChangedByUserUsername username: el.statusChangedByUserUsername
} }
: undefined, : undefined,
committerUser: { committerUser: el.committerUserId
userId: el.committerUserId, ? {
email: el.committerUserEmail, userId: el.committerUserId,
firstName: el.committerUserFirstName, email: el.committerUserEmail,
lastName: el.committerUserLastName, firstName: el.committerUserFirstName,
username: el.committerUserUsername lastName: el.committerUserLastName,
}, username: el.committerUserUsername
}
: null,
policy: { policy: {
id: el.policyId, id: el.policyId,
name: el.policyName, name: el.policyName,
@ -377,7 +379,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`, `${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId` `bypasserUserGroupMembership.groupId`
) )
.join<TUsers>( .leftJoin<TUsers>(
db(TableName.Users).as("committerUser"), db(TableName.Users).as("committerUser"),
`${TableName.SecretApprovalRequest}.committerUserId`, `${TableName.SecretApprovalRequest}.committerUserId`,
`committerUser.id` `committerUser.id`
@ -488,13 +490,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
enforcementLevel: el.policyEnforcementLevel, enforcementLevel: el.policyEnforcementLevel,
allowedSelfApprovals: el.policyAllowedSelfApprovals allowedSelfApprovals: el.policyAllowedSelfApprovals
}, },
committerUser: { committerUser: el.committerUserId
userId: el.committerUserId, ? {
email: el.committerUserEmail, userId: el.committerUserId,
firstName: el.committerUserFirstName, email: el.committerUserEmail,
lastName: el.committerUserLastName, firstName: el.committerUserFirstName,
username: el.committerUserUsername lastName: el.committerUserLastName,
} username: el.committerUserUsername
}
: null
}), }),
childrenMapper: [ childrenMapper: [
{ {
@ -581,7 +585,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`, `${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId` `bypasserUserGroupMembership.groupId`
) )
.join<TUsers>( .leftJoin<TUsers>(
db(TableName.Users).as("committerUser"), db(TableName.Users).as("committerUser"),
`${TableName.SecretApprovalRequest}.committerUserId`, `${TableName.SecretApprovalRequest}.committerUserId`,
`committerUser.id` `committerUser.id`
@ -693,13 +697,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
enforcementLevel: el.policyEnforcementLevel, enforcementLevel: el.policyEnforcementLevel,
allowedSelfApprovals: el.policyAllowedSelfApprovals allowedSelfApprovals: el.policyAllowedSelfApprovals
}, },
committerUser: { committerUser: el.committerUserId
userId: el.committerUserId, ? {
email: el.committerUserEmail, userId: el.committerUserId,
firstName: el.committerUserFirstName, email: el.committerUserEmail,
lastName: el.committerUserLastName, firstName: el.committerUserFirstName,
username: el.committerUserUsername lastName: el.committerUserLastName,
} username: el.committerUserUsername
}
: null
}), }),
childrenMapper: [ childrenMapper: [
{ {

View File

@ -1320,7 +1320,7 @@ export const secretApprovalRequestServiceFactory = ({
}); });
const env = await projectEnvDAL.findOne({ id: policy.envId }); const env = await projectEnvDAL.findOne({ id: policy.envId });
const user = await userDAL.findById(secretApprovalRequest.committerUserId); const user = await userDAL.findById(actorId);
await triggerWorkflowIntegrationNotification({ await triggerWorkflowIntegrationNotification({
input: { input: {
@ -1657,7 +1657,7 @@ export const secretApprovalRequestServiceFactory = ({
return { ...doc, commits: approvalCommits }; return { ...doc, commits: approvalCommits };
}); });
const user = await userDAL.findById(secretApprovalRequest.committerUserId); const user = await userDAL.findById(actorId);
const env = await projectEnvDAL.findOne({ id: policy.envId }); const env = await projectEnvDAL.findOne({ id: policy.envId });
await triggerWorkflowIntegrationNotification({ await triggerWorkflowIntegrationNotification({

View File

@ -37,7 +37,8 @@ import {
TQueueSecretScanningDataSourceFullScan, TQueueSecretScanningDataSourceFullScan,
TQueueSecretScanningResourceDiffScan, TQueueSecretScanningResourceDiffScan,
TQueueSecretScanningSendNotification, TQueueSecretScanningSendNotification,
TSecretScanningDataSourceWithConnection TSecretScanningDataSourceWithConnection,
TSecretScanningFinding
} from "./secret-scanning-v2-types"; } from "./secret-scanning-v2-types";
type TSecretRotationV2QueueServiceFactoryDep = { type TSecretRotationV2QueueServiceFactoryDep = {
@ -459,13 +460,16 @@ export const secretScanningV2QueueServiceFactory = async ({
const newFindings = allFindings.filter((finding) => finding.scanId === scanId); const newFindings = allFindings.filter((finding) => finding.scanId === scanId);
if (newFindings.length) { if (newFindings.length) {
const finding = newFindings[0] as TSecretScanningFinding;
await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, {
status: SecretScanningScanStatus.Completed, status: SecretScanningScanStatus.Completed,
resourceName: resource.name, resourceName: resource.name,
isDiffScan: true, isDiffScan: true,
dataSource, dataSource,
numberOfSecrets: newFindings.length, numberOfSecrets: newFindings.length,
scanId scanId,
authorName: finding?.details?.author,
authorEmail: finding?.details?.email
}); });
} }
@ -582,8 +586,8 @@ export const secretScanningV2QueueServiceFactory = async ({
substitutions: substitutions:
payload.status === SecretScanningScanStatus.Completed payload.status === SecretScanningScanStatus.Completed
? { ? {
authorName: "Jim", authorName: payload.authorName,
authorEmail: "jim@infisical.com", authorEmail: payload.authorEmail,
resourceName, resourceName,
numberOfSecrets: payload.numberOfSecrets, numberOfSecrets: payload.numberOfSecrets,
isDiffScan: payload.isDiffScan, isDiffScan: payload.isDiffScan,

View File

@ -119,7 +119,14 @@ export type TQueueSecretScanningSendNotification = {
resourceName: string; resourceName: string;
} & ( } & (
| { status: SecretScanningScanStatus.Failed; errorMessage: string } | { status: SecretScanningScanStatus.Failed; errorMessage: string }
| { status: SecretScanningScanStatus.Completed; numberOfSecrets: number; scanId: string; isDiffScan: boolean } | {
status: SecretScanningScanStatus.Completed;
numberOfSecrets: number;
scanId: string;
isDiffScan: boolean;
authorName?: string;
authorEmail?: string;
}
); );
export type TCloneRepository = { export type TCloneRepository = {

View File

@ -2472,6 +2472,9 @@ export const SecretSyncs = {
projectName: "The name of the Cloudflare Pages project to sync secrets to.", projectName: "The name of the Cloudflare Pages project to sync secrets to.",
environment: "The environment of the Cloudflare Pages project to sync secrets to." environment: "The environment of the Cloudflare Pages project to sync secrets to."
}, },
CLOUDFLARE_WORKERS: {
scriptId: "The ID of the Cloudflare Workers script to sync secrets to."
},
ZABBIX: { ZABBIX: {
scope: "The Zabbix scope that secrets should be synced to.", scope: "The Zabbix scope that secrets should be synced to.",
hostId: "The ID of the Zabbix host to sync secrets to.", hostId: "The ID of the Zabbix host to sync secrets to.",

View File

@ -50,4 +50,32 @@ export const registerCloudflareConnectionRouter = async (server: FastifyZodProvi
return projects; return projects;
} }
}); });
server.route({
method: "GET",
url: `/:connectionId/cloudflare-workers-scripts`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z
.object({
id: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const projects = await server.services.appConnection.cloudflare.listWorkersScripts(connectionId, req.permission);
return projects;
}
});
}; };

View File

@ -0,0 +1,17 @@
import {
CloudflareWorkersSyncSchema,
CreateCloudflareWorkersSyncSchema,
UpdateCloudflareWorkersSyncSchema
} from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerCloudflareWorkersSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.CloudflareWorkers,
server,
responseSchema: CloudflareWorkersSyncSchema,
createSchema: CreateCloudflareWorkersSyncSchema,
updateSchema: UpdateCloudflareWorkersSyncSchema
});

View File

@ -9,6 +9,7 @@ import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router";
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router"; import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
import { registerCamundaSyncRouter } from "./camunda-sync-router"; import { registerCamundaSyncRouter } from "./camunda-sync-router";
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router"; import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
import { registerDatabricksSyncRouter } from "./databricks-sync-router"; import { registerDatabricksSyncRouter } from "./databricks-sync-router";
import { registerFlyioSyncRouter } from "./flyio-sync-router"; import { registerFlyioSyncRouter } from "./flyio-sync-router";
import { registerGcpSyncRouter } from "./gcp-sync-router"; import { registerGcpSyncRouter } from "./gcp-sync-router";
@ -50,6 +51,8 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.Flyio]: registerFlyioSyncRouter, [SecretSync.Flyio]: registerFlyioSyncRouter,
[SecretSync.GitLab]: registerGitLabSyncRouter, [SecretSync.GitLab]: registerGitLabSyncRouter,
[SecretSync.CloudflarePages]: registerCloudflarePagesSyncRouter, [SecretSync.CloudflarePages]: registerCloudflarePagesSyncRouter,
[SecretSync.CloudflareWorkers]: registerCloudflareWorkersSyncRouter,
[SecretSync.Zabbix]: registerZabbixSyncRouter, [SecretSync.Zabbix]: registerZabbixSyncRouter,
[SecretSync.Railway]: registerRailwaySyncRouter [SecretSync.Railway]: registerRailwaySyncRouter
}; };

View File

@ -26,6 +26,10 @@ import {
CloudflarePagesSyncListItemSchema, CloudflarePagesSyncListItemSchema,
CloudflarePagesSyncSchema CloudflarePagesSyncSchema
} from "@app/services/secret-sync/cloudflare-pages/cloudflare-pages-schema"; } from "@app/services/secret-sync/cloudflare-pages/cloudflare-pages-schema";
import {
CloudflareWorkersSyncListItemSchema,
CloudflareWorkersSyncSchema
} from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas";
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks"; import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-sync/flyio"; import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-sync/flyio";
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp"; import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
@ -65,6 +69,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
FlyioSyncSchema, FlyioSyncSchema,
GitLabSyncSchema, GitLabSyncSchema,
CloudflarePagesSyncSchema, CloudflarePagesSyncSchema,
CloudflareWorkersSyncSchema,
ZabbixSyncSchema, ZabbixSyncSchema,
RailwaySyncSchema RailwaySyncSchema
]); ]);
@ -92,6 +98,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
FlyioSyncListItemSchema, FlyioSyncListItemSchema,
GitLabSyncListItemSchema, GitLabSyncListItemSchema,
CloudflarePagesSyncListItemSchema, CloudflarePagesSyncListItemSchema,
CloudflareWorkersSyncListItemSchema,
ZabbixSyncListItemSchema, ZabbixSyncListItemSchema,
RailwaySyncListItemSchema RailwaySyncListItemSchema
]); ]);

View File

@ -9,7 +9,8 @@ import { CloudflareConnectionMethod } from "./cloudflare-connection-enum";
import { import {
TCloudflareConnection, TCloudflareConnection,
TCloudflareConnectionConfig, TCloudflareConnectionConfig,
TCloudflarePagesProject TCloudflarePagesProject,
TCloudflareWorkersScript
} from "./cloudflare-connection-types"; } from "./cloudflare-connection-types";
export const getCloudflareConnectionListItem = () => { export const getCloudflareConnectionListItem = () => {
@ -43,6 +44,28 @@ export const listCloudflarePagesProjects = async (
})); }));
}; };
export const listCloudflareWorkersScripts = async (
appConnection: TCloudflareConnection
): Promise<TCloudflareWorkersScript[]> => {
const {
credentials: { apiToken, accountId }
} = appConnection;
const { data } = await request.get<{ result: { id: string }[] }>(
`${IntegrationUrls.CLOUDFLARE_API_URL}/client/v4/accounts/${accountId}/workers/scripts`,
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
return data.result.map((a) => ({
id: a.id
}));
};
export const validateCloudflareConnectionCredentials = async (config: TCloudflareConnectionConfig) => { export const validateCloudflareConnectionCredentials = async (config: TCloudflareConnectionConfig) => {
const { apiToken, accountId } = config.credentials; const { apiToken, accountId } = config.credentials;

View File

@ -2,7 +2,7 @@ import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../app-connection-enums";
import { listCloudflarePagesProjects } from "./cloudflare-connection-fns"; import { listCloudflarePagesProjects, listCloudflareWorkersScripts } from "./cloudflare-connection-fns";
import { TCloudflareConnection } from "./cloudflare-connection-types"; import { TCloudflareConnection } from "./cloudflare-connection-types";
type TGetAppConnectionFunc = ( type TGetAppConnectionFunc = (
@ -19,12 +19,31 @@ export const cloudflareConnectionService = (getAppConnection: TGetAppConnectionF
return projects; return projects;
} catch (error) { } catch (error) {
logger.error(error, "Failed to list Cloudflare Pages projects for Cloudflare connection"); logger.error(
error,
`Failed to list Cloudflare Pages projects for Cloudflare connection [connectionId=${connectionId}]`
);
return [];
}
};
const listWorkersScripts = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Cloudflare, connectionId, actor);
try {
const projects = await listCloudflareWorkersScripts(appConnection);
return projects;
} catch (error) {
logger.error(
error,
`Failed to list Cloudflare Workers scripts for Cloudflare connection [connectionId=${connectionId}]`
);
return []; return [];
} }
}; };
return { return {
listPagesProjects listPagesProjects,
listWorkersScripts
}; };
}; };

View File

@ -28,3 +28,7 @@ export type TCloudflarePagesProject = {
id: string; id: string;
name: string; name: string;
}; };
export type TCloudflareWorkersScript = {
id: string;
};

View File

@ -7,7 +7,6 @@ import { request } from "@app/lib/config/request";
import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors";
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns"; import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { getInstanceIntegrationsConfig } from "@app/services/super-admin/super-admin-service";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../app-connection-enums";
import { GitHubConnectionMethod } from "./github-connection-enums"; import { GitHubConnectionMethod } from "./github-connection-enums";
@ -15,14 +14,13 @@ import { TGitHubConnection, TGitHubConnectionConfig } from "./github-connection-
export const getGitHubConnectionListItem = () => { export const getGitHubConnectionListItem = () => {
const { INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_APP_SLUG } = getConfig(); const { INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_APP_SLUG } = getConfig();
const { gitHubAppConnection } = getInstanceIntegrationsConfig();
return { return {
name: "GitHub" as const, name: "GitHub" as const,
app: AppConnection.GitHub as const, app: AppConnection.GitHub as const,
methods: Object.values(GitHubConnectionMethod) as [GitHubConnectionMethod.App, GitHubConnectionMethod.OAuth], methods: Object.values(GitHubConnectionMethod) as [GitHubConnectionMethod.App, GitHubConnectionMethod.OAuth],
oauthClientId: INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, oauthClientId: INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
appClientSlug: gitHubAppConnection.appSlug || INF_APP_CONNECTION_GITHUB_APP_SLUG appClientSlug: INF_APP_CONNECTION_GITHUB_APP_SLUG
}; };
}; };
@ -32,10 +30,9 @@ export const getGitHubClient = (appConnection: TGitHubConnection) => {
const { method, credentials } = appConnection; const { method, credentials } = appConnection;
let client: Octokit; let client: Octokit;
const { gitHubAppConnection } = getInstanceIntegrationsConfig();
const appId = gitHubAppConnection.appId || appCfg.INF_APP_CONNECTION_GITHUB_APP_ID; const appId = appCfg.INF_APP_CONNECTION_GITHUB_APP_ID;
const appPrivateKey = gitHubAppConnection.privateKey || appCfg.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY; const appPrivateKey = appCfg.INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY;
switch (method) { switch (method) {
case GitHubConnectionMethod.App: case GitHubConnectionMethod.App:
@ -157,8 +154,6 @@ type TokenRespData = {
export const validateGitHubConnectionCredentials = async (config: TGitHubConnectionConfig) => { export const validateGitHubConnectionCredentials = async (config: TGitHubConnectionConfig) => {
const { credentials, method } = config; const { credentials, method } = config;
const { gitHubAppConnection } = getInstanceIntegrationsConfig();
const { const {
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET, INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET,
@ -170,8 +165,8 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect
const { clientId, clientSecret } = const { clientId, clientSecret } =
method === GitHubConnectionMethod.App method === GitHubConnectionMethod.App
? { ? {
clientId: gitHubAppConnection.clientId || INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID, clientId: INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID,
clientSecret: gitHubAppConnection.clientSecret || INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET clientSecret: INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET
} }
: // oauth : // oauth
{ {

View File

@ -912,14 +912,6 @@ export const orgServiceFactory = ({
// if there exist no org membership we set is as given by the request // if there exist no org membership we set is as given by the request
if (!inviteeOrgMembership) { if (!inviteeOrgMembership) {
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
throw new BadRequestError({
name: "InviteUser",
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
});
}
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) { if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed // limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
throw new BadRequestError({ throw new BadRequestError({

View File

@ -0,0 +1,10 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
export const CLOUDFLARE_WORKERS_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Cloudflare Workers",
destination: SecretSync.CloudflareWorkers,
connection: AppConnection.Cloudflare,
canImportSecrets: false
};

View File

@ -0,0 +1,121 @@
import { request } from "@app/lib/config/request";
import { applyJitter } from "@app/lib/dates";
import { delay as delayMs } from "@app/lib/delay";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
import { TCloudflareWorkersSyncWithCredentials } from "./cloudflare-workers-types";
const getSecretKeys = async (secretSync: TCloudflareWorkersSyncWithCredentials): Promise<string[]> => {
const {
destinationConfig,
connection: {
credentials: { apiToken, accountId }
}
} = secretSync;
const { data } = await request.get<{
result: Array<{ name: string }>;
}>(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/scripts/${destinationConfig.scriptId}/secrets`,
{
headers: {
Authorization: `Bearer ${apiToken}`,
Accept: "application/json"
}
}
);
return data.result.map((s) => s.name);
};
export const CloudflareWorkersSyncFns = {
syncSecrets: async (secretSync: TCloudflareWorkersSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection: {
credentials: { apiToken, accountId }
},
destinationConfig: { scriptId }
} = secretSync;
const existingSecretNames = await getSecretKeys(secretSync);
const secretMapKeys = new Set(Object.keys(secretMap));
for await (const [key, val] of Object.entries(secretMap)) {
await delayMs(Math.max(0, applyJitter(100, 200)));
await request.put(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/scripts/${scriptId}/secrets`,
{ name: key, text: val.value, type: "secret_text" },
{
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json"
}
}
);
}
if (!secretSync.syncOptions.disableSecretDeletion) {
const secretsToDelete = existingSecretNames.filter((existingKey) => {
const isManagedBySchema = matchesSchema(
existingKey,
secretSync.environment?.slug || "",
secretSync.syncOptions.keySchema
);
const isInNewSecretMap = secretMapKeys.has(existingKey);
return !isInNewSecretMap && isManagedBySchema;
});
for await (const key of secretsToDelete) {
await delayMs(Math.max(0, applyJitter(100, 200)));
await request.delete(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/scripts/${scriptId}/secrets/${key}`,
{
headers: {
Authorization: `Bearer ${apiToken}`
}
}
);
}
}
},
getSecrets: async (secretSync: TCloudflareWorkersSyncWithCredentials): Promise<TSecretMap> => {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
},
removeSecrets: async (secretSync: TCloudflareWorkersSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection: {
credentials: { apiToken, accountId }
},
destinationConfig: { scriptId }
} = secretSync;
const existingSecretNames = await getSecretKeys(secretSync);
const secretMapToRemoveKeys = new Set(Object.keys(secretMap));
for await (const existingKey of existingSecretNames) {
const isManagedBySchema = matchesSchema(
existingKey,
secretSync.environment?.slug || "",
secretSync.syncOptions.keySchema
);
const isInSecretMapToRemove = secretMapToRemoveKeys.has(existingKey);
if (isInSecretMapToRemove && isManagedBySchema) {
await delayMs(Math.max(0, applyJitter(100, 200)));
await request.delete(
`${IntegrationUrls.CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/scripts/${scriptId}/secrets/${existingKey}`,
{
headers: {
Authorization: `Bearer ${apiToken}`
}
}
);
}
}
}
};

View File

@ -0,0 +1,55 @@
import RE2 from "re2";
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const CloudflareWorkersSyncDestinationConfigSchema = z.object({
scriptId: z
.string()
.min(1, "Script ID is required")
.max(64)
.refine((val) => {
const re2 = new RE2(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/);
return re2.test(val);
}, "Invalid script ID format")
.describe(SecretSyncs.DESTINATION_CONFIG.CLOUDFLARE_WORKERS.scriptId)
});
const CloudflareWorkersSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
export const CloudflareWorkersSyncSchema = BaseSecretSyncSchema(
SecretSync.CloudflareWorkers,
CloudflareWorkersSyncOptionsConfig
).extend({
destination: z.literal(SecretSync.CloudflareWorkers),
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema
});
export const CreateCloudflareWorkersSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.CloudflareWorkers,
CloudflareWorkersSyncOptionsConfig
).extend({
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema
});
export const UpdateCloudflareWorkersSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.CloudflareWorkers,
CloudflareWorkersSyncOptionsConfig
).extend({
destinationConfig: CloudflareWorkersSyncDestinationConfigSchema.optional()
});
export const CloudflareWorkersSyncListItemSchema = z.object({
name: z.literal("Cloudflare Workers"),
connection: z.literal(AppConnection.Cloudflare),
destination: z.literal(SecretSync.CloudflareWorkers),
canImportSecrets: z.literal(false)
});

View File

@ -0,0 +1,19 @@
import z from "zod";
import { TCloudflareConnection } from "@app/services/app-connection/cloudflare/cloudflare-connection-types";
import {
CloudflareWorkersSyncListItemSchema,
CloudflareWorkersSyncSchema,
CreateCloudflareWorkersSyncSchema
} from "./cloudflare-workers-schemas";
export type TCloudflareWorkersSyncListItem = z.infer<typeof CloudflareWorkersSyncListItemSchema>;
export type TCloudflareWorkersSync = z.infer<typeof CloudflareWorkersSyncSchema>;
export type TCloudflareWorkersSyncInput = z.infer<typeof CreateCloudflareWorkersSyncSchema>;
export type TCloudflareWorkersSyncWithCredentials = TCloudflareWorkersSync & {
connection: TCloudflareConnection;
};

View File

@ -0,0 +1,4 @@
export * from "./cloudflare-workers-constants";
export * from "./cloudflare-workers-fns";
export * from "./cloudflare-workers-schemas";
export * from "./cloudflare-workers-types";

View File

@ -21,6 +21,8 @@ export enum SecretSync {
Flyio = "flyio", Flyio = "flyio",
GitLab = "gitlab", GitLab = "gitlab",
CloudflarePages = "cloudflare-pages", CloudflarePages = "cloudflare-pages",
CloudflareWorkers = "cloudflare-workers",
Zabbix = "zabbix", Zabbix = "zabbix",
Railway = "railway" Railway = "railway"
} }

View File

@ -31,6 +31,7 @@ import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./az
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda"; import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants"; import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns"; import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
import { FLYIO_SYNC_LIST_OPTION, FlyioSyncFns } from "./flyio"; import { FLYIO_SYNC_LIST_OPTION, FlyioSyncFns } from "./flyio";
import { GCP_SYNC_LIST_OPTION } from "./gcp"; import { GCP_SYNC_LIST_OPTION } from "./gcp";
import { GcpSyncFns } from "./gcp/gcp-sync-fns"; import { GcpSyncFns } from "./gcp/gcp-sync-fns";
@ -72,6 +73,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.Flyio]: FLYIO_SYNC_LIST_OPTION, [SecretSync.Flyio]: FLYIO_SYNC_LIST_OPTION,
[SecretSync.GitLab]: GITLAB_SYNC_LIST_OPTION, [SecretSync.GitLab]: GITLAB_SYNC_LIST_OPTION,
[SecretSync.CloudflarePages]: CLOUDFLARE_PAGES_SYNC_LIST_OPTION, [SecretSync.CloudflarePages]: CLOUDFLARE_PAGES_SYNC_LIST_OPTION,
[SecretSync.CloudflareWorkers]: CLOUDFLARE_WORKERS_SYNC_LIST_OPTION,
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION, [SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION [SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION
}; };
@ -241,6 +244,8 @@ export const SecretSyncFns = {
return GitLabSyncFns.syncSecrets(secretSync, schemaSecretMap, { appConnectionDAL, kmsService }); return GitLabSyncFns.syncSecrets(secretSync, schemaSecretMap, { appConnectionDAL, kmsService });
case SecretSync.CloudflarePages: case SecretSync.CloudflarePages:
return CloudflarePagesSyncFns.syncSecrets(secretSync, schemaSecretMap); return CloudflarePagesSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.CloudflareWorkers:
return CloudflareWorkersSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Zabbix: case SecretSync.Zabbix:
return ZabbixSyncFns.syncSecrets(secretSync, schemaSecretMap); return ZabbixSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Railway: case SecretSync.Railway:
@ -337,6 +342,9 @@ export const SecretSyncFns = {
case SecretSync.CloudflarePages: case SecretSync.CloudflarePages:
secretMap = await CloudflarePagesSyncFns.getSecrets(secretSync); secretMap = await CloudflarePagesSyncFns.getSecrets(secretSync);
break; break;
case SecretSync.CloudflareWorkers:
secretMap = await CloudflareWorkersSyncFns.getSecrets(secretSync);
break;
case SecretSync.Zabbix: case SecretSync.Zabbix:
secretMap = await ZabbixSyncFns.getSecrets(secretSync); secretMap = await ZabbixSyncFns.getSecrets(secretSync);
break; break;
@ -420,6 +428,8 @@ export const SecretSyncFns = {
return GitLabSyncFns.removeSecrets(secretSync, schemaSecretMap, { appConnectionDAL, kmsService }); return GitLabSyncFns.removeSecrets(secretSync, schemaSecretMap, { appConnectionDAL, kmsService });
case SecretSync.CloudflarePages: case SecretSync.CloudflarePages:
return CloudflarePagesSyncFns.removeSecrets(secretSync, schemaSecretMap); return CloudflarePagesSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.CloudflareWorkers:
return CloudflareWorkersSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Zabbix: case SecretSync.Zabbix:
return ZabbixSyncFns.removeSecrets(secretSync, schemaSecretMap); return ZabbixSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Railway: case SecretSync.Railway:

View File

@ -24,6 +24,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.Flyio]: "Fly.io", [SecretSync.Flyio]: "Fly.io",
[SecretSync.GitLab]: "GitLab", [SecretSync.GitLab]: "GitLab",
[SecretSync.CloudflarePages]: "Cloudflare Pages", [SecretSync.CloudflarePages]: "Cloudflare Pages",
[SecretSync.CloudflareWorkers]: "Cloudflare Workers",
[SecretSync.Zabbix]: "Zabbix", [SecretSync.Zabbix]: "Zabbix",
[SecretSync.Railway]: "Railway" [SecretSync.Railway]: "Railway"
}; };
@ -51,6 +53,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.Flyio]: AppConnection.Flyio, [SecretSync.Flyio]: AppConnection.Flyio,
[SecretSync.GitLab]: AppConnection.GitLab, [SecretSync.GitLab]: AppConnection.GitLab,
[SecretSync.CloudflarePages]: AppConnection.Cloudflare, [SecretSync.CloudflarePages]: AppConnection.Cloudflare,
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
[SecretSync.Zabbix]: AppConnection.Zabbix, [SecretSync.Zabbix]: AppConnection.Zabbix,
[SecretSync.Railway]: AppConnection.Railway [SecretSync.Railway]: AppConnection.Railway
}; };
@ -78,6 +82,8 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
[SecretSync.Flyio]: SecretSyncPlanType.Regular, [SecretSync.Flyio]: SecretSyncPlanType.Regular,
[SecretSync.GitLab]: SecretSyncPlanType.Regular, [SecretSync.GitLab]: SecretSyncPlanType.Regular,
[SecretSync.CloudflarePages]: SecretSyncPlanType.Regular, [SecretSync.CloudflarePages]: SecretSyncPlanType.Regular,
[SecretSync.CloudflareWorkers]: SecretSyncPlanType.Regular,
[SecretSync.Zabbix]: SecretSyncPlanType.Regular, [SecretSync.Zabbix]: SecretSyncPlanType.Regular,
[SecretSync.Railway]: SecretSyncPlanType.Regular [SecretSync.Railway]: SecretSyncPlanType.Regular
}; };

View File

@ -78,6 +78,12 @@ import {
TCloudflarePagesSyncListItem, TCloudflarePagesSyncListItem,
TCloudflarePagesSyncWithCredentials TCloudflarePagesSyncWithCredentials
} from "./cloudflare-pages/cloudflare-pages-types"; } from "./cloudflare-pages/cloudflare-pages-types";
import {
TCloudflareWorkersSync,
TCloudflareWorkersSyncInput,
TCloudflareWorkersSyncListItem,
TCloudflareWorkersSyncWithCredentials
} from "./cloudflare-workers";
import { TFlyioSync, TFlyioSyncInput, TFlyioSyncListItem, TFlyioSyncWithCredentials } from "./flyio/flyio-sync-types"; import { TFlyioSync, TFlyioSyncInput, TFlyioSyncListItem, TFlyioSyncWithCredentials } from "./flyio/flyio-sync-types";
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp"; import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
import { TGitLabSync, TGitLabSyncInput, TGitLabSyncListItem, TGitLabSyncWithCredentials } from "./gitlab"; import { TGitLabSync, TGitLabSyncInput, TGitLabSyncListItem, TGitLabSyncWithCredentials } from "./gitlab";
@ -144,6 +150,7 @@ export type TSecretSync =
| TFlyioSync | TFlyioSync
| TGitLabSync | TGitLabSync
| TCloudflarePagesSync | TCloudflarePagesSync
| TCloudflareWorkersSync
| TZabbixSync | TZabbixSync
| TRailwaySync; | TRailwaySync;
@ -170,6 +177,7 @@ export type TSecretSyncWithCredentials =
| TFlyioSyncWithCredentials | TFlyioSyncWithCredentials
| TGitLabSyncWithCredentials | TGitLabSyncWithCredentials
| TCloudflarePagesSyncWithCredentials | TCloudflarePagesSyncWithCredentials
| TCloudflareWorkersSyncWithCredentials
| TZabbixSyncWithCredentials | TZabbixSyncWithCredentials
| TRailwaySyncWithCredentials; | TRailwaySyncWithCredentials;
@ -196,6 +204,7 @@ export type TSecretSyncInput =
| TFlyioSyncInput | TFlyioSyncInput
| TGitLabSyncInput | TGitLabSyncInput
| TCloudflarePagesSyncInput | TCloudflarePagesSyncInput
| TCloudflareWorkersSyncInput
| TZabbixSyncInput | TZabbixSyncInput
| TRailwaySyncInput; | TRailwaySyncInput;
@ -222,6 +231,7 @@ export type TSecretSyncListItem =
| TFlyioSyncListItem | TFlyioSyncListItem
| TGitLabSyncListItem | TGitLabSyncListItem
| TCloudflarePagesSyncListItem | TCloudflarePagesSyncListItem
| TCloudflareWorkersSyncListItem
| TZabbixSyncListItem | TZabbixSyncListItem
| TRailwaySyncListItem; | TRailwaySyncListItem;

View File

@ -1,6 +1,36 @@
FROM node:20-alpine FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
RUN npm install -g mint
RUN npm install -g mint@4.2.13
COPY . . COPY . .
# Install a local version of our OpenAPI spec
RUN apk add --no-cache wget jq && \
wget -O spec.json https://app.infisical.com/api/docs/json && \
jq '.api.openapi = "./spec.json"' docs.json > temp.json && \
mv temp.json docs.json
# Run mint dev briefly to download the web client
RUN timeout 30 mint dev || true
FROM node:20-alpine
WORKDIR /app
RUN addgroup -g 1001 -S mintuser && \
adduser -S -D -H -u 1001 -s /sbin/nologin -G mintuser mintuser && \
npm install -g mint@4.2.13
COPY --chown=mintuser:mintuser . .
COPY --from=builder --chown=mintuser:mintuser /root/.mintlify /home/mintuser/.mintlify
COPY --from=builder --chown=mintuser:mintuser /app/docs.json /app/docs.json
COPY --from=builder --chown=mintuser:mintuser /app/spec.json /app/spec.json
USER mintuser
EXPOSE 3000 EXPOSE 3000
CMD ["mint", "dev"] CMD ["mint", "dev"]

View File

@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/secret-syncs/cloudflare-workers"
---

View File

@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/secret-syncs/cloudflare-workers/{syncId}"
---

View File

@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/secret-syncs/cloudflare-workers/{syncId}"
---

View File

@ -0,0 +1,4 @@
---
title: "Get by Name"
openapi: "GET /api/v1/secret-syncs/cloudflare-workers/sync-name/{syncName}"
---

View File

@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v1/secret-syncs/cloudflare-workers"
---

View File

@ -0,0 +1,4 @@
---
title: "Remove Secrets"
openapi: "POST /api/v1/secret-syncs/cloudflare-workers/{syncId}/remove-secrets"
---

View File

@ -0,0 +1,4 @@
---
title: "Sync Secrets"
openapi: "POST /api/v1/secret-syncs/cloudflare-workers/{syncId}/sync-secrets"
---

View File

@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/secret-syncs/cloudflare-workers/{syncId}"
---

View File

@ -78,7 +78,10 @@
}, },
{ {
"group": "Infisical SSH", "group": "Infisical SSH",
"pages": ["documentation/platform/ssh/overview", "documentation/platform/ssh/host-groups"] "pages": [
"documentation/platform/ssh/overview",
"documentation/platform/ssh/host-groups"
]
}, },
{ {
"group": "Key Management (KMS)", "group": "Key Management (KMS)",
@ -375,7 +378,10 @@
}, },
{ {
"group": "Architecture", "group": "Architecture",
"pages": ["internals/architecture/components", "internals/architecture/cloud"] "pages": [
"internals/architecture/components",
"internals/architecture/cloud"
]
}, },
"internals/security", "internals/security",
"internals/service-tokens" "internals/service-tokens"
@ -508,6 +514,7 @@
"integrations/secret-syncs/azure-key-vault", "integrations/secret-syncs/azure-key-vault",
"integrations/secret-syncs/camunda", "integrations/secret-syncs/camunda",
"integrations/secret-syncs/cloudflare-pages", "integrations/secret-syncs/cloudflare-pages",
"integrations/secret-syncs/cloudflare-workers",
"integrations/secret-syncs/databricks", "integrations/secret-syncs/databricks",
"integrations/secret-syncs/flyio", "integrations/secret-syncs/flyio",
"integrations/secret-syncs/gcp-secret-manager", "integrations/secret-syncs/gcp-secret-manager",
@ -546,7 +553,10 @@
"integrations/cloud/gcp-secret-manager", "integrations/cloud/gcp-secret-manager",
{ {
"group": "Cloudflare", "group": "Cloudflare",
"pages": ["integrations/cloud/cloudflare-pages", "integrations/cloud/cloudflare-workers"] "pages": [
"integrations/cloud/cloudflare-pages",
"integrations/cloud/cloudflare-workers"
]
}, },
"integrations/cloud/terraform-cloud", "integrations/cloud/terraform-cloud",
"integrations/cloud/databricks", "integrations/cloud/databricks",
@ -658,7 +668,11 @@
"cli/commands/reset", "cli/commands/reset",
{ {
"group": "infisical scan", "group": "infisical scan",
"pages": ["cli/commands/scan", "cli/commands/scan-git-changes", "cli/commands/scan-install"] "pages": [
"cli/commands/scan",
"cli/commands/scan-git-changes",
"cli/commands/scan-install"
]
} }
] ]
}, },
@ -982,7 +996,9 @@
"pages": [ "pages": [
{ {
"group": "Kubernetes", "group": "Kubernetes",
"pages": ["api-reference/endpoints/dynamic-secrets/kubernetes/create-lease"] "pages": [
"api-reference/endpoints/dynamic-secrets/kubernetes/create-lease"
]
}, },
"api-reference/endpoints/dynamic-secrets/create", "api-reference/endpoints/dynamic-secrets/create",
"api-reference/endpoints/dynamic-secrets/update", "api-reference/endpoints/dynamic-secrets/update",
@ -1705,6 +1721,19 @@
"api-reference/endpoints/secret-syncs/cloudflare-pages/remove-secrets" "api-reference/endpoints/secret-syncs/cloudflare-pages/remove-secrets"
] ]
}, },
{
"group": "Cloudflare Workers",
"pages": [
"api-reference/endpoints/secret-syncs/cloudflare-workers/list",
"api-reference/endpoints/secret-syncs/cloudflare-workers/get-by-id",
"api-reference/endpoints/secret-syncs/cloudflare-workers/get-by-name",
"api-reference/endpoints/secret-syncs/cloudflare-workers/create",
"api-reference/endpoints/secret-syncs/cloudflare-workers/update",
"api-reference/endpoints/secret-syncs/cloudflare-workers/delete",
"api-reference/endpoints/secret-syncs/cloudflare-workers/sync-secrets",
"api-reference/endpoints/secret-syncs/cloudflare-workers/remove-secrets"
]
},
{ {
"group": "Databricks", "group": "Databricks",
"pages": [ "pages": [

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

View File

@ -35,6 +35,17 @@ Infisical supports connecting to Cloudflare using API tokens and Account ID for
- **Account** - **Cloudflare Pages** - **Edit** - **Account** - **Cloudflare Pages** - **Edit**
- **Account** - **Account Settings** - **Read** - **Account** - **Account Settings** - **Read**
Add these permissions to your API token and click **Continue to summary**, then **Create Token** to generate your API token.
</Accordion>
<Accordion title="Cloudflare Workers">
Use the following permissions to grant Infisical access to sync secrets to Cloudflare Workers:
![Configure Token](/images/app-connections/cloudflare/cloudflare-workers-configure-permissions.png)
**Required Permissions:**
- **Account** - **Workers Scripts** - **Edit**
- **Account** - **Account Settings** - **Read**
Add these permissions to your API token and click **Continue to summary**, then **Create Token** to generate your API token. Add these permissions to your API token and click **Continue to summary**, then **Create Token** to generate your API token.
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
@ -44,7 +55,7 @@ Infisical supports connecting to Cloudflare using API tokens and Account ID for
</Step> </Step>
<Step title="Save Your API Token"> <Step title="Save Your API Token">
After creation, copy and securely store your API token as it will not be shown again. After creation, copy and securely store your API token as it will not be shown again.
![Generated API Token](/images/app-connections/cloudflare/cloudflare-generated-token.png) ![Generated API Token](/images/app-connections/cloudflare/cloudflare-generated-token.png)
<Warning> <Warning>

View File

@ -0,0 +1,128 @@
---
title: "Cloudflare Workers Sync"
description: "Learn how to configure a Cloudflare Workers Sync for Infisical."
---
**Prerequisites:**
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
- Create a [Cloudflare Connection](/integrations/app-connections/cloudflare)
<Tabs>
<Tab title="Infisical UI">
1. Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
![Secret Syncs Tab](/images/secret-syncs/general/secret-sync-tab.png)
2. Select the **Cloudflare Workers** option.
![Select Cloudflare Workers](/images/secret-syncs/cloudflare-workers/select-cloudflare-workers-option.png)
3. Configure the **Source** from where secrets should be retrieved, then click **Next**.
![Configure Source](/images/secret-syncs/cloudflare-workers/cloudflare-workers-sync-source.png)
- **Environment**: The project environment to retrieve secrets from.
- **Secret Path**: The folder path to retrieve secrets from.
<Tip>
If you need to sync secrets from multiple folder locations, check out [secret imports](/documentation/platform/secret-reference#secret-imports).
</Tip>
4. Configure the **Destination** to where secrets should be deployed, then click **Next**.
![Configure Destination](/images/secret-syncs/cloudflare-workers/cloudflare-workers-sync-destination.png)
- **Cloudflare Connection**: The Cloudflare Connection to authenticate with.
- **Cloudflare Workers Script**: Choose the Cloudflare Workers script you want to sync secrets to.
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
![Configure Options](/images/secret-syncs/cloudflare-workers/cloudflare-workers-sync-options.png)
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Cloudflare Workers Sync, then click **Next**.
![Configure Details](/images/secret-syncs/cloudflare-workers/cloudflare-workers-sync-details.png)
- **Name**: The name of your sync. Must be slug-friendly.
- **Description**: An optional description for your sync.
7. Review your Cloudflare Workers Sync configuration, then click **Create Sync**.
![Confirm Configuration](/images/secret-syncs/cloudflare-workers/cloudflare-workers-sync-review.png)
8. If enabled, your Cloudflare Workers Sync will begin syncing your secrets to the destination endpoint.
![Sync Secrets](/images/secret-syncs/cloudflare-workers/cloudflare-workers-sync-created.png)
</Tab>
<Tab title="API">
To create a **Cloudflare Workers Sync**, make an API request to the [Create Cloudflare Workers Sync](/api-reference/endpoints/secret-syncs/cloudflare-workers/create) API endpoint.
### Sample request
```bash Request
curl --request POST \
--url https://app.infisical.com/api/v1/secret-syncs/cloudflare-workers \
--header 'Content-Type: application/json' \
--data '{
"name": "my-cloudflare-workers-sync",
"projectId": "your-project-id",
"description": "an example sync",
"connectionId": "your-cloudflare-connection-id",
"environment": "production",
"secretPath": "/my-secrets",
"isEnabled": true,
"syncOptions": {
"initialSyncBehavior": "overwrite-destination"
},
"destinationConfig": {
"scriptId": "my-workers-script"
}
}'
```
### Sample response
```bash Response
{
"secretSync": {
"id": "your-sync-id",
"name": "my-cloudflare-workers-sync",
"description": "an example sync",
"isEnabled": true,
"version": 1,
"folderId": "your-folder-id",
"connectionId": "your-cloudflare-connection-id",
"createdAt": "2024-05-01T12:00:00Z",
"updatedAt": "2024-05-01T12:00:00Z",
"syncStatus": "succeeded",
"lastSyncJobId": "123",
"lastSyncMessage": null,
"lastSyncedAt": "2024-05-01T12:00:00Z",
"syncOptions": {
"initialSyncBehavior": "overwrite-destination"
},
"projectId": "your-project-id",
"connection": {
"app": "cloudflare",
"name": "my-cloudflare-connection",
"id": "your-cloudflare-connection-id"
},
"environment": {
"slug": "production",
"name": "Production",
"id": "your-env-id"
},
"folder": {
"id": "your-folder-id",
"path": "/my-secrets"
},
"destination": "cloudflare-workers",
"destinationConfig": {
"scriptId": "my-workers-script"
}
}
}
```
</Tab>
</Tabs>

View File

@ -4,7 +4,7 @@ import { SingleValue } from "react-select";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField"; import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl, Select, SelectItem } from "@app/components/v2"; import { FilterableSelect, FormControl, Select, SelectItem } from "@app/components/v2";
import { import {
TCloudflareProject, TCloudflarePagesProject,
useCloudflareConnectionListPagesProjects useCloudflareConnectionListPagesProjects
} from "@app/hooks/api/appConnections/cloudflare"; } from "@app/hooks/api/appConnections/cloudflare";
import { SecretSync } from "@app/hooks/api/secretSyncs"; import { SecretSync } from "@app/hooks/api/secretSyncs";
@ -52,7 +52,7 @@ export const CloudflarePagesSyncFields = () => {
isDisabled={!connectionId} isDisabled={!connectionId}
value={projects ? (projects.find((project) => project.name === value) ?? []) : []} value={projects ? (projects.find((project) => project.name === value) ?? []) : []}
onChange={(option) => { onChange={(option) => {
onChange((option as SingleValue<TCloudflareProject>)?.name ?? null); onChange((option as SingleValue<TCloudflarePagesProject>)?.name ?? null);
}} }}
options={projects} options={projects}
placeholder="Select a project..." placeholder="Select a project..."

View File

@ -0,0 +1,59 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl } from "@app/components/v2";
import {
TCloudflareWorkersScript,
useCloudflareConnectionListWorkersScripts
} from "@app/hooks/api/appConnections/cloudflare";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const CloudflareWorkersSyncFields = () => {
const { control, setValue } = useFormContext<
TSecretSyncForm & { destination: SecretSync.CloudflareWorkers }
>();
const connectionId = useWatch({ name: "connection.id", control });
const { data: scripts = [], isPending: isScriptsPending } =
useCloudflareConnectionListWorkersScripts(connectionId, {
enabled: Boolean(connectionId)
});
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.scriptId", "");
}}
/>
<Controller
name="destinationConfig.scriptId"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Worker Script"
>
<FilterableSelect
isLoading={isScriptsPending && Boolean(connectionId)}
isDisabled={!connectionId}
value={scripts?.find((script) => script.id === value) || []}
onChange={(option) => {
onChange((option as SingleValue<TCloudflareWorkersScript>)?.id ?? null);
}}
options={scripts}
placeholder="Select a worker script..."
getOptionLabel={(option) => option.id}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
</>
);
};

View File

@ -11,6 +11,7 @@ import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields";
import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields"; import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields";
import { CamundaSyncFields } from "./CamundaSyncFields"; import { CamundaSyncFields } from "./CamundaSyncFields";
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields"; import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
import { CloudflareWorkersSyncFields } from "./CloudflareWorkersSyncFields";
import { DatabricksSyncFields } from "./DatabricksSyncFields"; import { DatabricksSyncFields } from "./DatabricksSyncFields";
import { FlyioSyncFields } from "./FlyioSyncFields"; import { FlyioSyncFields } from "./FlyioSyncFields";
import { GcpSyncFields } from "./GcpSyncFields"; import { GcpSyncFields } from "./GcpSyncFields";
@ -78,6 +79,8 @@ export const SecretSyncDestinationFields = () => {
return <GitLabSyncFields />; return <GitLabSyncFields />;
case SecretSync.CloudflarePages: case SecretSync.CloudflarePages:
return <CloudflarePagesSyncFields />; return <CloudflarePagesSyncFields />;
case SecretSync.CloudflareWorkers:
return <CloudflareWorkersSyncFields />;
case SecretSync.Zabbix: case SecretSync.Zabbix:
return <ZabbixSyncFields />; return <ZabbixSyncFields />;
case SecretSync.Railway: case SecretSync.Railway:

View File

@ -58,6 +58,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.Flyio: case SecretSync.Flyio:
case SecretSync.GitLab: case SecretSync.GitLab:
case SecretSync.CloudflarePages: case SecretSync.CloudflarePages:
case SecretSync.CloudflareWorkers:
case SecretSync.Zabbix: case SecretSync.Zabbix:
case SecretSync.Railway: case SecretSync.Railway:
AdditionalSyncOptionsFieldsComponent = null; AdditionalSyncOptionsFieldsComponent = null;

View File

@ -0,0 +1,14 @@
import { useFormContext } from "react-hook-form";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { GenericFieldLabel } from "@app/components/v2";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const CloudflareWorkersSyncReviewFields = () => {
const { watch } = useFormContext<
TSecretSyncForm & { destination: SecretSync.CloudflareWorkers }
>();
const scriptId = watch("destinationConfig.scriptId");
return <GenericFieldLabel label="Script">{scriptId}</GenericFieldLabel>;
};

View File

@ -20,6 +20,7 @@ import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields";
import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields"; import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields";
import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields"; import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields";
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields"; import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
import { CloudflareWorkersSyncReviewFields } from "./CloudflareWorkersReviewFields";
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields"; import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields"; import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields";
import { GcpSyncReviewFields } from "./GcpSyncReviewFields"; import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
@ -126,6 +127,9 @@ export const SecretSyncReviewFields = () => {
case SecretSync.CloudflarePages: case SecretSync.CloudflarePages:
DestinationFieldsComponent = <CloudflarePagesSyncReviewFields />; DestinationFieldsComponent = <CloudflarePagesSyncReviewFields />;
break; break;
case SecretSync.CloudflareWorkers:
DestinationFieldsComponent = <CloudflareWorkersSyncReviewFields />;
break;
case SecretSync.Zabbix: case SecretSync.Zabbix:
DestinationFieldsComponent = <ZabbixSyncReviewFields />; DestinationFieldsComponent = <ZabbixSyncReviewFields />;
break; break;

View File

@ -0,0 +1,18 @@
import { z } from "zod";
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const CloudflareWorkersSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({
destination: z.literal(SecretSync.CloudflareWorkers),
destinationConfig: z.object({
scriptId: z
.string()
.trim()
.min(1, "Script ID is required")
.max(64)
.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "Invalid script ID format")
})
})
);

View File

@ -8,6 +8,7 @@ import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destinatio
import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema"; import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema";
import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema"; import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema";
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema"; import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
import { CloudflareWorkersSyncDestinationSchema } from "./cloudflare-workers-sync-destination-schema";
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema"; import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema"; import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema";
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema"; import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
@ -48,6 +49,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
FlyioSyncDestinationSchema, FlyioSyncDestinationSchema,
GitlabSyncDestinationSchema, GitlabSyncDestinationSchema,
CloudflarePagesSyncDestinationSchema, CloudflarePagesSyncDestinationSchema,
CloudflareWorkersSyncDestinationSchema,
ZabbixSyncDestinationSchema, ZabbixSyncDestinationSchema,
RailwaySyncDestinationSchema RailwaySyncDestinationSchema
]); ]);

View File

@ -29,10 +29,6 @@ export const ROUTE_PATHS = Object.freeze({
"/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback" "/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback"
) )
}, },
SsoPage: setRoute(
"/organization/sso",
"/_authenticate/_inject-org-details/_org-layout/organization/sso"
),
SecretSharing: setRoute( SecretSharing: setRoute(
"/organization/secret-sharing", "/organization/secret-sharing",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/" "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/"

View File

@ -82,6 +82,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
name: "Cloudflare Pages", name: "Cloudflare Pages",
image: "Cloudflare.png" image: "Cloudflare.png"
}, },
[SecretSync.CloudflareWorkers]: {
name: "Cloudflare Workers",
image: "Cloudflare.png"
},
[SecretSync.Zabbix]: { [SecretSync.Zabbix]: {
name: "Zabbix", name: "Zabbix",
image: "Zabbix.png" image: "Zabbix.png"
@ -115,6 +119,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.Flyio]: AppConnection.Flyio, [SecretSync.Flyio]: AppConnection.Flyio,
[SecretSync.GitLab]: AppConnection.Gitlab, [SecretSync.GitLab]: AppConnection.Gitlab,
[SecretSync.CloudflarePages]: AppConnection.Cloudflare, [SecretSync.CloudflarePages]: AppConnection.Cloudflare,
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
[SecretSync.Zabbix]: AppConnection.Zabbix, [SecretSync.Zabbix]: AppConnection.Zabbix,
[SecretSync.Railway]: AppConnection.Railway [SecretSync.Railway]: AppConnection.Railway
}; };

View File

@ -170,7 +170,7 @@ export type TCreateAccessPolicyDTO = {
approvers?: Approver[]; approvers?: Approver[];
bypassers?: Bypasser[]; bypassers?: Bypasser[];
approvals?: number; approvals?: number;
secretPath?: string; secretPath: string;
enforcementLevel?: EnforcementLevel; enforcementLevel?: EnforcementLevel;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[]; approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];

View File

@ -3,21 +3,23 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { appConnectionKeys } from "../queries"; import { appConnectionKeys } from "../queries";
import { TCloudflareProject } from "./types"; import { TCloudflarePagesProject, TCloudflareWorkersScript } from "./types";
const cloudflareConnectionKeys = { const cloudflareConnectionKeys = {
all: [...appConnectionKeys.all, "cloudflare"] as const, all: [...appConnectionKeys.all, "cloudflare"] as const,
listPagesProjects: (connectionId: string) => listPagesProjects: (connectionId: string) =>
[...cloudflareConnectionKeys.all, "pages-projects", connectionId] as const [...cloudflareConnectionKeys.all, "pages-projects", connectionId] as const,
listWorkersScripts: (connectionId: string) =>
[...cloudflareConnectionKeys.all, "workers-scripts", connectionId] as const
}; };
export const useCloudflareConnectionListPagesProjects = ( export const useCloudflareConnectionListPagesProjects = (
connectionId: string, connectionId: string,
options?: Omit< options?: Omit<
UseQueryOptions< UseQueryOptions<
TCloudflareProject[], TCloudflarePagesProject[],
unknown, unknown,
TCloudflareProject[], TCloudflarePagesProject[],
ReturnType<typeof cloudflareConnectionKeys.listPagesProjects> ReturnType<typeof cloudflareConnectionKeys.listPagesProjects>
>, >,
"queryKey" | "queryFn" "queryKey" | "queryFn"
@ -26,7 +28,7 @@ export const useCloudflareConnectionListPagesProjects = (
return useQuery({ return useQuery({
queryKey: cloudflareConnectionKeys.listPagesProjects(connectionId), queryKey: cloudflareConnectionKeys.listPagesProjects(connectionId),
queryFn: async () => { queryFn: async () => {
const { data } = await apiRequest.get<TCloudflareProject[]>( const { data } = await apiRequest.get<TCloudflarePagesProject[]>(
`/api/v1/app-connections/cloudflare/${connectionId}/cloudflare-pages-projects` `/api/v1/app-connections/cloudflare/${connectionId}/cloudflare-pages-projects`
); );
@ -35,3 +37,28 @@ export const useCloudflareConnectionListPagesProjects = (
...options ...options
}); });
}; };
export const useCloudflareConnectionListWorkersScripts = (
connectionId: string,
options?: Omit<
UseQueryOptions<
TCloudflareWorkersScript[],
unknown,
TCloudflareWorkersScript[],
ReturnType<typeof cloudflareConnectionKeys.listWorkersScripts>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: cloudflareConnectionKeys.listWorkersScripts(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<TCloudflareWorkersScript[]>(
`/api/v1/app-connections/cloudflare/${connectionId}/cloudflare-workers-scripts`
);
return data;
},
...options
});
};

View File

@ -1,4 +1,8 @@
export type TCloudflareProject = { export type TCloudflarePagesProject = {
id: string; id: string;
name: string; name: string;
}; };
export type TCloudflareWorkersScript = {
id: string;
};

View File

@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { organizationKeys } from "../organization/queries"; import { organizationKeys } from "../organization/queries";
import { subscriptionQueryKeys } from "../subscriptions/queries";
import { identitiesKeys } from "./queries"; import { identitiesKeys } from "./queries";
import { import {
AddIdentityAliCloudAuthDTO, AddIdentityAliCloudAuthDTO,
@ -82,6 +83,9 @@ export const useCreateIdentity = () => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId) queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
}); });
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
});
} }
}); });
}; };
@ -123,6 +127,9 @@ export const useDeleteIdentity = () => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId) queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
}); });
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
});
} }
}); });
}; };

View File

@ -49,7 +49,7 @@ export type TCreateSecretPolicyDTO = {
workspaceId: string; workspaceId: string;
name?: string; name?: string;
environment: string; environment: string;
secretPath?: string | null; secretPath: string;
approvers?: Approver[]; approvers?: Approver[];
bypassers?: Bypasser[]; bypassers?: Bypasser[];
approvals?: number; approvals?: number;
@ -62,7 +62,7 @@ export type TUpdateSecretPolicyDTO = {
name?: string; name?: string;
approvers?: Approver[]; approvers?: Approver[];
bypassers?: Bypasser[]; bypassers?: Bypasser[];
secretPath?: string | null; secretPath?: string;
approvals?: number; approvals?: number;
allowedSelfApprovals?: boolean; allowedSelfApprovals?: boolean;
enforcementLevel?: EnforcementLevel; enforcementLevel?: EnforcementLevel;

View File

@ -21,6 +21,8 @@ export enum SecretSync {
Flyio = "flyio", Flyio = "flyio",
GitLab = "gitlab", GitLab = "gitlab",
CloudflarePages = "cloudflare-pages", CloudflarePages = "cloudflare-pages",
CloudflareWorkers = "cloudflare-workers",
Zabbix = "zabbix", Zabbix = "zabbix",
Railway = "railway" Railway = "railway"
} }

View File

@ -0,0 +1,15 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
export type TCloudflareWorkersSync = TRootSecretSync & {
destination: SecretSync.CloudflareWorkers;
destinationConfig: {
scriptId: string;
};
connection: {
app: AppConnection.Cloudflare;
name: string;
id: string;
};
};

View File

@ -10,6 +10,7 @@ import { TAzureDevOpsSync } from "./azure-devops-sync";
import { TAzureKeyVaultSync } from "./azure-key-vault-sync"; import { TAzureKeyVaultSync } from "./azure-key-vault-sync";
import { TCamundaSync } from "./camunda-sync"; import { TCamundaSync } from "./camunda-sync";
import { TCloudflarePagesSync } from "./cloudflare-pages-sync"; import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
import { TCloudflareWorkersSync } from "./cloudflare-workers-sync";
import { TDatabricksSync } from "./databricks-sync"; import { TDatabricksSync } from "./databricks-sync";
import { TFlyioSync } from "./flyio-sync"; import { TFlyioSync } from "./flyio-sync";
import { TGcpSync } from "./gcp-sync"; import { TGcpSync } from "./gcp-sync";
@ -56,6 +57,7 @@ export type TSecretSync =
| TFlyioSync | TFlyioSync
| TGitLabSync | TGitLabSync
| TCloudflarePagesSync | TCloudflarePagesSync
| TCloudflareWorkersSync
| TZabbixSync | TZabbixSync
| TRailwaySync; | TRailwaySync;

View File

@ -9,6 +9,7 @@ import { APIKeyDataV2 } from "../apiKeys/types";
import { MfaMethod } from "../auth/types"; import { MfaMethod } from "../auth/types";
import { TGroupWithProjectMemberships } from "../groups/types"; import { TGroupWithProjectMemberships } from "../groups/types";
import { setAuthToken } from "../reactQuery"; import { setAuthToken } from "../reactQuery";
import { subscriptionQueryKeys } from "../subscriptions/queries";
import { workspaceKeys } from "../workspace"; import { workspaceKeys } from "../workspace";
import { userKeys } from "./query-keys"; import { userKeys } from "./query-keys";
import { import {
@ -188,6 +189,9 @@ export const useAddUsersToOrg = () => {
}, },
onSuccess: (_, { organizationId, projects }) => { onSuccess: (_, { organizationId, projects }) => {
queryClient.invalidateQueries({ queryKey: userKeys.getOrgUsers(organizationId) }); queryClient.invalidateQueries({ queryKey: userKeys.getOrgUsers(organizationId) });
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
});
projects?.forEach((project) => { projects?.forEach((project) => {
if (project.slug) { if (project.slug) {

View File

@ -1,6 +1,5 @@
import { import {
faBook, faBook,
faCheckCircle,
faCog, faCog,
faCubes, faCubes,
faDoorClosed, faDoorClosed,
@ -100,18 +99,6 @@ export const OrgSidebar = ({ isHidden }: Props) => {
</MenuItem> </MenuItem>
)} )}
</Link> </Link>
<Link to="/organization/sso">
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCheckCircle} className="mr-4" />
</div>
SSO Settings
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/settings"> <Link to="/organization/settings">
{({ isActive }) => ( {({ isActive }) => (
<MenuItem isSelected={isActive}> <MenuItem isSelected={isActive}>

View File

@ -1,222 +0,0 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { FaGithub } from "react-icons/fa";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
Button,
FormControl,
Input,
TextArea
} from "@app/components/v2";
import { useToggle } from "@app/hooks";
import { useUpdateServerConfig } from "@app/hooks/api";
import { AdminIntegrationsConfig } from "@app/hooks/api/admin/types";
const gitHubAppFormSchema = z.object({
clientId: z.string(),
clientSecret: z.string(),
appSlug: z.string(),
appId: z.string(),
privateKey: z.string()
});
type TGitHubAppConnectionForm = z.infer<typeof gitHubAppFormSchema>;
type Props = {
adminIntegrationsConfig?: AdminIntegrationsConfig;
};
export const GitHubAppConnectionForm = ({ adminIntegrationsConfig }: Props) => {
const { mutateAsync: updateAdminServerConfig } = useUpdateServerConfig();
const [isGitHubAppClientSecretFocused, setIsGitHubAppClientSecretFocused] = useToggle();
const {
control,
handleSubmit,
setValue,
formState: { isSubmitting, isDirty }
} = useForm<TGitHubAppConnectionForm>({
resolver: zodResolver(gitHubAppFormSchema)
});
const onSubmit = async (data: TGitHubAppConnectionForm) => {
await updateAdminServerConfig({
gitHubAppConnectionClientId: data.clientId,
gitHubAppConnectionClientSecret: data.clientSecret,
gitHubAppConnectionSlug: data.appSlug,
gitHubAppConnectionId: data.appId,
gitHubAppConnectionPrivateKey: data.privateKey
});
createNotification({
text: "Updated GitHub app connection configuration. It can take up to 5 minutes to take effect.",
type: "success"
});
};
useEffect(() => {
if (adminIntegrationsConfig) {
setValue("clientId", adminIntegrationsConfig.gitHubAppConnection.clientId);
setValue("clientSecret", adminIntegrationsConfig.gitHubAppConnection.clientSecret);
setValue("appSlug", adminIntegrationsConfig.gitHubAppConnection.appSlug);
setValue("appId", adminIntegrationsConfig.gitHubAppConnection.appId);
setValue("privateKey", adminIntegrationsConfig.gitHubAppConnection.privateKey);
}
}, [adminIntegrationsConfig]);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="github-app-integration" className="data-[state=open]:border-none">
<AccordionTrigger className="flex h-fit w-full justify-start rounded-md border border-mineshaft-500 bg-mineshaft-700 px-4 py-6 text-sm transition-colors data-[state=open]:rounded-b-none">
<div className="text-md group order-1 ml-3 flex items-center gap-2">
<FaGithub className="text-lg group-hover:text-primary-400" />
<div className="text-[15px] font-semibold">GitHub App</div>
</div>
</AccordionTrigger>
<AccordionContent childrenClassName="px-0 py-0">
<div className="flex w-full flex-col justify-start rounded-md rounded-t-none border border-t-0 border-mineshaft-500 bg-mineshaft-700 px-4 py-4">
<div className="mb-2 max-w-lg text-sm text-mineshaft-300">
Step 1: Create and configure GitHub App. Please refer to the documentation below for
more information.
</div>
<div className="mb-6">
<a
href="https://infisical.com/docs/integrations/app-connections/github#self-hosted-instance"
target="_blank"
rel="noopener noreferrer"
>
<Button colorSchema="secondary">Documentation</Button>
</a>
</div>
<div className="mb-4 max-w-lg text-sm text-mineshaft-300">
Step 2: Configure your instance-wide settings to enable GitHub App connections. Copy
the credentials from your GitHub App&apos;s settings page.
</div>
<Controller
control={control}
name="clientId"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Client ID"
className="w-96"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
type="text"
onChange={(e) => field.onChange(e.target.value)}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="clientSecret"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Client Secret"
tooltipText="You can find your Client Secret in the GitHub App's settings under 'Client secrets'."
className="w-96"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
type={isGitHubAppClientSecretFocused ? "text" : "password"}
onFocus={() => setIsGitHubAppClientSecretFocused.on()}
onBlur={() => setIsGitHubAppClientSecretFocused.off()}
onChange={(e) => field.onChange(e.target.value)}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="appSlug"
render={({ field, fieldState: { error } }) => (
<FormControl
label="App Slug"
tooltipText="The GitHub App slug from the app's URL (e.g., 'my-app' from github.com/apps/my-app)."
className="w-96"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
type="text"
onChange={(e) => field.onChange(e.target.value)}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="appId"
render={({ field, fieldState: { error } }) => (
<FormControl
label="App ID"
tooltipText="The numeric App ID found in your GitHub App's settings."
className="w-96"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
type="text"
onChange={(e) => field.onChange(e.target.value)}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="privateKey"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Private Key"
tooltipText="The private key generated for your GitHub App (PEM format)."
className="w-96"
isError={Boolean(error)}
errorText={error?.message}
>
<TextArea
{...field}
value={field.value || ""}
className="min-h-32"
onChange={(e) => field.onChange(e.target.value)}
/>
</FormControl>
)}
/>
<div>
<Button
className="mt-2"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting || !isDirty}
>
Save
</Button>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</form>
);
};

View File

@ -5,23 +5,17 @@ import { ROUTE_PATHS } from "@app/const/routes";
import { useGetAdminIntegrationsConfig } from "@app/hooks/api"; import { useGetAdminIntegrationsConfig } from "@app/hooks/api";
import { AdminIntegrationsConfig } from "@app/hooks/api/admin/types"; import { AdminIntegrationsConfig } from "@app/hooks/api/admin/types";
import { GitHubAppConnectionForm } from "./GitHubAppConnectionForm";
import { MicrosoftTeamsIntegrationForm } from "./MicrosoftTeamsIntegrationForm"; import { MicrosoftTeamsIntegrationForm } from "./MicrosoftTeamsIntegrationForm";
import { SlackIntegrationForm } from "./SlackIntegrationForm"; import { SlackIntegrationForm } from "./SlackIntegrationForm";
enum IntegrationTabSections { enum IntegrationTabSections {
Workflow = "workflow", Workflow = "workflow"
AppConnections = "app-connections"
} }
interface WorkflowTabProps { interface WorkflowTabProps {
adminIntegrationsConfig: AdminIntegrationsConfig; adminIntegrationsConfig: AdminIntegrationsConfig;
} }
interface AppConnectionsTabProps {
adminIntegrationsConfig: AdminIntegrationsConfig;
}
const WorkflowTab = ({ adminIntegrationsConfig }: WorkflowTabProps) => ( const WorkflowTab = ({ adminIntegrationsConfig }: WorkflowTabProps) => (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<SlackIntegrationForm adminIntegrationsConfig={adminIntegrationsConfig} /> <SlackIntegrationForm adminIntegrationsConfig={adminIntegrationsConfig} />
@ -29,12 +23,6 @@ const WorkflowTab = ({ adminIntegrationsConfig }: WorkflowTabProps) => (
</div> </div>
); );
const AppConnectionsTab = ({ adminIntegrationsConfig }: AppConnectionsTabProps) => (
<div className="flex flex-col gap-2">
<GitHubAppConnectionForm adminIntegrationsConfig={adminIntegrationsConfig} />
</div>
);
export const IntegrationsPageForm = () => { export const IntegrationsPageForm = () => {
const { data: adminIntegrationsConfig } = useGetAdminIntegrationsConfig(); const { data: adminIntegrationsConfig } = useGetAdminIntegrationsConfig();
@ -59,11 +47,6 @@ export const IntegrationsPageForm = () => {
key: IntegrationTabSections.Workflow, key: IntegrationTabSections.Workflow,
label: "Workflows", label: "Workflows",
component: WorkflowTab component: WorkflowTab
},
{
key: IntegrationTabSections.AppConnections,
label: "App Connections",
component: AppConnectionsTab
} }
]; ];

View File

@ -39,10 +39,6 @@ export const OrgMembersSection = () => {
const { mutateAsync: deleteMutateAsync } = useDeleteOrgMembership(); const { mutateAsync: deleteMutateAsync } = useDeleteOrgMembership();
const { mutateAsync: updateOrgMembership } = useUpdateOrgMembership(); const { mutateAsync: updateOrgMembership } = useUpdateOrgMembership();
const isMoreUsersAllowed = subscription?.memberLimit
? subscription.membersUsed < subscription.memberLimit
: true;
const isMoreIdentitiesAllowed = subscription?.identityLimit const isMoreIdentitiesAllowed = subscription?.identityLimit
? subscription.identitiesUsed < subscription.identityLimit ? subscription.identitiesUsed < subscription.identityLimit
: true; : true;
@ -58,7 +54,7 @@ export const OrgMembersSection = () => {
return; return;
} }
if ((!isMoreUsersAllowed || !isMoreIdentitiesAllowed) && !isEnterprise) { if (!isMoreIdentitiesAllowed && !isEnterprise) {
handlePopUpOpen("upgradePlan", { handlePopUpOpen("upgradePlan", {
description: "You can add more members if you upgrade your Infisical plan." description: "You can add more members if you upgrade your Infisical plan."
}); });

View File

@ -1,6 +1,3 @@
import { Link } from "@tanstack/react-router";
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc"; import { withPermission } from "@app/hoc";
@ -11,21 +8,6 @@ export const OrgSecurityTab = withPermission(
() => { () => {
return ( return (
<> <>
<NoticeBannerV2
className="mx-auto mb-4"
titleClassName="text-base"
title="Single Sign-On (SSO) Settings"
>
<p className="mt-1 text-mineshaft-300">
SSO Settings have been relocated:{" "}
<Link
className="text-mineshaft-200 underline underline-offset-2"
to="/organization/sso"
>
Click here to view SSO Settings
</Link>
</p>
</NoticeBannerV2>
<OrgGenericAuthSection /> <OrgGenericAuthSection />
<OrgUserAccessTokenLimitSection /> <OrgUserAccessTokenLimitSection />
</> </>

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