Compare commits
29 Commits
create-pol
...
empty-secr
Author | SHA1 | Date | |
---|---|---|---|
f5b95fbe25 | |||
6df6f44b50 | |||
2f6c79beb6 | |||
b67fcad252 | |||
5a41862dc9 | |||
9fd0189dbb | |||
74fae78c31 | |||
1aa9be203e | |||
f9ef5cf930 | |||
16c89c6dbd | |||
e35ac599f8 | |||
782b6fce4a | |||
6d91297ca9 | |||
db369b8f51 | |||
a50a95ad6e | |||
4ec0031c42 | |||
a6edb67f58 | |||
97c96acea5 | |||
5e24015f2a | |||
f17e1f6699 | |||
e71b136859 | |||
79d80fad08 | |||
f58de53995 | |||
f85c045b09 | |||
6477a9f095 | |||
e3a7478acb | |||
4f348316e7 | |||
7d2d69fc7d | |||
0569c7e692 |
@ -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();
|
||||||
|
});
|
||||||
|
}
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
||||||
|
}
|
@ -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>;
|
||||||
|
@ -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(),
|
||||||
|
@ -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()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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>;
|
||||||
|
@ -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(),
|
||||||
|
@ -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()
|
||||||
});
|
});
|
||||||
|
@ -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({
|
||||||
|
@ -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)
|
||||||
}),
|
}),
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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({
|
||||||
|
@ -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" },
|
||||||
|
@ -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 {
|
||||||
|
@ -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({
|
||||||
|
@ -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,
|
||||||
|
@ -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 }
|
||||||
|
@ -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: [
|
||||||
{
|
{
|
||||||
|
@ -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({
|
||||||
|
@ -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.",
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
});
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
]);
|
]);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -28,3 +28,7 @@ export type TCloudflarePagesProject = {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TCloudflareWorkersScript = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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({
|
||||||
|
@ -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
|
||||||
|
};
|
@ -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}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -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)
|
||||||
|
});
|
@ -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;
|
||||||
|
};
|
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./cloudflare-workers-constants";
|
||||||
|
export * from "./cloudflare-workers-fns";
|
||||||
|
export * from "./cloudflare-workers-schemas";
|
||||||
|
export * from "./cloudflare-workers-types";
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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"]
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/cloudflare-workers"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/cloudflare-workers/{syncId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/cloudflare-workers/{syncId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/cloudflare-workers/sync-name/{syncName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/cloudflare-workers"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/cloudflare-workers/{syncId}/remove-secrets"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/cloudflare-workers/{syncId}/sync-secrets"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/cloudflare-workers/{syncId}"
|
||||||
|
---
|
@ -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": [
|
||||||
|
After Width: | Height: | Size: 325 KiB |
After Width: | Height: | Size: 966 KiB |
After Width: | Height: | Size: 578 KiB |
After Width: | Height: | Size: 575 KiB |
After Width: | Height: | Size: 624 KiB |
After Width: | Height: | Size: 598 KiB |
After Width: | Height: | Size: 558 KiB |
After Width: | Height: | Size: 637 KiB |
@ -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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
|
128
docs/integrations/secret-syncs/cloudflare-workers.mdx
Normal 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.
|
||||||
|

|
||||||
|
|
||||||
|
2. Select the **Cloudflare Workers** option.
|
||||||
|

|
||||||
|
|
||||||
|
3. Configure the **Source** from where secrets should be retrieved, then click **Next**.
|
||||||
|

|
||||||
|
|
||||||
|
- **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**.
|
||||||
|

|
||||||
|
|
||||||
|
- **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**.
|
||||||
|

|
||||||
|
|
||||||
|
- **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**.
|
||||||
|

|
||||||
|
|
||||||
|
- **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**.
|
||||||
|

|
||||||
|
|
||||||
|
8. If enabled, your Cloudflare Workers Sync will begin syncing your secrets to the destination endpoint.
|
||||||
|

|
||||||
|
|
||||||
|
</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>
|
@ -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..."
|
||||||
|
@ -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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -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:
|
||||||
|
@ -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;
|
||||||
|
@ -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>;
|
||||||
|
};
|
@ -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;
|
||||||
|
@ -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")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
@ -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
|
||||||
]);
|
]);
|
||||||
|
@ -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/"
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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 }[];
|
||||||
|
@ -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
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
export type TCloudflareProject = {
|
export type TCloudflarePagesProject = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TCloudflareWorkersScript = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
@ -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)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
@ -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;
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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}>
|
||||||
|
@ -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'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>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -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."
|
||||||
});
|
});
|
||||||
|
@ -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 />
|
||||||
</>
|
</>
|
||||||
|