mirror of
https://github.com/Infisical/infisical.git
synced 2025-09-07 10:22:29 +00:00
Compare commits
5 Commits
daniel/ope
...
migrate-ap
Author | SHA1 | Date | |
---|---|---|---|
|
84569b012d | ||
|
f4dcb44da0 | ||
|
37c3b5d42a | ||
|
ef6d0108d2 | ||
|
72e47f9eb3 |
@@ -94,7 +94,8 @@ const createOracleDBAppConnection = async (credentials: TGenericSqlCredentials)
|
||||
description: "Test OracleDB App Connection",
|
||||
gatewayId: null,
|
||||
isPlatformManagedCredentials: false,
|
||||
method: "username-and-password"
|
||||
method: "username-and-password",
|
||||
projectId: seedData1.projectV3.id
|
||||
};
|
||||
|
||||
const res = await testServer.inject({
|
||||
@@ -120,6 +121,7 @@ const createMySQLAppConnection = async (credentials: TGenericSqlCredentials) =>
|
||||
description: "test-mysql",
|
||||
gatewayId: null,
|
||||
method: "username-and-password",
|
||||
projectId: seedData1.projectV3.id,
|
||||
credentials: {
|
||||
host: credentials.host,
|
||||
port: credentials.port,
|
||||
@@ -162,7 +164,8 @@ const createPostgresAppConnection = async (credentials: TGenericSqlCredentials)
|
||||
name: `postgres-test-${uuidv4()}`,
|
||||
description: "test-postgres",
|
||||
gatewayId: null,
|
||||
method: "username-and-password"
|
||||
method: "username-and-password",
|
||||
projectId: seedData1.projectV3.id
|
||||
};
|
||||
|
||||
const res = await testServer.inject({
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
@@ -13,9 +14,7 @@ export async function up(knex: Knex): Promise<void> {
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||
t.dropUnique(["orgId", "name"]);
|
||||
});
|
||||
await dropConstraintIfExists(TableName.AppConnection, "app_connections_orgid_name_unique", knex);
|
||||
|
||||
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||
t.dropUnique(["projectId", "name"]);
|
||||
|
@@ -0,0 +1,30 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.AppConnection)) {
|
||||
// we can't add the constraint back after up since there may be conflicting names so we do if exists
|
||||
await dropConstraintIfExists(TableName.AppConnection, "app_connections_orgid_name_unique", knex);
|
||||
|
||||
if (!(await knex.schema.hasColumn(TableName.AppConnection, "projectId"))) {
|
||||
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||
t.string("projectId").nullable();
|
||||
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||
t.unique(["name", "projectId"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.AppConnection)) {
|
||||
if (await knex.schema.hasColumn(TableName.AppConnection, "projectId")) {
|
||||
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||
t.dropUnique(["name", "projectId"]);
|
||||
t.dropColumn("projectId");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,7 +21,8 @@ export const AppConnectionsSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
isPlatformManagedCredentials: z.boolean().default(false).nullable().optional(),
|
||||
gatewayId: z.string().uuid().nullable().optional()
|
||||
gatewayId: z.string().uuid().nullable().optional(),
|
||||
projectId: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export type TAppConnections = z.infer<typeof AppConnectionsSchema>;
|
||||
|
@@ -390,6 +390,8 @@ export enum EventType {
|
||||
CREATE_APP_CONNECTION = "create-app-connection",
|
||||
UPDATE_APP_CONNECTION = "update-app-connection",
|
||||
DELETE_APP_CONNECTION = "delete-app-connection",
|
||||
GET_APP_CONNECTION_USAGE = "get-app-connection-usage",
|
||||
MIGRATE_APP_CONNECTION = "migrate-app-connection",
|
||||
CREATE_SHARED_SECRET = "create-shared-secret",
|
||||
CREATE_SECRET_REQUEST = "create-secret-request",
|
||||
DELETE_SHARED_SECRET = "delete-shared-secret",
|
||||
@@ -2756,14 +2758,31 @@ interface GetAppConnectionEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetAppConnectionUsageEvent {
|
||||
type: EventType.GET_APP_CONNECTION_USAGE;
|
||||
metadata: {
|
||||
connectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface MigrateAppConnectionEvent {
|
||||
type: EventType.MIGRATE_APP_CONNECTION;
|
||||
metadata: {
|
||||
connectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateAppConnectionEvent {
|
||||
type: EventType.CREATE_APP_CONNECTION;
|
||||
metadata: Omit<TCreateAppConnectionDTO, "credentials"> & { connectionId: string };
|
||||
metadata: Omit<TCreateAppConnectionDTO, "credentials" | "projectId"> & { connectionId: string };
|
||||
}
|
||||
|
||||
interface UpdateAppConnectionEvent {
|
||||
type: EventType.UPDATE_APP_CONNECTION;
|
||||
metadata: Omit<TUpdateAppConnectionDTO, "credentials"> & { connectionId: string; credentialsUpdated: boolean };
|
||||
metadata: Omit<TUpdateAppConnectionDTO, "credentials" | "projectId"> & {
|
||||
connectionId: string;
|
||||
credentialsUpdated: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteAppConnectionEvent {
|
||||
@@ -3670,6 +3689,8 @@ export type Event =
|
||||
| CreateAppConnectionEvent
|
||||
| UpdateAppConnectionEvent
|
||||
| DeleteAppConnectionEvent
|
||||
| GetAppConnectionUsageEvent
|
||||
| MigrateAppConnectionEvent
|
||||
| GetSshHostGroupEvent
|
||||
| CreateSshHostGroupEvent
|
||||
| UpdateSshHostGroupEvent
|
||||
|
@@ -2,6 +2,7 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability"
|
||||
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionAppConnectionActions,
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionCmekActions,
|
||||
ProjectPermissionCommitsActions,
|
||||
@@ -253,6 +254,17 @@ const buildAdminPermissionRules = () => {
|
||||
ProjectPermissionSub.SecretScanningConfigs
|
||||
);
|
||||
|
||||
can(
|
||||
[
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionAppConnectionActions.Edit,
|
||||
ProjectPermissionAppConnectionActions.Delete,
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
ProjectPermissionAppConnectionActions.Connect
|
||||
],
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
@@ -457,6 +469,8 @@ const buildMemberPermissionRules = () => {
|
||||
|
||||
can([ProjectPermissionSecretScanningConfigActions.Read], ProjectPermissionSub.SecretScanningConfigs);
|
||||
|
||||
can(ProjectPermissionAppConnectionActions.Connect, ProjectPermissionSub.AppConnections);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
|
@@ -148,6 +148,14 @@ export enum ProjectPermissionSecretScanningDataSourceActions {
|
||||
ReadResources = "read-data-source-resources"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionAppConnectionActions {
|
||||
Read = "read-app-connections",
|
||||
Create = "create-app-connections",
|
||||
Edit = "edit-app-connections",
|
||||
Delete = "delete-app-connections",
|
||||
Connect = "connect-app-connections"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSecretScanningFindingActions {
|
||||
Read = "read-findings",
|
||||
Update = "update-findings"
|
||||
@@ -197,7 +205,8 @@ export enum ProjectPermissionSub {
|
||||
Kmip = "kmip",
|
||||
SecretScanningDataSources = "secret-scanning-data-sources",
|
||||
SecretScanningFindings = "secret-scanning-findings",
|
||||
SecretScanningConfigs = "secret-scanning-configs"
|
||||
SecretScanningConfigs = "secret-scanning-configs",
|
||||
AppConnections = "app-connections"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -255,6 +264,10 @@ export type PkiSubscriberSubjectFields = {
|
||||
// (dangtony98): consider adding [commonName] as a subject field in the future
|
||||
};
|
||||
|
||||
export type AppConnectionSubjectFields = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type ProjectPermissionSet =
|
||||
| [
|
||||
ProjectPermissionSecretActions,
|
||||
@@ -344,7 +357,14 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionCommitsActions, ProjectPermissionSub.Commits]
|
||||
| [ProjectPermissionSecretScanningDataSourceActions, ProjectPermissionSub.SecretScanningDataSources]
|
||||
| [ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub.SecretScanningFindings]
|
||||
| [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs];
|
||||
| [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs]
|
||||
| [
|
||||
ProjectPermissionAppConnectionActions,
|
||||
(
|
||||
| ProjectPermissionSub.AppConnections
|
||||
| (ForcedSubject<ProjectPermissionSub.AppConnections> & AppConnectionSubjectFields)
|
||||
)
|
||||
];
|
||||
|
||||
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
|
||||
const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([
|
||||
@@ -559,6 +579,21 @@ const PkiTemplateConditionSchema = z
|
||||
})
|
||||
.partial();
|
||||
|
||||
const AppConnectionConditionSchema = z
|
||||
.object({
|
||||
connectionId: z.union([
|
||||
z.string(),
|
||||
z
|
||||
.object({
|
||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||
})
|
||||
.partial()
|
||||
])
|
||||
})
|
||||
.partial();
|
||||
|
||||
const GeneralPermissionSchema = [
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||
@@ -739,6 +774,16 @@ const GeneralPermissionSchema = [
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretScanningConfigActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.AppConnections).describe("The entity this permission pertains to."),
|
||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionAppConnectionActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
),
|
||||
conditions: AppConnectionConditionSchema.describe(
|
||||
"When specified, only matching conditions will be allowed to access given resource."
|
||||
).optional()
|
||||
})
|
||||
];
|
||||
|
||||
|
@@ -175,7 +175,8 @@ export const ldapPasswordRotationFactory: TRotationFactory<
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: connection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(connection.id, { encryptedCredentials });
|
||||
|
@@ -52,6 +52,7 @@ const baseSecretRotationV2Query = ({
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -106,6 +107,7 @@ const expandSecretRotation = <T extends Awaited<ReturnType<typeof baseSecretRota
|
||||
connectionUpdatedAt,
|
||||
connectionVersion,
|
||||
connectionGatewayId,
|
||||
connectionProjectId,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
...el
|
||||
} = secretRotation;
|
||||
@@ -126,6 +128,7 @@ const expandSecretRotation = <T extends Awaited<ReturnType<typeof baseSecretRota
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
gatewayId: connectionGatewayId,
|
||||
projectId: connectionProjectId,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
||||
},
|
||||
folder: {
|
||||
|
@@ -50,6 +50,7 @@ const baseSecretScanningDataSourceQuery = ({
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -84,6 +85,7 @@ const expandSecretScanningDataSource = <
|
||||
connectionVersion,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
connectionGatewayId,
|
||||
connectionProjectId,
|
||||
...el
|
||||
} = dataSource;
|
||||
|
||||
@@ -103,7 +105,8 @@ const expandSecretScanningDataSource = <
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||
gatewayId: connectionGatewayId
|
||||
gatewayId: connectionGatewayId,
|
||||
projectId: connectionProjectId
|
||||
}
|
||||
: undefined
|
||||
};
|
||||
|
@@ -2170,6 +2170,9 @@ export const CertificateAuthorities = {
|
||||
};
|
||||
|
||||
export const AppConnections = {
|
||||
LIST: (app?: AppConnection) => ({
|
||||
projectId: `The ID of the project to list ${app ? APP_CONNECTION_NAME_MAP[app] : "App"} Connections from.`
|
||||
}),
|
||||
GET_BY_ID: (app: AppConnection) => ({
|
||||
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.`
|
||||
}),
|
||||
@@ -2183,7 +2186,8 @@ export const AppConnections = {
|
||||
description: `An optional description for the ${appName} Connection.`,
|
||||
credentials: `The credentials used to connect with ${appName}.`,
|
||||
method: `The method used to authenticate with ${appName}.`,
|
||||
isPlatformManagedCredentials: `Whether or not the ${appName} Connection credentials should be managed by Infisical. Once enabled this cannot be reversed.`
|
||||
isPlatformManagedCredentials: `Whether or not the ${appName} Connection credentials should be managed by Infisical. Once enabled this cannot be reversed.`,
|
||||
projectId: `The ID of the project to create the ${appName} Connection in.`
|
||||
};
|
||||
},
|
||||
UPDATE: (app: AppConnection) => {
|
||||
|
@@ -1758,7 +1758,12 @@ export const registerRoutes = async (
|
||||
kmsService,
|
||||
licenseService,
|
||||
gatewayService,
|
||||
gatewayDAL
|
||||
gatewayDAL,
|
||||
projectDAL,
|
||||
secretSyncDAL,
|
||||
secretRotationV2DAL,
|
||||
externalCertificateAuthorityDAL,
|
||||
secretScanningV2DAL
|
||||
});
|
||||
|
||||
const secretSyncService = secretSyncServiceFactory({
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectType } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ApiDocsTags, AppConnections } from "@app/lib/api-docs";
|
||||
import { startsWithVowel } from "@app/lib/fn";
|
||||
@@ -26,6 +27,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
description?: string | null;
|
||||
isPlatformManagedCredentials?: boolean;
|
||||
gatewayId?: string | null;
|
||||
projectId: string;
|
||||
}>;
|
||||
updateSchema: z.ZodType<{
|
||||
name?: string;
|
||||
@@ -47,18 +49,27 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: `List the ${appName} Connections for the current organization.`,
|
||||
description: `List the ${appName} Connections for the current organization or project.`,
|
||||
querystring: z.object({
|
||||
projectId: z.string().optional().describe(AppConnections.LIST(app).projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({ appConnections: sanitizedResponseSchema.array() })
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const appConnections = (await server.services.appConnection.listAppConnectionsByOrg(req.permission, app)) as T[];
|
||||
const { projectId } = req.query;
|
||||
const appConnections = (await server.services.appConnection.listAppConnections(
|
||||
req.permission,
|
||||
app,
|
||||
projectId
|
||||
)) as T[];
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTIONS,
|
||||
metadata: {
|
||||
@@ -82,14 +93,19 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: `List the ${appName} Connections the current user has permission to establish connections with.`,
|
||||
description: `List the ${appName} Connections the current user has permission to establish connections within this project.`,
|
||||
querystring: z.object({
|
||||
projectId: z.string().describe(AppConnections.LIST(app).projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
appConnections: z
|
||||
.object({
|
||||
app: z.literal(app),
|
||||
name: z.string(),
|
||||
id: z.string().uuid()
|
||||
id: z.string().uuid(),
|
||||
projectId: z.string().nullish(),
|
||||
orgId: z.string()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
@@ -97,14 +113,17 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { projectId } = req.query;
|
||||
const appConnections = await server.services.appConnection.listAvailableAppConnectionsForUser(
|
||||
app,
|
||||
req.permission
|
||||
req.permission,
|
||||
projectId
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS,
|
||||
metadata: {
|
||||
@@ -149,6 +168,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: appConnection.projectId ?? undefined,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -195,6 +215,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: appConnection.projectId ?? undefined,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -216,9 +237,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: `Create ${
|
||||
startsWithVowel(appName) ? "an" : "a"
|
||||
} ${appName} Connection for the current organization.`,
|
||||
description: `Create ${startsWithVowel(appName) ? "an" : "a"} ${appName} Connection for the specified project.`,
|
||||
body: createSchema,
|
||||
response: {
|
||||
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||
@@ -226,16 +245,17 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { name, method, credentials, description, isPlatformManagedCredentials, gatewayId } = req.body;
|
||||
const { name, method, credentials, description, isPlatformManagedCredentials, gatewayId, projectId } = req.body;
|
||||
|
||||
const appConnection = (await server.services.appConnection.createAppConnection(
|
||||
{ name, method, app, credentials, description, isPlatformManagedCredentials, gatewayId },
|
||||
{ name, method, app, credentials, description, isPlatformManagedCredentials, gatewayId, projectId },
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.CREATE_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -283,6 +303,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: appConnection.projectId ?? undefined,
|
||||
event: {
|
||||
type: EventType.UPDATE_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -329,6 +350,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: appConnection.projectId ?? undefined,
|
||||
event: {
|
||||
type: EventType.DELETE_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -340,4 +362,116 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
return { appConnection };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: `/:connectionId/usage`,
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
hide: true, // scott: we could expose this in the future but just for UI right now
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
params: z.object({
|
||||
connectionId: z.string().uuid()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
projects: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
type: z.nativeEnum(ProjectType),
|
||||
slug: z.string(),
|
||||
resources: z.object({
|
||||
secretSyncs: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
.array(),
|
||||
secretRotations: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
.array(),
|
||||
externalCas: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
.array(),
|
||||
dataSources: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
})
|
||||
.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { connectionId } = req.params;
|
||||
|
||||
const projects = await server.services.appConnection.findAppConnectionUsageById(
|
||||
app,
|
||||
connectionId,
|
||||
req.permission
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTION_USAGE,
|
||||
metadata: {
|
||||
connectionId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { projects };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:connectionId/migrate",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
hide: true,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
params: z.object({
|
||||
connectionId: z.string().uuid()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { connectionId } = req.params;
|
||||
const appConnection = await server.services.appConnection.migrateAppConnection(app, connectionId, req.permission);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
event: {
|
||||
type: EventType.MIGRATE_APP_CONNECTION,
|
||||
metadata: {
|
||||
connectionId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { appConnection };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectType } from "@app/db/schemas";
|
||||
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
|
||||
import {
|
||||
OracleDBConnectionListItemSchema,
|
||||
SanitizedOracleDBConnectionSchema
|
||||
} from "@app/ee/services/app-connections/oracledb";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ApiDocsTags } from "@app/lib/api-docs";
|
||||
import { ApiDocsTags, AppConnections } from "@app/lib/api-docs";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import {
|
||||
@@ -204,6 +205,9 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: "List the available App Connection Options.",
|
||||
querystring: z.object({
|
||||
projectType: z.nativeEnum(ProjectType).optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
appConnectionOptions: AppConnectionOptionsSchema.array()
|
||||
@@ -211,8 +215,8 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: () => {
|
||||
const appConnectionOptions = server.services.appConnection.listAppConnectionOptions();
|
||||
handler: (req) => {
|
||||
const appConnectionOptions = server.services.appConnection.listAppConnectionOptions(req.query.projectType);
|
||||
return { appConnectionOptions };
|
||||
}
|
||||
});
|
||||
@@ -226,18 +230,27 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: "List all the App Connections for the current organization.",
|
||||
description: "List all the App Connections for the current organization or project.",
|
||||
querystring: z.object({
|
||||
projectId: z.string().optional().describe(AppConnections.LIST().projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({ appConnections: SanitizedAppConnectionSchema.array() })
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const appConnections = await server.services.appConnection.listAppConnectionsByOrg(req.permission);
|
||||
const { projectId } = req.query;
|
||||
const appConnections = await server.services.appConnection.listAppConnections(
|
||||
req.permission,
|
||||
undefined,
|
||||
projectId
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTIONS,
|
||||
metadata: {
|
||||
|
@@ -1,11 +1,81 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { transformUsageToProjects } from "@app/services/app-connection/app-connection-fns";
|
||||
|
||||
export type TAppConnectionDALFactory = ReturnType<typeof appConnectionDALFactory>;
|
||||
|
||||
export const appConnectionDALFactory = (db: TDbClient) => {
|
||||
const appConnectionOrm = ormify(db, TableName.AppConnection);
|
||||
|
||||
return { ...appConnectionOrm };
|
||||
const findAppConnectionUsageById = async (connectionId: string, tx?: Knex) => {
|
||||
const secretSyncs = await (tx || db.replicaNode())(TableName.SecretSync)
|
||||
.where(`${TableName.SecretSync}.connectionId`, connectionId)
|
||||
.join(TableName.Project, `${TableName.SecretSync}.projectId`, `${TableName.Project}.id`)
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.SecretSync),
|
||||
db.ref("id").withSchema(TableName.SecretSync),
|
||||
db.ref("projectId").withSchema(TableName.SecretSync),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("slug").as("projectSlug").withSchema(TableName.Project),
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project)
|
||||
);
|
||||
|
||||
const secretRotations = await (tx || db.replicaNode())(TableName.SecretRotationV2)
|
||||
.where(`${TableName.SecretRotationV2}.connectionId`, connectionId)
|
||||
.join(TableName.SecretFolder, `${TableName.SecretRotationV2}.folderId`, `${TableName.SecretFolder}.id`)
|
||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||
.join(TableName.Project, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.SecretRotationV2),
|
||||
db.ref("id").withSchema(TableName.SecretRotationV2),
|
||||
db.ref("id").as("projectId").withSchema(TableName.Project),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("slug").as("projectSlug").withSchema(TableName.Project),
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project)
|
||||
);
|
||||
|
||||
const externalCas = await (tx || db.replicaNode())(TableName.ExternalCertificateAuthority)
|
||||
.where(`${TableName.ExternalCertificateAuthority}.appConnectionId`, connectionId)
|
||||
.orWhere(`${TableName.ExternalCertificateAuthority}.dnsAppConnectionId`, connectionId)
|
||||
.join(
|
||||
TableName.CertificateAuthority,
|
||||
`${TableName.ExternalCertificateAuthority}.caId`,
|
||||
`${TableName.CertificateAuthority}.id`
|
||||
)
|
||||
.join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`)
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.CertificateAuthority),
|
||||
db.ref("id").withSchema(TableName.ExternalCertificateAuthority),
|
||||
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority),
|
||||
db.ref("dnsAppConnectionId").withSchema(TableName.ExternalCertificateAuthority),
|
||||
db.ref("id").as("projectId").withSchema(TableName.Project),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("slug").as("projectSlug").withSchema(TableName.Project),
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project)
|
||||
);
|
||||
|
||||
const dataSources = await (tx || db.replicaNode())(TableName.SecretScanningDataSource)
|
||||
.where(`${TableName.SecretScanningDataSource}.connectionId`, connectionId)
|
||||
.join(TableName.Project, `${TableName.SecretScanningDataSource}.projectId`, `${TableName.Project}.id`)
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.SecretScanningDataSource),
|
||||
db.ref("id").withSchema(TableName.SecretScanningDataSource),
|
||||
db.ref("id").as("projectId").withSchema(TableName.Project),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("slug").as("projectSlug").withSchema(TableName.Project),
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project)
|
||||
);
|
||||
|
||||
return transformUsageToProjects({
|
||||
secretSyncs,
|
||||
secretRotations,
|
||||
dataSources,
|
||||
externalCas
|
||||
});
|
||||
};
|
||||
|
||||
return { ...appConnectionOrm, findAppConnectionUsageById };
|
||||
};
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { ProjectType } from "@app/db/schemas";
|
||||
import { TAppConnections } from "@app/db/schemas/app-connections";
|
||||
import {
|
||||
getOCIConnectionListItem,
|
||||
@@ -7,6 +8,8 @@ import {
|
||||
import { getOracleDBConnectionListItem, OracleDBConnectionMethod } from "@app/ee/services/app-connections/oracledb";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { SECRET_ROTATION_CONNECTION_MAP } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-maps";
|
||||
import { SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-maps";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "@app/services/app-connection/app-connection-maps";
|
||||
@@ -15,6 +18,7 @@ import {
|
||||
validateSqlConnectionCredentials
|
||||
} from "@app/services/app-connection/shared/sql";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
import { SECRET_SYNC_CONNECTION_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
||||
|
||||
import {
|
||||
getOnePassConnectionListItem,
|
||||
@@ -127,7 +131,19 @@ import {
|
||||
} from "./windmill";
|
||||
import { getZabbixConnectionListItem, validateZabbixConnectionCredentials, ZabbixConnectionMethod } from "./zabbix";
|
||||
|
||||
export const listAppConnectionOptions = () => {
|
||||
const SECRET_SYNC_APP_CONNECTION_MAP = Object.fromEntries(
|
||||
Object.entries(SECRET_SYNC_CONNECTION_MAP).map(([key, value]) => [value, key])
|
||||
);
|
||||
|
||||
const SECRET_ROTATION_APP_CONNECTION_MAP = Object.fromEntries(
|
||||
Object.entries(SECRET_ROTATION_CONNECTION_MAP).map(([key, value]) => [value, key])
|
||||
);
|
||||
|
||||
const SECRET_SCANNING_APP_CONNECTION_MAP = Object.fromEntries(
|
||||
Object.entries(SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP).map(([key, value]) => [value, key])
|
||||
);
|
||||
|
||||
export const listAppConnectionOptions = (projectType?: ProjectType) => {
|
||||
return [
|
||||
getAwsConnectionListItem(),
|
||||
getGitHubConnectionListItem(),
|
||||
@@ -166,22 +182,51 @@ export const listAppConnectionOptions = () => {
|
||||
getDigitalOceanConnectionListItem(),
|
||||
getNetlifyConnectionListItem(),
|
||||
getOktaConnectionListItem()
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
]
|
||||
.filter((option) => {
|
||||
switch (projectType) {
|
||||
case ProjectType.SecretManager:
|
||||
return (
|
||||
Boolean(SECRET_SYNC_APP_CONNECTION_MAP[option.app]) ||
|
||||
Boolean(SECRET_ROTATION_APP_CONNECTION_MAP[option.app])
|
||||
);
|
||||
case ProjectType.SecretScanning:
|
||||
return Boolean(SECRET_SCANNING_APP_CONNECTION_MAP[option.app]);
|
||||
case ProjectType.CertificateManager:
|
||||
return option.app === AppConnection.AWS || option.app === AppConnection.Cloudflare;
|
||||
case ProjectType.KMS:
|
||||
return false;
|
||||
case ProjectType.SSH:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
export const encryptAppConnectionCredentials = async ({
|
||||
orgId,
|
||||
credentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
}: {
|
||||
orgId: string;
|
||||
credentials: TAppConnection["credentials"];
|
||||
kmsService: TAppConnectionServiceFactoryDep["kmsService"];
|
||||
projectId: string | null | undefined;
|
||||
}) => {
|
||||
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
});
|
||||
const { encryptor } = await kmsService.createCipherPairWithDataKey(
|
||||
projectId
|
||||
? {
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
}
|
||||
: {
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
}
|
||||
);
|
||||
|
||||
const { cipherTextBlob: encryptedCredentialsBlob } = encryptor({
|
||||
plainText: Buffer.from(JSON.stringify(credentials))
|
||||
@@ -193,16 +238,22 @@ export const encryptAppConnectionCredentials = async ({
|
||||
export const decryptAppConnectionCredentials = async ({
|
||||
orgId,
|
||||
encryptedCredentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
}: {
|
||||
orgId: string;
|
||||
encryptedCredentials: Buffer;
|
||||
kmsService: TAppConnectionServiceFactoryDep["kmsService"];
|
||||
projectId: string | null | undefined;
|
||||
}) => {
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
});
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey(
|
||||
projectId
|
||||
? { type: KmsDataKey.SecretManager, projectId }
|
||||
: {
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
}
|
||||
);
|
||||
|
||||
const decryptedPlainTextBlob = decryptor({
|
||||
cipherTextBlob: encryptedCredentials
|
||||
@@ -333,6 +384,7 @@ export const decryptAppConnection = async (
|
||||
credentials: await decryptAppConnectionCredentials({
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
projectId: appConnection.projectId,
|
||||
kmsService
|
||||
}),
|
||||
credentialsHash: crypto.nativeCrypto.createHash("sha256").update(appConnection.encryptedCredentials).digest("hex")
|
||||
@@ -402,3 +454,73 @@ export const enterpriseAppCheck = async (
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
type Resource = {
|
||||
name: string;
|
||||
id: string;
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
projectSlug: string;
|
||||
projectType: string;
|
||||
};
|
||||
|
||||
type UsageData = {
|
||||
secretSyncs: Resource[];
|
||||
secretRotations: Resource[];
|
||||
dataSources: Resource[];
|
||||
externalCas: Resource[];
|
||||
};
|
||||
|
||||
type ResourceSummary = {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
type ProjectWithResources = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
type: ProjectType;
|
||||
resources: {
|
||||
secretSyncs: ResourceSummary[];
|
||||
secretRotations: ResourceSummary[];
|
||||
dataSources: ResourceSummary[];
|
||||
externalCas: (ResourceSummary & { appConnectionId?: string; dnsAppConnectionId?: string })[];
|
||||
};
|
||||
};
|
||||
|
||||
export const transformUsageToProjects = (data: UsageData): ProjectWithResources[] => {
|
||||
const projectMap = new Map<string, ProjectWithResources>();
|
||||
|
||||
Object.entries(data).forEach(([resourceType, resources]) => {
|
||||
resources.forEach((resource) => {
|
||||
const { projectId, projectName, projectSlug, projectType, name, id, ...rest } = resource;
|
||||
|
||||
const projectKey = projectId;
|
||||
|
||||
if (!projectMap.has(projectKey)) {
|
||||
projectMap.set(projectKey, {
|
||||
id: projectId,
|
||||
name: projectName,
|
||||
slug: projectSlug,
|
||||
type: projectType as ProjectType,
|
||||
resources: {
|
||||
secretSyncs: [],
|
||||
secretRotations: [],
|
||||
dataSources: [],
|
||||
externalCas: []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const project = projectMap.get(projectKey)!;
|
||||
project.resources[resourceType as keyof ProjectWithResources["resources"]].push({
|
||||
name,
|
||||
id,
|
||||
...rest
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(projectMap.values());
|
||||
};
|
||||
|
@@ -28,6 +28,7 @@ export const GenericCreateAppConnectionFieldsSchema = (
|
||||
.max(256, "Description cannot exceed 256 characters")
|
||||
.nullish()
|
||||
.describe(AppConnections.CREATE(app).description),
|
||||
projectId: z.string().describe(AppConnections.CREATE(app).projectId),
|
||||
isPlatformManagedCredentials: supportsPlatformManagedCredentials
|
||||
? z.boolean().optional().default(false).describe(AppConnections.CREATE(app).isPlatformManagedCredentials)
|
||||
: z
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType, OrgMembershipRole, TAppConnections } from "@app/db/schemas";
|
||||
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
|
||||
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
|
||||
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
|
||||
@@ -12,6 +13,12 @@ import {
|
||||
OrgPermissionSubjects
|
||||
} from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionAppConnectionActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { TSecretRotationV2DALFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-dal";
|
||||
import { TSecretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||
@@ -25,9 +32,10 @@ import {
|
||||
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
|
||||
validateAppConnectionCredentials
|
||||
} from "@app/services/app-connection/app-connection-fns";
|
||||
import { auth0ConnectionService } from "@app/services/app-connection/auth0/auth0-connection-service";
|
||||
import { githubRadarConnectionService } from "@app/services/app-connection/github-radar/github-radar-connection-service";
|
||||
import { TExternalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/external-certificate-authority-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TSecretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
|
||||
|
||||
import { ValidateOnePassConnectionCredentialsSchema } from "./1password";
|
||||
import { onePassConnectionService } from "./1password/1password-connection-service";
|
||||
@@ -43,6 +51,7 @@ import {
|
||||
TValidateAppConnectionCredentialsSchema
|
||||
} from "./app-connection-types";
|
||||
import { ValidateAuth0ConnectionCredentialsSchema } from "./auth0";
|
||||
import { auth0ConnectionService } from "./auth0/auth0-connection-service";
|
||||
import { ValidateAwsConnectionCredentialsSchema } from "./aws";
|
||||
import { awsConnectionService } from "./aws/aws-connection-service";
|
||||
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
|
||||
@@ -70,6 +79,7 @@ import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
||||
import { ValidateGitHubConnectionCredentialsSchema } from "./github";
|
||||
import { githubConnectionService } from "./github/github-connection-service";
|
||||
import { ValidateGitHubRadarConnectionCredentialsSchema } from "./github-radar";
|
||||
import { githubRadarConnectionService } from "./github-radar/github-radar-connection-service";
|
||||
import { ValidateGitLabConnectionCredentialsSchema } from "./gitlab";
|
||||
import { gitlabConnectionService } from "./gitlab/gitlab-connection-service";
|
||||
import { ValidateHCVaultConnectionCredentialsSchema } from "./hc-vault";
|
||||
@@ -105,11 +115,16 @@ import { zabbixConnectionService } from "./zabbix/zabbix-connection-service";
|
||||
|
||||
export type TAppConnectionServiceFactoryDep = {
|
||||
appConnectionDAL: TAppConnectionDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayDAL: Pick<TGatewayDALFactory, "find">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectById">;
|
||||
secretSyncDAL: Pick<TSecretSyncDALFactory, "update">;
|
||||
secretRotationV2DAL: Pick<TSecretRotationV2DALFactory, "update">;
|
||||
secretScanningV2DAL: Pick<TSecretScanningV2DALFactory, "dataSources">;
|
||||
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "update">;
|
||||
};
|
||||
|
||||
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
|
||||
@@ -160,29 +175,69 @@ export const appConnectionServiceFactory = ({
|
||||
kmsService,
|
||||
licenseService,
|
||||
gatewayService,
|
||||
gatewayDAL
|
||||
gatewayDAL,
|
||||
projectDAL,
|
||||
secretSyncDAL,
|
||||
secretRotationV2DAL,
|
||||
secretScanningV2DAL,
|
||||
externalCertificateAuthorityDAL
|
||||
}: TAppConnectionServiceFactoryDep) => {
|
||||
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
const listAppConnections = async (actor: OrgServiceActor, app?: AppConnection, projectId?: string) => {
|
||||
let appConnections: TAppConnections[];
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
if (projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
const appConnections = await appConnectionDAL.find(
|
||||
app
|
||||
? { orgId: actor.orgId, app }
|
||||
: {
|
||||
orgId: actor.orgId
|
||||
}
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
appConnections = (
|
||||
await appConnectionDAL.find({
|
||||
projectId,
|
||||
...(app ? { app } : {})
|
||||
})
|
||||
).filter((appConnection) =>
|
||||
permission.can(
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id })
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
|
||||
appConnections = (
|
||||
await appConnectionDAL.find({
|
||||
orgId: actor.orgId,
|
||||
projectId: null,
|
||||
...(app ? { app } : {})
|
||||
})
|
||||
).filter((appConnection) =>
|
||||
permission.can(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
appConnections
|
||||
@@ -241,24 +296,37 @@ export const appConnectionServiceFactory = ({
|
||||
};
|
||||
|
||||
const createAppConnection = async (
|
||||
{ method, app, credentials, gatewayId, ...params }: TCreateAppConnectionDTO,
|
||||
{ method, app, credentials, gatewayId, projectId, ...params }: TCreateAppConnectionDTO,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
const project = await projectDAL.findProjectById(projectId);
|
||||
|
||||
if (!project) throw new BadRequestError({ message: `Could not find project with ID ${projectId}` });
|
||||
|
||||
if (gatewayId) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionGatewayActions.AttachGateways,
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
@@ -294,7 +362,8 @@ export const appConnectionServiceFactory = ({
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: connectionCredentials,
|
||||
orgId: actor.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
return appConnectionDAL.create({
|
||||
@@ -303,6 +372,7 @@ export const appConnectionServiceFactory = ({
|
||||
method,
|
||||
app,
|
||||
gatewayId,
|
||||
projectId,
|
||||
...params
|
||||
});
|
||||
};
|
||||
@@ -354,7 +424,7 @@ export const appConnectionServiceFactory = ({
|
||||
"Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections."
|
||||
);
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
@@ -362,13 +432,29 @@ export const appConnectionServiceFactory = ({
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Edit,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
if (appConnection.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId: appConnection.projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
if (gatewayId !== appConnection.gatewayId) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Edit,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
} else {
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Edit,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
}
|
||||
|
||||
if (gatewayId !== undefined && gatewayId !== appConnection.gatewayId) {
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionGatewayActions.AttachGateways,
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
@@ -428,7 +514,8 @@ export const appConnectionServiceFactory = ({
|
||||
? await encryptAppConnectionCredentials({
|
||||
credentials: connectionCredentials,
|
||||
orgId: actor.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
})
|
||||
: undefined;
|
||||
|
||||
@@ -477,18 +564,34 @@ export const appConnectionServiceFactory = ({
|
||||
|
||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
if (appConnection.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId: appConnection.projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Delete,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Delete,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Delete,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
}
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
|
||||
@@ -530,18 +633,34 @@ export const appConnectionServiceFactory = ({
|
||||
"Failed to connect app due to plan restriction. Upgrade plan to access enterprise app connections."
|
||||
);
|
||||
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
appConnection.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
if (appConnection.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId: appConnection.projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Connect,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Connect,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
} else {
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
appConnection.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Connect,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
}
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({
|
||||
@@ -555,7 +674,25 @@ export const appConnectionServiceFactory = ({
|
||||
return connection as T;
|
||||
};
|
||||
|
||||
const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor) => {
|
||||
const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor, projectId: string) => {
|
||||
const project = await projectDAL.findProjectById(projectId);
|
||||
|
||||
if (!project) throw new BadRequestError({ message: `Could not find project with ID ${projectId}` });
|
||||
|
||||
const { permission: projectPermission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Connect,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
@@ -564,21 +701,217 @@ export const appConnectionServiceFactory = ({
|
||||
actor.orgId
|
||||
);
|
||||
|
||||
const appConnections = await appConnectionDAL.find({ app, orgId: actor.orgId });
|
||||
const orgAppConnections = await appConnectionDAL.find({ app, orgId: actor.orgId, projectId: null });
|
||||
|
||||
const availableConnections = appConnections.filter((connection) =>
|
||||
const availableOrgConnections = orgAppConnections.filter((connection) =>
|
||||
orgPermission.can(
|
||||
OrgPermissionAppConnectionActions.Connect,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: connection.id })
|
||||
)
|
||||
);
|
||||
|
||||
return availableConnections as Omit<TAppConnection, "credentials">[];
|
||||
const projectAppConnections = await appConnectionDAL.find({ app, projectId });
|
||||
|
||||
const availableProjectConnections = projectAppConnections.filter((connection) =>
|
||||
projectPermission.can(
|
||||
ProjectPermissionAppConnectionActions.Connect,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId: connection.id })
|
||||
)
|
||||
);
|
||||
|
||||
return [...availableOrgConnections, ...availableProjectConnections].sort((a, b) =>
|
||||
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||
) as Omit<TAppConnection, "credentials">[];
|
||||
};
|
||||
|
||||
const findAppConnectionUsageById = async (app: AppConnection, connectionId: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await appConnectionDAL.findById(connectionId);
|
||||
|
||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
|
||||
|
||||
const projectUsage = await appConnectionDAL.findAppConnectionUsageById(connectionId);
|
||||
|
||||
return projectUsage;
|
||||
};
|
||||
|
||||
const migrateAppConnection = async <T extends TAppConnection>(
|
||||
app: AppConnection,
|
||||
connectionId: string,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const appConnection = await appConnectionDAL.findById(connectionId);
|
||||
|
||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
||||
|
||||
const { permission, membership } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
|
||||
if (membership.role !== OrgMembershipRole.Admin) {
|
||||
throw new BadRequestError({
|
||||
message: "You must be an organization admin to migrate App Connections"
|
||||
});
|
||||
}
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
|
||||
|
||||
const decryptedConnection = await decryptAppConnection(appConnection, kmsService);
|
||||
|
||||
const {
|
||||
createdAt,
|
||||
id,
|
||||
version,
|
||||
updatedAt,
|
||||
projectId,
|
||||
encryptedCredentials: orgEncryptedCredentials,
|
||||
...createPayload
|
||||
} = appConnection;
|
||||
|
||||
if (projectId) {
|
||||
throw new BadRequestError({
|
||||
message: "This App Connection already belongs to a project and cannot be migrated"
|
||||
});
|
||||
}
|
||||
|
||||
await appConnectionDAL.transaction(async (tx) => {
|
||||
const projectUsage = await appConnectionDAL.findAppConnectionUsageById(connectionId, tx);
|
||||
|
||||
if (!projectUsage.length) {
|
||||
throw new BadRequestError({
|
||||
message: "This App Connection is not used in any projects."
|
||||
});
|
||||
}
|
||||
|
||||
for await (const project of projectUsage) {
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: decryptedConnection.credentials,
|
||||
orgId: actor.orgId,
|
||||
kmsService,
|
||||
projectId: project.id
|
||||
});
|
||||
|
||||
const projectAppConnection = await appConnectionDAL.create(
|
||||
{
|
||||
...createPayload,
|
||||
encryptedCredentials,
|
||||
projectId: project.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
if (project.resources.secretSyncs.length) {
|
||||
await secretSyncDAL.update(
|
||||
{
|
||||
$in: {
|
||||
id: project.resources.secretSyncs.map((r) => r.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
connectionId: projectAppConnection.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
if (project.resources.secretRotations.length) {
|
||||
await secretRotationV2DAL.update(
|
||||
{
|
||||
$in: {
|
||||
id: project.resources.secretRotations.map((r) => r.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
connectionId: projectAppConnection.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
if (project.resources.dataSources.length) {
|
||||
await secretScanningV2DAL.dataSources.update(
|
||||
{
|
||||
$in: {
|
||||
id: project.resources.dataSources.map((r) => r.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
connectionId: projectAppConnection.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
if (project.resources.externalCas.length) {
|
||||
const appConnectionProperty = project.resources.externalCas.filter(
|
||||
(ex) => ex.appConnectionId === connectionId
|
||||
);
|
||||
const dnsAppConnectionProperty = project.resources.externalCas.filter(
|
||||
(ex) => ex.dnsAppConnectionId === connectionId
|
||||
);
|
||||
|
||||
if (appConnectionProperty.length) {
|
||||
await externalCertificateAuthorityDAL.update(
|
||||
{
|
||||
$in: {
|
||||
id: appConnectionProperty.map((r) => r.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
appConnectionId: projectAppConnection.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
if (dnsAppConnectionProperty.length) {
|
||||
await externalCertificateAuthorityDAL.update(
|
||||
{
|
||||
$in: {
|
||||
id: dnsAppConnectionProperty.map((r) => r.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
dnsAppConnectionId: projectAppConnection.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return decryptedConnection as T;
|
||||
};
|
||||
|
||||
return {
|
||||
listAppConnectionOptions,
|
||||
listAppConnectionsByOrg,
|
||||
listAppConnections,
|
||||
findAppConnectionById,
|
||||
findAppConnectionByName,
|
||||
createAppConnection,
|
||||
@@ -586,6 +919,8 @@ export const appConnectionServiceFactory = ({
|
||||
deleteAppConnection,
|
||||
connectAppConnectionById,
|
||||
listAvailableAppConnectionsForUser,
|
||||
findAppConnectionUsageById,
|
||||
migrateAppConnection,
|
||||
github: githubConnectionService(connectAppConnectionById, gatewayService),
|
||||
githubRadar: githubRadarConnectionService(connectAppConnectionById),
|
||||
gcp: gcpConnectionService(connectAppConnectionById),
|
||||
|
@@ -307,10 +307,10 @@ export type TSqlConnectionInput =
|
||||
|
||||
export type TCreateAppConnectionDTO = Pick<
|
||||
TAppConnectionInput,
|
||||
"credentials" | "method" | "name" | "app" | "description" | "isPlatformManagedCredentials" | "gatewayId"
|
||||
"credentials" | "method" | "name" | "app" | "description" | "isPlatformManagedCredentials" | "gatewayId" | "projectId"
|
||||
>;
|
||||
|
||||
export type TUpdateAppConnectionDTO = Partial<Omit<TCreateAppConnectionDTO, "method" | "app">> & {
|
||||
export type TUpdateAppConnectionDTO = Partial<Omit<TCreateAppConnectionDTO, "method" | "app" | "projectId">> & {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
|
@@ -51,7 +51,7 @@ const authorizeAuth0Connection = async ({
|
||||
};
|
||||
|
||||
export const getAuth0ConnectionAccessToken = async (
|
||||
{ id, orgId, credentials }: TAuth0Connection,
|
||||
{ id, orgId, credentials, projectId }: TAuth0Connection,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
) => {
|
||||
@@ -72,7 +72,8 @@ export const getAuth0ConnectionAccessToken = async (
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(id, { encryptedCredentials });
|
||||
|
@@ -57,6 +57,7 @@ export const getAzureConnectionAccessToken = async (
|
||||
const credentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
projectId: appConnection.projectId,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
})) as TAzureClientSecretsConnectionCredentials;
|
||||
|
||||
@@ -93,6 +94,7 @@ export const getAzureConnectionAccessToken = async (
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
projectId: appConnection.projectId,
|
||||
kmsService
|
||||
});
|
||||
|
||||
@@ -102,6 +104,7 @@ export const getAzureConnectionAccessToken = async (
|
||||
case AzureClientSecretsConnectionMethod.ClientSecret:
|
||||
const accessTokenCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
projectId: appConnection.projectId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
})) as TAzureClientSecretsConnectionClientSecretCredentials;
|
||||
@@ -129,6 +132,7 @@ export const getAzureConnectionAccessToken = async (
|
||||
const encryptedClientCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedClientCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
projectId: appConnection.projectId,
|
||||
kmsService
|
||||
});
|
||||
|
||||
|
@@ -70,7 +70,8 @@ export const getAzureDevopsConnection = async (
|
||||
const oauthCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as TAzureDevOpsConnectionCredentials;
|
||||
|
||||
if (!("refreshToken" in oauthCredentials)) {
|
||||
@@ -100,7 +101,8 @@ export const getAzureDevopsConnection = async (
|
||||
const encryptedOAuthCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedOAuthCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedOAuthCredentials });
|
||||
@@ -111,7 +113,8 @@ export const getAzureDevopsConnection = async (
|
||||
const accessTokenCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as { accessToken: string };
|
||||
|
||||
if (!("accessToken" in accessTokenCredentials)) {
|
||||
@@ -124,7 +127,8 @@ export const getAzureDevopsConnection = async (
|
||||
const clientSecretCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as TAzureDevOpsConnectionClientSecretCredentials;
|
||||
|
||||
const { accessToken, expiresAt, clientId, clientSecret, tenantId: clientTenantId } = clientSecretCredentials;
|
||||
@@ -153,7 +157,8 @@ export const getAzureDevopsConnection = async (
|
||||
const encryptedClientCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedClientCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials });
|
||||
|
@@ -58,7 +58,8 @@ export const getAzureConnectionAccessToken = async (
|
||||
const oauthCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as TAzureKeyVaultConnectionCredentials;
|
||||
|
||||
const { data } = await request.post<ExchangeCodeAzureResponse>(
|
||||
@@ -82,7 +83,8 @@ export const getAzureConnectionAccessToken = async (
|
||||
const encryptedOAuthCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedOAuthCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedOAuthCredentials });
|
||||
@@ -95,7 +97,8 @@ export const getAzureConnectionAccessToken = async (
|
||||
const clientSecretCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as TAzureKeyVaultConnectionClientSecretCredentials;
|
||||
|
||||
const { accessToken, expiresAt, clientId, clientSecret, tenantId } = clientSecretCredentials;
|
||||
@@ -124,7 +127,8 @@ export const getAzureConnectionAccessToken = async (
|
||||
const encryptedClientCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedClientCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials });
|
||||
|
@@ -40,7 +40,7 @@ const authorizeCamundaConnection = async ({
|
||||
};
|
||||
|
||||
export const getCamundaConnectionAccessToken = async (
|
||||
{ id, orgId, credentials }: TCamundaConnection,
|
||||
{ id, orgId, credentials, projectId }: TCamundaConnection,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
) => {
|
||||
@@ -61,7 +61,8 @@ export const getCamundaConnectionAccessToken = async (
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(id, { encryptedCredentials });
|
||||
|
@@ -47,7 +47,7 @@ const authorizeDatabricksConnection = async ({
|
||||
};
|
||||
|
||||
export const getDatabricksConnectionAccessToken = async (
|
||||
{ id, orgId, credentials }: TDatabricksConnection,
|
||||
{ id, orgId, credentials, projectId }: TDatabricksConnection,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
) => {
|
||||
@@ -68,7 +68,8 @@ export const getDatabricksConnectionAccessToken = async (
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(id, { encryptedCredentials });
|
||||
|
@@ -64,6 +64,7 @@ export const refreshGitLabToken = async (
|
||||
refreshToken: string,
|
||||
appId: string,
|
||||
orgId: string,
|
||||
projectId: string | undefined | null,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">,
|
||||
instanceUrl?: string
|
||||
@@ -105,7 +106,8 @@ export const refreshGitLabToken = async (
|
||||
expiresAt
|
||||
},
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appId, { encryptedCredentials });
|
||||
@@ -238,6 +240,7 @@ export const getGitLabConnectionClient = async (
|
||||
appConnection.credentials.refreshToken,
|
||||
appConnection.id,
|
||||
appConnection.orgId,
|
||||
appConnection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
appConnection.credentials.instanceUrl
|
||||
@@ -273,6 +276,7 @@ export const listGitLabProjects = async ({
|
||||
appConnection.credentials.refreshToken,
|
||||
appConnection.id,
|
||||
appConnection.orgId,
|
||||
appConnection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
appConnection.credentials.instanceUrl
|
||||
@@ -341,6 +345,7 @@ export const listGitLabGroups = async ({
|
||||
appConnection.credentials.refreshToken,
|
||||
appConnection.id,
|
||||
appConnection.orgId,
|
||||
appConnection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
appConnection.credentials.instanceUrl
|
||||
|
@@ -36,6 +36,7 @@ export const refreshHerokuToken = async (
|
||||
refreshToken: string,
|
||||
appId: string,
|
||||
orgId: string,
|
||||
projectId: string | null | undefined,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
): Promise<string> => {
|
||||
@@ -64,7 +65,8 @@ export const refreshHerokuToken = async (
|
||||
expiresAt: new Date(Date.now() + data.expires_in * 1000 - 60000)
|
||||
},
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appId, { encryptedCredentials });
|
||||
@@ -186,6 +188,7 @@ export const listHerokuApps = async ({
|
||||
appConnection.credentials.refreshToken,
|
||||
appConnection.id,
|
||||
appConnection.orgId,
|
||||
appConnection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
);
|
||||
|
@@ -53,6 +53,7 @@ const getValidAccessToken = async (
|
||||
connection.credentials.refreshToken,
|
||||
connection.id,
|
||||
connection.orgId,
|
||||
connection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
connection.credentials.instanceUrl
|
||||
|
@@ -32,6 +32,7 @@ const getValidAuthToken = async (
|
||||
connection.credentials.refreshToken,
|
||||
connection.id,
|
||||
connection.orgId,
|
||||
connection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
);
|
||||
|
@@ -31,6 +31,7 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -67,6 +68,7 @@ const expandSecretSync = (
|
||||
connectionVersion,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
connectionGatewayId,
|
||||
connectionProjectId,
|
||||
...el
|
||||
} = secretSync;
|
||||
|
||||
@@ -86,7 +88,8 @@ const expandSecretSync = (
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||
gatewayId: connectionGatewayId
|
||||
gatewayId: connectionGatewayId,
|
||||
projectId: connectionProjectId
|
||||
},
|
||||
folder: folder
|
||||
? {
|
||||
|
@@ -446,13 +446,14 @@ export const secretSyncQueueFactory = ({
|
||||
|
||||
try {
|
||||
const {
|
||||
connection: { orgId, encryptedCredentials }
|
||||
connection: { orgId, encryptedCredentials, projectId }
|
||||
} = secretSync;
|
||||
|
||||
const credentials = await decryptAppConnectionCredentials({
|
||||
orgId,
|
||||
encryptedCredentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
const secretSyncWithCredentials = {
|
||||
@@ -589,13 +590,14 @@ export const secretSyncQueueFactory = ({
|
||||
|
||||
try {
|
||||
const {
|
||||
connection: { orgId, encryptedCredentials }
|
||||
connection: { orgId, encryptedCredentials, projectId }
|
||||
} = secretSync;
|
||||
|
||||
const credentials = await decryptAppConnectionCredentials({
|
||||
orgId,
|
||||
encryptedCredentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await $importSecrets(
|
||||
@@ -713,13 +715,14 @@ export const secretSyncQueueFactory = ({
|
||||
|
||||
try {
|
||||
const {
|
||||
connection: { orgId, encryptedCredentials }
|
||||
connection: { orgId, encryptedCredentials, projectId }
|
||||
} = secretSync;
|
||||
|
||||
const credentials = await decryptAppConnectionCredentials({
|
||||
orgId,
|
||||
encryptedCredentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
const secretMap = await $getInfisicalSecrets(secretSync);
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 930 KiB |
@@ -53,7 +53,7 @@ Infisical supports the use of [Service Accounts](https://developer.1password.com
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -72,7 +72,7 @@ Infisical supports the use of [Service Accounts](https://developer.1password.com
|
||||

|
||||
</Step>
|
||||
<Step title="Connection Created">
|
||||
After clicking Create, your **1Password Connection** is established and ready to use with your Infisical projects.
|
||||
After clicking Create, your **1Password Connection** is established and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -90,6 +90,7 @@ Infisical supports the use of [Service Accounts](https://developer.1password.com
|
||||
--data '{
|
||||
"name": "my-1password-connection",
|
||||
"method": "api-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"instanceUrl": "https://1pass.example.com",
|
||||
"apiToken": "<YOUR-API-TOKEN>"
|
||||
@@ -104,6 +105,7 @@ Infisical supports the use of [Service Accounts](https://developer.1password.com
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-1password-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -42,7 +42,7 @@ Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-st
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
|
||||
2. Select the **Auth0 Connection** option.
|
||||
@@ -67,6 +67,7 @@ Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-st
|
||||
--data '{
|
||||
"name": "my-auth0-connection",
|
||||
"method": "client-credentials",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"domain": "xxx-xxxxxxxxx.us.auth0.com",
|
||||
"clientId": "...",
|
||||
@@ -83,6 +84,7 @@ Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-st
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-auth0-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 1,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
|
@@ -184,7 +184,7 @@ Infisical supports two methods for connecting to AWS.
|
||||
<Step title="Setup AWS Connection in Infisical">
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
|
||||
2. Select the **AWS Connection** option.
|
||||
@@ -209,6 +209,7 @@ Infisical supports two methods for connecting to AWS.
|
||||
--data '{
|
||||
"name": "my-aws-connection",
|
||||
"method": "assume-role",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"roleArn": "...",
|
||||
}
|
||||
@@ -222,6 +223,7 @@ Infisical supports two methods for connecting to AWS.
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-aws-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 123,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
@@ -361,7 +363,7 @@ Infisical supports two methods for connecting to AWS.
|
||||
<Step title="Setup AWS Connection in Infisical">
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
|
||||
2. Select the **AWS Connection** option.
|
||||
@@ -386,6 +388,7 @@ Infisical supports two methods for connecting to AWS.
|
||||
--data '{
|
||||
"name": "my-aws-connection",
|
||||
"method": "access-key",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"accessKeyId": "...",
|
||||
"secretKey": "..."
|
||||
@@ -400,6 +403,7 @@ Infisical supports two methods for connecting to AWS.
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-aws-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 123,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
|
@@ -83,7 +83,7 @@ Infisical currently only supports two methods for connecting to Azure, which are
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -94,7 +94,7 @@ Infisical currently only supports two methods for connecting to Azure, which are
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -117,7 +117,7 @@ Infisical currently supports three methods for connecting to Azure DevOps, which
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -83,7 +83,7 @@ Infisical currently only supports two methods for connecting to Azure, which are
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -78,7 +78,7 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -95,7 +95,7 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck
|
||||

|
||||
</Step>
|
||||
<Step title="Connection Created">
|
||||
After clicking Create, your **Bitbucket Connection** is established and ready to use with your Infisical projects.
|
||||
After clicking Create, your **Bitbucket Connection** is established and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -113,6 +113,7 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck
|
||||
--data '{
|
||||
"name": "my-bitbucket-connection",
|
||||
"method": "api-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"email": "user@example.com",
|
||||
"apiToken": "<YOUR-API-TOKEN>"
|
||||
@@ -127,6 +128,7 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-bitbucket-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -50,8 +50,7 @@ Infisical supports connecting to Camunda APIs using [client credentials](https:/
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings**
|
||||
page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -37,7 +37,7 @@ Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -55,7 +55,7 @@ Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user
|
||||

|
||||
</Step>
|
||||
<Step title="Connection created">
|
||||
After submitting the form, your **Checkly Connection** will be successfully created and ready to use with your Infisical projects.
|
||||
After submitting the form, your **Checkly Connection** will be successfully created and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -75,6 +75,7 @@ Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user
|
||||
--data '{
|
||||
"name": "my-checkly-connection",
|
||||
"method": "api-key",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"apiKey": "[API KEY]"
|
||||
}
|
||||
@@ -88,6 +89,7 @@ Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-checkly-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -88,8 +88,7 @@ Infisical supports connecting to Cloudflare using API tokens and Account ID for
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings**
|
||||
page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -43,8 +43,7 @@ Infisical supports the use of [service principals](https://docs.databricks.com/e
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings**
|
||||
page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -45,7 +45,7 @@ Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/accoun
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -63,7 +63,7 @@ Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/accoun
|
||||

|
||||
</Step>
|
||||
<Step title="Connection created">
|
||||
After submitting the form, your **DigitalOcean Connection** will be successfully created and ready to use with your Infisical projects.
|
||||
After submitting the form, your **DigitalOcean Connection** will be successfully created and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -82,6 +82,7 @@ Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/accoun
|
||||
--data '{
|
||||
"name": "my-digitalocean-connection",
|
||||
"method": "api-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"apiToken": "[API TOKEN]"
|
||||
}
|
||||
@@ -95,6 +96,7 @@ Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/accoun
|
||||
"appConnection": {
|
||||
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
|
||||
"name": "my-digitalocean-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "abcdef12-3456-7890-abcd-ef1234567890",
|
||||
|
@@ -30,7 +30,7 @@ Infisical supports the use of [Access Tokens](https://fly.io/docs/security/token
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -48,7 +48,7 @@ Infisical supports the use of [Access Tokens](https://fly.io/docs/security/token
|
||||

|
||||
</Step>
|
||||
<Step title="Connection Created">
|
||||
After clicking Create, your **Fly.io Connection** is established and ready to use with your Infisical projects.
|
||||
After clicking Create, your **Fly.io Connection** is established and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -66,6 +66,7 @@ Infisical supports the use of [Access Tokens](https://fly.io/docs/security/token
|
||||
--data '{
|
||||
"name": "my-flyio-connection",
|
||||
"method": "access-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"accessToken": "[PRIVATE TOKEN]"
|
||||
}
|
||||
@@ -79,6 +80,7 @@ Infisical supports the use of [Access Tokens](https://fly.io/docs/security/token
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-flyio-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -82,8 +82,7 @@ Infisical supports [service account impersonation](https://cloud.google.com/iam/
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings**
|
||||
page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -97,7 +97,7 @@ Infisical supports GitHub App installation for creating a GitHub Radar Connectio
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -85,7 +85,7 @@ Infisical supports two methods for connecting to GitHub.
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
@@ -156,7 +156,7 @@ Infisical supports two methods for connecting to GitHub.
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -70,7 +70,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
@@ -193,7 +193,7 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -131,7 +131,7 @@ Infisical supports two methods for connecting to Hashicorp Vault.
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -182,6 +182,7 @@ Infisical supports two methods for connecting to Hashicorp Vault.
|
||||
--data '{
|
||||
"name": "my-vault-connection",
|
||||
"method": "app-role",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"instanceUrl": "https://vault.example.com",
|
||||
"roleId": "4797c4fa-7794-71f0-c8b1-7c87759df5bf",
|
||||
@@ -197,6 +198,7 @@ Infisical supports two methods for connecting to Hashicorp Vault.
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-vault-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 1,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2025-04-01T05:31:56Z",
|
||||
|
@@ -51,7 +51,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
@@ -93,7 +93,7 @@ Infisical supports two methods for connecting to Heroku: **OAuth** and **Auth To
|
||||

|
||||
</Step>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -53,7 +53,7 @@ Infisical supports connecting to Humanitec using a service user.
|
||||

|
||||
</Step>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -33,7 +33,7 @@ Depending on how you intend to use your LDAP connection, there may be additional
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
|
||||
2. Select the **LDAP Connection** option.
|
||||
@@ -58,6 +58,7 @@ Depending on how you intend to use your LDAP connection, there may be additional
|
||||
--data '{
|
||||
"name": "my-ldap-connection",
|
||||
"method": "simple-bind",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"provider": "active-directory",
|
||||
"url": "ldaps://domain-or-ip:636",
|
||||
@@ -76,6 +77,7 @@ Depending on how you intend to use your LDAP connection, there may be additional
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-ldap-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 1,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
|
@@ -62,7 +62,7 @@ Infisical supports connecting to Microsoft SQL Server using database principals.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
|
||||
2. Select the **Microsoft SQL Server Connection** option.
|
||||
@@ -96,6 +96,7 @@ Infisical supports connecting to Microsoft SQL Server using database principals.
|
||||
--data '{
|
||||
"name": "my-mssql-connection",
|
||||
"method": "username-and-password",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"isPlatformManagedCredentials": true,
|
||||
"credentials": {
|
||||
"host": "123.4.5.6",
|
||||
@@ -115,7 +116,8 @@ Infisical supports connecting to Microsoft SQL Server using database principals.
|
||||
{
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-pg-connection",
|
||||
"name": "my-mssql-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 1,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
|
@@ -52,7 +52,7 @@ Infisical supports connecting to MySQL using a database role.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
|
||||
2. Select the **MySQL Connection** option.
|
||||
@@ -88,6 +88,7 @@ Infisical supports connecting to MySQL using a database role.
|
||||
"name": "my-mysql-connection",
|
||||
"method": "username-and-password",
|
||||
"isPlatformManagedCredentials": true,
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"host": "123.4.5.6",
|
||||
"port": 3306,
|
||||
@@ -107,6 +108,7 @@ Infisical supports connecting to MySQL using a database role.
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-mysql-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 1,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
|
@@ -35,7 +35,7 @@ Infisical supports the use of [Personal Access Tokens](https://docs.netlify.com/
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -53,7 +53,7 @@ Infisical supports the use of [Personal Access Tokens](https://docs.netlify.com/
|
||||

|
||||
</Step>
|
||||
<Step title="Connection created">
|
||||
After submitting the form, your **Netlify Connection** will be successfully created and ready to use with your Infisical projects.
|
||||
After submitting the form, your **Netlify Connection** will be successfully created and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -72,6 +72,7 @@ Infisical supports the use of [Personal Access Tokens](https://docs.netlify.com/
|
||||
--data '{
|
||||
"name": "my-netlify-connection",
|
||||
"method": "access-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"accessToken": "[ACCESS TOKEN]"
|
||||
}
|
||||
@@ -86,6 +87,7 @@ Infisical supports the use of [Personal Access Tokens](https://docs.netlify.com/
|
||||
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
|
||||
"name": "my-netlify-connection",
|
||||
"description": null,
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 1,
|
||||
"orgId": "abcdef12-3456-7890-abcd-ef1234567890",
|
||||
"createdAt": "2025-07-19T10:15:00.000Z",
|
||||
|
@@ -117,7 +117,7 @@ Infisical supports the use of [API Signing Key Authentication](https://docs.orac
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -139,7 +139,7 @@ Infisical supports the use of [API Signing Key Authentication](https://docs.orac
|
||||

|
||||
</Step>
|
||||
<Step title="Connection Created">
|
||||
After clicking Create, your **OCI Connection** is established and ready to use with your Infisical projects.
|
||||
After clicking Create, your **OCI Connection** is established and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -157,6 +157,7 @@ Infisical supports the use of [API Signing Key Authentication](https://docs.orac
|
||||
--data '{
|
||||
"name": "my-oci-connection",
|
||||
"method": "access-key",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"userOcid": "ocid1.user.oc1..aaaaaaaagrp35tbkvvad4y2j7sug7xonua7dl2gfp4at2u5i5xj4ghnitg3a",
|
||||
"tenancyOcid": "ocid1.tenancy.oc1..aaaaaaaaotfma465m4zumfe2ua64mj2m5dwmlw2llh4g4dnfttnakiifonta",
|
||||
@@ -174,6 +175,7 @@ Infisical supports the use of [API Signing Key Authentication](https://docs.orac
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-oci-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -31,7 +31,7 @@ Infisical supports the use of [API Tokens](https://developer.okta.com/docs/guide
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -48,7 +48,7 @@ Infisical supports the use of [API Tokens](https://developer.okta.com/docs/guide
|
||||

|
||||
</Step>
|
||||
<Step title="Connection Created">
|
||||
After clicking Create, your **Okta Connection** is established and ready to use with your Infisical projects.
|
||||
After clicking Create, your **Okta Connection** is established and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -66,6 +66,7 @@ Infisical supports the use of [API Tokens](https://developer.okta.com/docs/guide
|
||||
--data '{
|
||||
"name": "my-okta-connection",
|
||||
"method": "api-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"instanceUrl": "https://example.okta.com",
|
||||
"apiToken": "<YOUR-API-TOKEN>"
|
||||
@@ -80,6 +81,7 @@ Infisical supports the use of [API Tokens](https://developer.okta.com/docs/guide
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-okta-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -62,7 +62,7 @@ Infisical supports connecting to OracleDB using a database user.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
|
||||
2. Select the **OracleDB Connection** option.
|
||||
@@ -98,6 +98,7 @@ Infisical supports connecting to OracleDB using a database user.
|
||||
"name": "my-oracledb-connection",
|
||||
"method": "username-and-password",
|
||||
"isPlatformManagedCredentials": true,
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"host": "123.4.5.6",
|
||||
"port": 1521,
|
||||
@@ -117,6 +118,7 @@ Infisical supports connecting to OracleDB using a database user.
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-oracledb-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 1,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
|
@@ -3,12 +3,12 @@ sidebarTitle: "Overview"
|
||||
description: "Learn how to manage and configure third-party app connections with Infisical."
|
||||
---
|
||||
|
||||
App Connections enable your organization to integrate Infisical with third-party services in a secure and versatile way.
|
||||
App Connections enable you to integrate your Infisical projects with third-party services in a secure and versatile way.
|
||||
|
||||
## Concept
|
||||
|
||||
App Connections are an organization-level resource used to establish connections with third-party applications
|
||||
that can be used across Infisical projects. Example use cases include syncing secrets, generating dynamic secrets, and more.
|
||||
App Connections are a project-level resource used to establish connections with third-party applications
|
||||
that can be used across multiple features. Example use cases include syncing secrets, rotating credentials, scanning repositories for secret leaks, and more.
|
||||
|
||||
<br />
|
||||
|
||||
|
@@ -60,7 +60,7 @@ Infisical supports connecting to PostgreSQL using a database role.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
|
||||
2. Select the **PostgreSQL Connection** option.
|
||||
@@ -95,6 +95,7 @@ Infisical supports connecting to PostgreSQL using a database role.
|
||||
"name": "my-pg-connection",
|
||||
"method": "username-and-password",
|
||||
"isPlatformManagedCredentials": true,
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"host": "123.4.5.6",
|
||||
"port": 5432,
|
||||
@@ -114,6 +115,7 @@ Infisical supports connecting to PostgreSQL using a database role.
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-pg-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 1,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
|
@@ -96,7 +96,7 @@ Infisical supports the use of [API Tokens](https://docs.railway.com/guides/publi
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -115,7 +115,7 @@ Infisical supports the use of [API Tokens](https://docs.railway.com/guides/publi
|
||||

|
||||
</Step>
|
||||
<Step title="Connection created">
|
||||
After submitting the form, your **Railway Connection** will be successfully created and ready to use with your Infisical projects.
|
||||
After submitting the form, your **Railway Connection** will be successfully created and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -134,6 +134,7 @@ Infisical supports the use of [API Tokens](https://docs.railway.com/guides/publi
|
||||
--data '{
|
||||
"name": "my-railway-connection",
|
||||
"method": "team-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"apiToken": "[TEAM TOKEN]"
|
||||
}
|
||||
@@ -147,6 +148,7 @@ Infisical supports the use of [API Tokens](https://docs.railway.com/guides/publi
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-railway-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -33,8 +33,7 @@ Infisical supports connecting to Render using API keys for secure access to your
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
Navigate to the **App Connections** tab on the **Organization Settings**
|
||||
page. 
|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
|
@@ -34,7 +34,7 @@ Infisical supports the use of [Personal Access Tokens](https://supabase.com/dash
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -53,7 +53,7 @@ Infisical supports the use of [Personal Access Tokens](https://supabase.com/dash
|
||||

|
||||
</Step>
|
||||
<Step title="Connection created">
|
||||
After submitting the form, your **Supabase Connection** will be successfully created and ready to use with your Infisical projects.
|
||||
After submitting the form, your **Supabase Connection** will be successfully created and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -73,6 +73,7 @@ Infisical supports the use of [Personal Access Tokens](https://supabase.com/dash
|
||||
--data '{
|
||||
"name": "my-supabase-connection",
|
||||
"method": "access-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"accessToken": "[Access Token]",
|
||||
"instanceUrl": "https://api.supabase.com"
|
||||
@@ -87,6 +88,7 @@ Infisical supports the use of [Personal Access Tokens](https://supabase.com/dash
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-supabase-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -51,7 +51,7 @@ Infisical supports connecting to TeamCity using Access Tokens.
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to App Connections
|
||||
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||

|
||||
2. Add Connection
|
||||
|
||||
@@ -68,7 +68,7 @@ Infisical supports connecting to TeamCity using Access Tokens.
|
||||

|
||||
4. Connection Created
|
||||
|
||||
After clicking Create, your **TeamCity Connection** is established and ready to use with your Infisical projects.
|
||||
After clicking Create, your **TeamCity Connection** is established and ready to use with your Infisical project.
|
||||

|
||||
</Tab>
|
||||
<Tab title="API">
|
||||
@@ -84,6 +84,7 @@ Infisical supports connecting to TeamCity using Access Tokens.
|
||||
--data '{
|
||||
"name": "my-teamcity-connection",
|
||||
"method": "access-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"accessToken": "...",
|
||||
"instanceUrl": "https://yourcompany.teamcity.com"
|
||||
@@ -98,6 +99,7 @@ Infisical supports connecting to TeamCity using Access Tokens.
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-teamcity-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -30,7 +30,7 @@ Infisical supports connecting to Terraform Cloud using a service user.
|
||||
<Step title="Add Terraform Cloud Connection in Infisical">
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to the **App Connections** tab on the **Organization Settings** page.
|
||||
1. Navigate to the **App Connections** page in the desired project.
|
||||

|
||||
2. Select the **Terraform Cloud Connection** option from the connection options modal.
|
||||

|
||||
@@ -52,6 +52,7 @@ Infisical supports connecting to Terraform Cloud using a service user.
|
||||
--data '{
|
||||
"name": "my-terraform-cloud-connection",
|
||||
"method": "api-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"apiToken": "...",
|
||||
}
|
||||
@@ -65,6 +66,7 @@ Infisical supports connecting to Terraform Cloud using a service user.
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-terraform-cloud-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 123,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
|
@@ -37,7 +37,7 @@ Infisical supports connecting to Vercel using API Tokens.
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to App Connections
|
||||
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||

|
||||
2. Add Connection
|
||||
|
||||
@@ -52,7 +52,7 @@ Infisical supports connecting to Vercel using API Tokens.
|
||||

|
||||
4. Connection Created
|
||||
|
||||
After clicking Create, your **Vercel Connection** is established and ready to use with your Infisical projects.
|
||||
After clicking Create, your **Vercel Connection** is established and ready to use with your Infisical project.
|
||||

|
||||
</Tab>
|
||||
<Tab title="API">
|
||||
@@ -67,6 +67,7 @@ Infisical supports connecting to Vercel using API Tokens.
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"name": "my-vercel-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"method": "api-token",
|
||||
"credentials": {
|
||||
"apiToken": "...",
|
||||
@@ -81,6 +82,7 @@ Infisical supports connecting to Vercel using API Tokens.
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-vercel-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 123,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2025-04-01T05:31:56Z",
|
||||
|
@@ -47,7 +47,8 @@ Ensure the user generating the access token has the required role and permission
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Add Connection">
|
||||
@@ -82,6 +83,7 @@ Ensure the user generating the access token has the required role and permission
|
||||
--data '{
|
||||
"name": "my-windmill-connection",
|
||||
"method": "access-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"token": "...",
|
||||
"instanceUrl": "https://app.windmill.dev"
|
||||
@@ -96,6 +98,7 @@ Ensure the user generating the access token has the required role and permission
|
||||
"appConnection": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "my-windmill-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"version": 123,
|
||||
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2025-04-01T05:31:56Z",
|
||||
|
@@ -31,7 +31,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/
|
||||
<Tab title="Infisical UI">
|
||||
<Steps>
|
||||
<Step title="Navigate to App Connections">
|
||||
In your Infisical dashboard, go to **Organization Settings** and select the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||
In your Infisical dashboard, navigate to the **App Connections** page in the desired project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -50,7 +50,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/
|
||||

|
||||
</Step>
|
||||
<Step title="Connection Created">
|
||||
After clicking Create, your **Zabbix Connection** is established and ready to use with your Infisical projects.
|
||||
After clicking Create, your **Zabbix Connection** is established and ready to use with your Infisical project.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -68,6 +68,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/
|
||||
--data '{
|
||||
"name": "my-zabbix-connection",
|
||||
"method": "api-token",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"credentials": {
|
||||
"apiToken": "[API TOKEN]",
|
||||
"instanceUrl": "https://zabbix.example.com"
|
||||
@@ -82,6 +83,7 @@ Infisical supports the use of [API Tokens](https://www.zabbix.com/documentation/
|
||||
"appConnection": {
|
||||
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||
"name": "my-zabbix-connection",
|
||||
"projectId": "7ffbb072-2575-495a-b5b0-127f88caef78",
|
||||
"description": null,
|
||||
"version": 1,
|
||||
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||
|
@@ -0,0 +1,45 @@
|
||||
import { components, OptionProps } from "react-select";
|
||||
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faBuilding, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Badge, Tooltip } from "@app/components/v2";
|
||||
import { TAvailableAppConnection } from "@app/hooks/api/appConnections";
|
||||
|
||||
export const AppConnectionOption = ({
|
||||
isSelected,
|
||||
children,
|
||||
...props
|
||||
}: OptionProps<TAvailableAppConnection>) => {
|
||||
const isCreateOption = props.data.id === "_create";
|
||||
|
||||
return (
|
||||
<components.Option isSelected={isSelected} {...props}>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
{isCreateOption ? (
|
||||
<div className="flex items-center gap-x-1 text-mineshaft-400">
|
||||
<FontAwesomeIcon icon={faPlus} size="sm" />
|
||||
<span className="mr-auto">Create New Connection</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="truncate">{children}</p>
|
||||
{!props.data.projectId && (
|
||||
<Tooltip content="This connection belongs to your organization.">
|
||||
<div className="ml-2 mr-auto">
|
||||
<Badge className="flex h-5 w-min items-center gap-1 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300 hover:text-bunker-300">
|
||||
<FontAwesomeIcon icon={faBuilding} size="sm" />
|
||||
Organization
|
||||
</Badge>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isSelected && (
|
||||
<FontAwesomeIcon className="ml-2 text-primary" icon={faCheckCircle} size="sm" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
1
frontend/src/components/app-connections/index.ts
Normal file
1
frontend/src/components/app-connections/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./AppConnectionOption";
|
@@ -1,7 +1,9 @@
|
||||
import { FunctionComponent, ReactNode } from "react";
|
||||
import { BoundCanProps, Can } from "@casl/react";
|
||||
import { AbilityTuple, MongoAbility } from "@casl/ability";
|
||||
import { Can } from "@casl/react";
|
||||
|
||||
import { TOrgPermission, useOrgPermission } from "@app/context/OrgPermissionContext";
|
||||
import { useOrgPermission } from "@app/context/OrgPermissionContext";
|
||||
import { OrgPermissionSet } from "@app/context/OrgPermissionContext/types";
|
||||
|
||||
import { AccessRestrictedBanner, Tooltip } from "../v2";
|
||||
|
||||
@@ -13,16 +15,25 @@ export const OrgPermissionGuardBanner = () => {
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
type Props<T extends AbilityTuple> = {
|
||||
label?: ReactNode;
|
||||
// this prop is used when there exist already a tooltip as helper text for users
|
||||
// so when permission is allowed same tooltip will be reused to show helpertext
|
||||
renderTooltip?: boolean;
|
||||
allowedLabel?: string;
|
||||
renderGuardBanner?: boolean;
|
||||
} & BoundCanProps<TOrgPermission>;
|
||||
I: T[0];
|
||||
ability?: MongoAbility<T>;
|
||||
children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode);
|
||||
passThrough?: boolean;
|
||||
} & (
|
||||
| { an: T[1] }
|
||||
| {
|
||||
a: T[1];
|
||||
}
|
||||
);
|
||||
|
||||
export const OrgPermissionCan: FunctionComponent<Props> = ({
|
||||
export const OrgPermissionCan: FunctionComponent<Props<OrgPermissionSet>> = ({
|
||||
label = "Access restricted",
|
||||
children,
|
||||
passThrough = true,
|
||||
@@ -38,9 +49,7 @@ export const OrgPermissionCan: FunctionComponent<Props> = ({
|
||||
{(isAllowed, ability) => {
|
||||
// akhilmhdh: This is set as type due to error in casl react type.
|
||||
const finalChild =
|
||||
typeof children === "function"
|
||||
? children(isAllowed, ability as TOrgPermission)
|
||||
: children;
|
||||
typeof children === "function" ? children(isAllowed, ability as any) : children;
|
||||
|
||||
if (!isAllowed && passThrough) {
|
||||
return <Tooltip content={label}>{finalChild}</Tooltip>;
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
||||
|
||||
import { SecretRotationV2Form } from "@app/components/secret-rotations-v2/forms";
|
||||
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
|
||||
import { SecretRotationV2ModalHeader } from "@app/components/secret-rotations-v2/SecretRotationV2ModalHeader";
|
||||
import { SecretRotationV2Select } from "@app/components/secret-rotations-v2/SecretRotationV2Select";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
@@ -24,14 +26,21 @@ type ContentProps = {
|
||||
onComplete: (secretRotation: TSecretRotationV2) => void;
|
||||
selectedRotation: SecretRotation | null;
|
||||
setSelectedRotation: (selectedRotation: SecretRotation | null) => void;
|
||||
initialFormData?: Partial<TSecretRotationV2Form>;
|
||||
} & SharedProps;
|
||||
|
||||
const Content = ({ setSelectedRotation, selectedRotation, ...props }: ContentProps) => {
|
||||
const Content = ({
|
||||
setSelectedRotation,
|
||||
selectedRotation,
|
||||
initialFormData,
|
||||
...props
|
||||
}: ContentProps) => {
|
||||
if (selectedRotation) {
|
||||
return (
|
||||
<SecretRotationV2Form
|
||||
onCancel={() => setSelectedRotation(null)}
|
||||
type={selectedRotation}
|
||||
initialFormData={initialFormData}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -42,12 +51,56 @@ const Content = ({ setSelectedRotation, selectedRotation, ...props }: ContentPro
|
||||
|
||||
export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }: Props) => {
|
||||
const [selectedRotation, setSelectedRotation] = useState<SecretRotation | null>(null);
|
||||
const [initialFormData, setInitialFormData] = useState<Partial<TSecretRotationV2Form>>();
|
||||
|
||||
const {
|
||||
location: {
|
||||
search: { connectionId, connectionName, ...search },
|
||||
pathname
|
||||
}
|
||||
} = useRouterState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (connectionId && connectionName) {
|
||||
const storedFormData = localStorage.getItem("secretRotationFormData");
|
||||
|
||||
if (!storedFormData) return;
|
||||
|
||||
let form: Partial<TSecretRotationV2Form> = {};
|
||||
try {
|
||||
form = JSON.parse(storedFormData) as TSecretRotationV2Form;
|
||||
} catch {
|
||||
return;
|
||||
} finally {
|
||||
localStorage.removeItem("secretRotationFormData");
|
||||
}
|
||||
|
||||
onOpenChange(true);
|
||||
|
||||
setSelectedRotation(form.type ?? null);
|
||||
|
||||
setInitialFormData({
|
||||
...form,
|
||||
connection: { id: connectionId, name: connectionName }
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: pathname,
|
||||
search
|
||||
});
|
||||
}
|
||||
}, [connectionId, connectionName]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setSelectedRotation(null);
|
||||
if (!open) {
|
||||
setSelectedRotation(null);
|
||||
setInitialFormData(undefined);
|
||||
}
|
||||
onOpenChange(open);
|
||||
}}
|
||||
>
|
||||
@@ -87,6 +140,7 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }:
|
||||
setSelectedRotation(null);
|
||||
onOpenChange(false);
|
||||
}}
|
||||
initialFormData={initialFormData}
|
||||
selectedRotation={selectedRotation}
|
||||
setSelectedRotation={setSelectedRotation}
|
||||
{...props}
|
||||
|
@@ -1,14 +1,17 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnectionOption } from "@app/components/app-connections";
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { SECRET_ROTATION_CONNECTION_MAP } from "@app/helpers/secretRotationsV2";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
|
||||
import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
|
||||
import { TSecretRotationV2Form } from "./schemas";
|
||||
|
||||
@@ -18,19 +21,26 @@ type Props = {
|
||||
};
|
||||
|
||||
export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate }: Props) => {
|
||||
const { permission } = useOrgPermission();
|
||||
const { control, watch } = useFormContext<TSecretRotationV2Form>();
|
||||
const { permission } = useProjectPermission();
|
||||
const { control, watch, setValue } = useFormContext<TSecretRotationV2Form>();
|
||||
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const);
|
||||
|
||||
const rotationType = watch("type");
|
||||
const app = SECRET_ROTATION_CONNECTION_MAP[rotationType];
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(app);
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(
|
||||
app,
|
||||
currentWorkspace.id
|
||||
);
|
||||
|
||||
const connectionName = APP_CONNECTION_MAP[app].name;
|
||||
|
||||
const canCreateConnection = permission.can(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
const appName = APP_CONNECTION_MAP[app].name;
|
||||
@@ -66,37 +76,56 @@ export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate }
|
||||
<FilterableSelect
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") {
|
||||
handlePopUpOpen("addConnection");
|
||||
onChange(null);
|
||||
// store for oauth callback connections
|
||||
localStorage.setItem("secretRotationFormData", JSON.stringify(watch()));
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(newValue);
|
||||
if (callback) callback();
|
||||
}}
|
||||
isLoading={isPending}
|
||||
options={availableConnections}
|
||||
options={[
|
||||
...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []),
|
||||
...(availableConnections ?? [])
|
||||
]}
|
||||
isDisabled={isUpdate}
|
||||
placeholder="Select connection..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
components={{ Option: AppConnectionOption }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="connection"
|
||||
/>
|
||||
{!isUpdate && availableConnections?.length === 0 && (
|
||||
{!isUpdate && !isPending && !availableConnections?.length && !canCreateConnection && (
|
||||
<p className="-mt-2.5 mb-2.5 text-xs text-yellow">
|
||||
<FontAwesomeIcon className="mr-1" size="xs" icon={faInfoCircle} />
|
||||
{canCreateConnection ? (
|
||||
<>
|
||||
You do not have access to any {appName} Connections. Create one from the{" "}
|
||||
<Link to="/organization/app-connections" className="underline">
|
||||
App Connections
|
||||
</Link>{" "}
|
||||
page.
|
||||
</>
|
||||
) : (
|
||||
`You do not have access to any ${appName} Connections. Contact an admin to create one.`
|
||||
)}
|
||||
You do not have access to any ${appName} Connections. Contact an admin to create one.
|
||||
</p>
|
||||
)}
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
// remove form storage, not oauth connection
|
||||
localStorage.removeItem("secretRotationFormData");
|
||||
handlePopUpToggle("addConnection", isOpen);
|
||||
}}
|
||||
projectType={currentWorkspace.type}
|
||||
projectId={currentWorkspace.id}
|
||||
app={app}
|
||||
onComplete={(connection) => {
|
||||
if (connection) {
|
||||
setValue("connection", connection);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -34,6 +34,7 @@ type Props = {
|
||||
environment?: string;
|
||||
environments?: WorkspaceEnv[];
|
||||
secretRotation?: TSecretRotationV2;
|
||||
initialFormData?: Partial<TSecretRotationV2Form>;
|
||||
};
|
||||
|
||||
const FORM_TABS: { name: string; key: string; fields: (keyof TSecretRotationV2Form)[] }[] = [
|
||||
@@ -64,7 +65,8 @@ export const SecretRotationV2Form = ({
|
||||
environment: envSlug,
|
||||
secretPath,
|
||||
secretRotation,
|
||||
environments
|
||||
environments,
|
||||
initialFormData
|
||||
}: Props) => {
|
||||
const createSecretRotation = useCreateSecretRotationV2();
|
||||
const updateSecretRotation = useUpdateSecretRotationV2();
|
||||
@@ -93,7 +95,8 @@ export const SecretRotationV2Form = ({
|
||||
},
|
||||
environment: currentWorkspace?.environments.find((env) => env.slug === envSlug),
|
||||
secretPath,
|
||||
...(rotationOption!.template as object) // can't infer type since we don't know which specific type it is
|
||||
...((rotationOption?.template as object) ?? {}), // can't infer type since we don't know which specific type it is
|
||||
...(initialFormData as object)
|
||||
},
|
||||
reValidateMode: "onChange"
|
||||
});
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
||||
|
||||
import { TSecretScanningDataSourceForm } from "@app/components/secret-scanning/forms/schemas";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import {
|
||||
SecretScanningDataSource,
|
||||
@@ -21,6 +23,7 @@ type ContentProps = {
|
||||
onComplete: (dataSource: TSecretScanningDataSource) => void;
|
||||
selectedDataSource: SecretScanningDataSource | null;
|
||||
setSelectedDataSource: (selectedDataSource: SecretScanningDataSource | null) => void;
|
||||
initialFormData?: Partial<TSecretScanningDataSourceForm>;
|
||||
};
|
||||
|
||||
const Content = ({ setSelectedDataSource, selectedDataSource, ...props }: ContentProps) => {
|
||||
@@ -41,6 +44,47 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
|
||||
const [selectedDataSource, setSelectedDataSource] = useState<SecretScanningDataSource | null>(
|
||||
null
|
||||
);
|
||||
const [initialFormData, setInitialFormData] = useState<Partial<TSecretScanningDataSourceForm>>();
|
||||
|
||||
const {
|
||||
location: {
|
||||
search: { connectionId, connectionName, ...search },
|
||||
pathname
|
||||
}
|
||||
} = useRouterState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (connectionId && connectionName) {
|
||||
const storedFormData = localStorage.getItem("secretScanningDataSourceFormData");
|
||||
|
||||
if (!storedFormData) return;
|
||||
|
||||
let form: Partial<TSecretScanningDataSourceForm> = {};
|
||||
try {
|
||||
form = JSON.parse(storedFormData) as TSecretScanningDataSourceForm;
|
||||
} catch {
|
||||
return;
|
||||
} finally {
|
||||
localStorage.removeItem("secretScanningDataSourceFormData");
|
||||
}
|
||||
|
||||
onOpenChange(true);
|
||||
|
||||
setSelectedDataSource(form.type ?? null);
|
||||
|
||||
setInitialFormData({
|
||||
...form,
|
||||
connection: { id: connectionId, name: connectionName }
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: pathname,
|
||||
search
|
||||
});
|
||||
}
|
||||
}, [connectionId, connectionName]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -88,6 +132,7 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
|
||||
}}
|
||||
selectedDataSource={selectedDataSource}
|
||||
setSelectedDataSource={setSelectedDataSource}
|
||||
initialFormData={initialFormData}
|
||||
{...props}
|
||||
/>
|
||||
</ModalContent>
|
||||
|
@@ -1,14 +1,17 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnectionOption } from "@app/components/app-connections";
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP } from "@app/helpers/secretScanningV2";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
|
||||
import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
|
||||
import { TSecretScanningDataSourceForm } from "./schemas";
|
||||
|
||||
@@ -21,19 +24,26 @@ export const SecretScanningDataSourceConnectionField = ({
|
||||
onChange: callback,
|
||||
isUpdate
|
||||
}: Props) => {
|
||||
const { permission } = useOrgPermission();
|
||||
const { control, watch } = useFormContext<TSecretScanningDataSourceForm>();
|
||||
const { permission } = useProjectPermission();
|
||||
const { control, watch, setValue } = useFormContext<TSecretScanningDataSourceForm>();
|
||||
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const);
|
||||
|
||||
const dataSourceType = watch("type");
|
||||
const app = SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[dataSourceType];
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(app);
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(
|
||||
app,
|
||||
currentWorkspace.id
|
||||
);
|
||||
|
||||
const connectionName = APP_CONNECTION_MAP[app].name;
|
||||
|
||||
const canCreateConnection = permission.can(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -67,37 +77,57 @@ export const SecretScanningDataSourceConnectionField = ({
|
||||
<FilterableSelect
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") {
|
||||
handlePopUpOpen("addConnection");
|
||||
onChange(null);
|
||||
// store for oauth callback connections
|
||||
localStorage.setItem("secretScanningDataSourceFormData", JSON.stringify(watch()));
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(newValue);
|
||||
if (callback) callback();
|
||||
}}
|
||||
isLoading={isPending}
|
||||
options={availableConnections}
|
||||
options={[
|
||||
...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []),
|
||||
...(availableConnections ?? [])
|
||||
]}
|
||||
isDisabled={isUpdate}
|
||||
placeholder="Select connection..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
components={{ Option: AppConnectionOption }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="connection"
|
||||
/>
|
||||
{!isUpdate && availableConnections?.length === 0 && (
|
||||
{!isUpdate && !isPending && !availableConnections?.length && !canCreateConnection && (
|
||||
<p className="-mt-2.5 mb-2.5 text-xs text-yellow">
|
||||
<FontAwesomeIcon className="mr-1" size="xs" icon={faInfoCircle} />
|
||||
{canCreateConnection ? (
|
||||
<>
|
||||
You do not have access to any {connectionName} Connections. Create one from the{" "}
|
||||
<Link to="/organization/app-connections" className="underline">
|
||||
App Connections
|
||||
</Link>{" "}
|
||||
page.
|
||||
</>
|
||||
) : (
|
||||
`You do not have access to any ${connectionName} Connections. Contact an admin to create one.`
|
||||
)}
|
||||
You do not have access to any ${connectionName} Connections. Contact an admin to create
|
||||
one.
|
||||
</p>
|
||||
)}
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
// remove form storage, not oauth connection
|
||||
localStorage.removeItem("secretScanningDataSourceFormData");
|
||||
handlePopUpToggle("addConnection", isOpen);
|
||||
}}
|
||||
projectType={currentWorkspace.type}
|
||||
projectId={currentWorkspace.id}
|
||||
app={app}
|
||||
onComplete={(connection) => {
|
||||
if (connection) {
|
||||
setValue("connection", connection);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -27,6 +27,7 @@ type Props = {
|
||||
type: SecretScanningDataSource;
|
||||
onCancel: () => void;
|
||||
dataSource?: TSecretScanningDataSource;
|
||||
initialFormData?: Partial<TSecretScanningDataSourceForm>;
|
||||
};
|
||||
|
||||
const FORM_TABS: { name: string; key: string; fields: (keyof TSecretScanningDataSourceForm)[] }[] =
|
||||
@@ -36,7 +37,13 @@ const FORM_TABS: { name: string; key: string; fields: (keyof TSecretScanningData
|
||||
{ name: "Review", key: "review", fields: [] }
|
||||
];
|
||||
|
||||
export const SecretScanningDataSourceForm = ({ type, onComplete, onCancel, dataSource }: Props) => {
|
||||
export const SecretScanningDataSourceForm = ({
|
||||
type,
|
||||
onComplete,
|
||||
onCancel,
|
||||
dataSource,
|
||||
initialFormData
|
||||
}: Props) => {
|
||||
const createDataSource = useCreateSecretScanningDataSource();
|
||||
const updateDataSource = useUpdateSecretScanningDataSource();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -48,7 +55,8 @@ export const SecretScanningDataSourceForm = ({ type, onComplete, onCancel, dataS
|
||||
resolver: zodResolver(SecretScanningDataSourceSchema),
|
||||
defaultValues: dataSource ?? {
|
||||
type,
|
||||
isAutoScanEnabled: true // scott: this may need to be derived from type in the future
|
||||
isAutoScanEnabled: true, // scott: this may need to be derived from type in the future
|
||||
...(initialFormData as object)
|
||||
},
|
||||
reValidateMode: "onChange"
|
||||
});
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
@@ -11,18 +12,21 @@ type Props = {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
selectSync?: SecretSync | null;
|
||||
initialFormData?: Partial<TSecretSyncForm>;
|
||||
};
|
||||
|
||||
type ContentProps = {
|
||||
onComplete: (secretSync: TSecretSync) => void;
|
||||
selectedSync: SecretSync | null;
|
||||
setSelectedSync: (selectedSync: SecretSync | null) => void;
|
||||
initialFormData?: Partial<TSecretSyncForm>;
|
||||
};
|
||||
|
||||
const Content = ({ onComplete, setSelectedSync, selectedSync }: ContentProps) => {
|
||||
const Content = ({ onComplete, setSelectedSync, selectedSync, initialFormData }: ContentProps) => {
|
||||
if (selectedSync) {
|
||||
return (
|
||||
<CreateSecretSyncForm
|
||||
initialFormData={initialFormData}
|
||||
onComplete={onComplete}
|
||||
onCancel={() => setSelectedSync(null)}
|
||||
destination={selectedSync}
|
||||
@@ -33,7 +37,12 @@ const Content = ({ onComplete, setSelectedSync, selectedSync }: ContentProps) =>
|
||||
return <SecretSyncSelect onSelect={setSelectedSync} />;
|
||||
};
|
||||
|
||||
export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...props }: Props) => {
|
||||
export const CreateSecretSyncModal = ({
|
||||
onOpenChange,
|
||||
selectSync = null,
|
||||
initialFormData,
|
||||
...props
|
||||
}: Props) => {
|
||||
const [selectedSync, setSelectedSync] = useState<SecretSync | null>(selectSync);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,6 +76,7 @@ export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...prop
|
||||
}}
|
||||
selectedSync={selectedSync}
|
||||
setSelectedSync={setSelectedSync}
|
||||
initialFormData={initialFormData}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
@@ -29,6 +29,7 @@ type Props = {
|
||||
onComplete: (secretSync: TSecretSync) => void;
|
||||
destination: SecretSync;
|
||||
onCancel: () => void;
|
||||
initialFormData?: Partial<TSecretSyncForm>;
|
||||
};
|
||||
|
||||
const FORM_TABS: { name: string; key: string; fields: (keyof TSecretSyncForm)[] }[] = [
|
||||
@@ -39,14 +40,20 @@ const FORM_TABS: { name: string; key: string; fields: (keyof TSecretSyncForm)[]
|
||||
{ name: "Review", key: "review", fields: [] }
|
||||
];
|
||||
|
||||
export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Props) => {
|
||||
export const CreateSecretSyncForm = ({
|
||||
destination,
|
||||
onComplete,
|
||||
onCancel,
|
||||
initialFormData
|
||||
}: Props) => {
|
||||
const createSecretSync = useCreateSecretSync();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { name: destinationName } = SECRET_SYNC_MAP[destination];
|
||||
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
// scoot: right now we only do this when creating a connection so we know index 1
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(initialFormData ? 1 : 0);
|
||||
|
||||
const { syncOption } = useSecretSyncOption(destination);
|
||||
|
||||
@@ -59,7 +66,8 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
||||
initialSyncBehavior: syncOption?.canImportSecrets
|
||||
? undefined
|
||||
: SecretSyncInitialSyncBehavior.OverwriteDestination
|
||||
}
|
||||
},
|
||||
...initialFormData
|
||||
} as Partial<TSecretSyncForm>,
|
||||
reValidateMode: "onChange"
|
||||
});
|
||||
|
@@ -1,14 +1,17 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnectionOption } from "@app/components/app-connections";
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { SECRET_SYNC_CONNECTION_MAP } from "@app/helpers/secretSyncs";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
|
||||
import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
|
||||
import { TSecretSyncForm } from "./schemas";
|
||||
|
||||
@@ -17,19 +20,26 @@ type Props = {
|
||||
};
|
||||
|
||||
export const SecretSyncConnectionField = ({ onChange: callback }: Props) => {
|
||||
const { permission } = useOrgPermission();
|
||||
const { control, watch } = useFormContext<TSecretSyncForm>();
|
||||
const { permission } = useProjectPermission();
|
||||
const { control, watch, setValue } = useFormContext<TSecretSyncForm>();
|
||||
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const);
|
||||
|
||||
const destination = watch("destination");
|
||||
const app = SECRET_SYNC_CONNECTION_MAP[destination];
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(app);
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(
|
||||
app,
|
||||
currentWorkspace.id
|
||||
);
|
||||
|
||||
const connectionName = APP_CONNECTION_MAP[app].name;
|
||||
|
||||
const canCreateConnection = permission.can(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
const appName = APP_CONNECTION_MAP[SECRET_SYNC_CONNECTION_MAP[destination]].name;
|
||||
@@ -51,36 +61,55 @@ export const SecretSyncConnectionField = ({ onChange: callback }: Props) => {
|
||||
<FilterableSelect
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") {
|
||||
handlePopUpOpen("addConnection");
|
||||
onChange(null);
|
||||
// store for oauth callback connections
|
||||
localStorage.setItem("secretSyncFormData", JSON.stringify(watch()));
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(newValue);
|
||||
if (callback) callback();
|
||||
}}
|
||||
isLoading={isPending}
|
||||
options={availableConnections}
|
||||
options={[
|
||||
...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []),
|
||||
...(availableConnections ?? [])
|
||||
]}
|
||||
placeholder="Select connection..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
components={{ Option: AppConnectionOption }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="connection"
|
||||
/>
|
||||
{availableConnections?.length === 0 && (
|
||||
{!isPending && !availableConnections?.length && !canCreateConnection && (
|
||||
<p className="-mt-2.5 mb-2.5 text-xs text-yellow">
|
||||
<FontAwesomeIcon className="mr-1" size="xs" icon={faInfoCircle} />
|
||||
{canCreateConnection ? (
|
||||
<>
|
||||
You do not have access to any {appName} Connections. Create one from the{" "}
|
||||
<Link to="/organization/app-connections" className="underline">
|
||||
App Connections
|
||||
</Link>{" "}
|
||||
page.
|
||||
</>
|
||||
) : (
|
||||
`You do not have access to any ${appName} Connections. Contact an admin to create one.`
|
||||
)}
|
||||
You do not have access to any ${appName} Connections. Contact an admin to create one.
|
||||
</p>
|
||||
)}
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
// remove form storage, not oauth connection
|
||||
localStorage.removeItem("secretSyncFormData");
|
||||
handlePopUpToggle("addConnection", isOpen);
|
||||
}}
|
||||
projectType={currentWorkspace.type}
|
||||
projectId={currentWorkspace.id}
|
||||
app={app}
|
||||
onComplete={(connection) => {
|
||||
if (connection) {
|
||||
setValue("connection", connection);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { MongoAbility } from "@casl/ability";
|
||||
import { ForcedSubject, MongoAbility } from "@casl/ability";
|
||||
|
||||
export enum OrgPermissionActions {
|
||||
Read = "read",
|
||||
@@ -120,7 +120,6 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||
| [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections]
|
||||
| [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity]
|
||||
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
|
||||
| [
|
||||
@@ -128,14 +127,13 @@ export type OrgPermissionSet =
|
||||
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||
]
|
||||
| [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]
|
||||
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
|
||||
// TODO(scott): add back once org UI refactored
|
||||
// | [
|
||||
// OrgPermissionAppConnectionActions,
|
||||
// (
|
||||
// | OrgPermissionSubjects.AppConnections
|
||||
// | (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
||||
// )
|
||||
// ];
|
||||
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare]
|
||||
| [
|
||||
OrgPermissionAppConnectionActions,
|
||||
(
|
||||
| OrgPermissionSubjects.AppConnections
|
||||
| (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
||||
)
|
||||
];
|
||||
|
||||
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
||||
|
@@ -143,6 +143,14 @@ export enum ProjectPermissionSecretScanningConfigActions {
|
||||
Update = "update-configs"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionAppConnectionActions {
|
||||
Read = "read-app-connections",
|
||||
Create = "create-app-connections",
|
||||
Edit = "edit-app-connections",
|
||||
Delete = "delete-app-connections",
|
||||
Connect = "connect-app-connections"
|
||||
}
|
||||
|
||||
export enum PermissionConditionOperators {
|
||||
$IN = "$in",
|
||||
$ALL = "$all",
|
||||
@@ -162,6 +170,10 @@ export type IdentityManagementSubjectFields = {
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type AppConnectionSubjectFields = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type ConditionalProjectPermissionSubject =
|
||||
| ProjectPermissionSub.SecretSyncs
|
||||
| ProjectPermissionSub.Secrets
|
||||
@@ -172,7 +184,8 @@ export type ConditionalProjectPermissionSubject =
|
||||
| ProjectPermissionSub.CertificateTemplates
|
||||
| ProjectPermissionSub.SecretFolders
|
||||
| ProjectPermissionSub.SecretImports
|
||||
| ProjectPermissionSub.SecretRotation;
|
||||
| ProjectPermissionSub.SecretRotation
|
||||
| ProjectPermissionSub.AppConnections;
|
||||
|
||||
export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = {
|
||||
[PermissionConditionOperators.$EQ]: "equal to",
|
||||
@@ -250,7 +263,8 @@ export enum ProjectPermissionSub {
|
||||
Commits = "commits",
|
||||
SecretScanningDataSources = "secret-scanning-data-sources",
|
||||
SecretScanningFindings = "secret-scanning-findings",
|
||||
SecretScanningConfigs = "secret-scanning-configs"
|
||||
SecretScanningConfigs = "secret-scanning-configs",
|
||||
AppConnections = "app-connections"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -403,6 +417,13 @@ export type ProjectPermissionSet =
|
||||
ProjectPermissionSub.SecretScanningDataSources
|
||||
]
|
||||
| [ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub.SecretScanningFindings]
|
||||
| [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs];
|
||||
| [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs]
|
||||
| [
|
||||
ProjectPermissionAppConnectionActions,
|
||||
(
|
||||
| ProjectPermissionSub.AppConnections
|
||||
| (ForcedSubject<ProjectPermissionSub.AppConnections> & AppConnectionSubjectFields)
|
||||
)
|
||||
];
|
||||
|
||||
export type TProjectPermission = MongoAbility<ProjectPermissionSet>;
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
faServer,
|
||||
faUser
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { useRouterState } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import {
|
||||
@@ -50,6 +51,7 @@ import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-con
|
||||
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
|
||||
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection";
|
||||
import { SupabaseConnectionMethod } from "@app/hooks/api/appConnections/types/supabase-connection";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export const APP_CONNECTION_MAP: Record<
|
||||
AppConnection,
|
||||
@@ -218,3 +220,18 @@ export const AWS_REGIONS = [
|
||||
{ name: "AWS GovCloud (US-East)", slug: "us-gov-east-1" },
|
||||
{ name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" }
|
||||
];
|
||||
|
||||
type Params = {
|
||||
projectId: string | undefined | null;
|
||||
projectType: ProjectType | undefined | null;
|
||||
};
|
||||
|
||||
export const useGetAppConnectionOauthReturnUrl = ({ projectId, projectType }: Params) => {
|
||||
const {
|
||||
location: { pathname }
|
||||
} = useRouterState();
|
||||
|
||||
if (!projectId || !projectType) return undefined;
|
||||
|
||||
return pathname;
|
||||
};
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
TAppConnectionResponse,
|
||||
TCreateAppConnectionDTO,
|
||||
TDeleteAppConnectionDTO,
|
||||
TMigrateAppConnectionDTO,
|
||||
TUpdateAppConnectionDTO
|
||||
} from "@app/hooks/api/appConnections/types";
|
||||
|
||||
@@ -20,7 +21,10 @@ export const useCreateAppConnection = () => {
|
||||
|
||||
return data.appConnection;
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() })
|
||||
onSuccess: ({ projectId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,9 +39,10 @@ export const useUpdateAppConnection = () => {
|
||||
|
||||
return data.appConnection;
|
||||
},
|
||||
onSuccess: (_, { connectionId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
onSuccess: ({ projectId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) });
|
||||
// queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -50,9 +55,28 @@ export const useDeleteAppConnection = () => {
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { connectionId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
onSuccess: ({ projectId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) });
|
||||
// queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useMigrateAppConnection = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ connectionId, app }: TMigrateAppConnectionDTO) => {
|
||||
const { data } = await apiRequest.post(
|
||||
`/api/v1/app-connections/${app}/${connectionId}/migrate`
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: ({ projectId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) });
|
||||
// queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -4,30 +4,36 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import {
|
||||
AppConnectionUsage,
|
||||
TAppConnection,
|
||||
TAppConnectionMap,
|
||||
TAppConnectionOptions,
|
||||
TAvailableAppConnection,
|
||||
TAvailableAppConnectionsResponse,
|
||||
TGetAppConnection,
|
||||
TListAppConnections
|
||||
} from "@app/hooks/api/appConnections/types";
|
||||
import {
|
||||
TAppConnectionOption,
|
||||
TAppConnectionOptionMap
|
||||
} from "@app/hooks/api/appConnections/types/app-options";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
export const appConnectionKeys = {
|
||||
all: ["app-connection"] as const,
|
||||
options: () => [...appConnectionKeys.all, "options"] as const,
|
||||
list: () => [...appConnectionKeys.all, "list"] as const,
|
||||
listAvailable: (app: AppConnection) => [...appConnectionKeys.all, app, "list-available"] as const,
|
||||
listByApp: (app: AppConnection) => [...appConnectionKeys.list(), app],
|
||||
byId: (app: AppConnection, connectionId: string) =>
|
||||
[...appConnectionKeys.all, app, "by-id", connectionId] as const
|
||||
options: (projectType?: ProjectType) =>
|
||||
[...appConnectionKeys.all, "options", ...(projectType ? [projectType] : [])] as const,
|
||||
list: (projectId?: string | null) =>
|
||||
[...appConnectionKeys.all, "list", ...(projectId ? [projectId] : [])] as const,
|
||||
listAvailable: (app: AppConnection, projectId?: string | null) =>
|
||||
[...appConnectionKeys.all, app, "list-available", ...(projectId ? [projectId] : [])] as const,
|
||||
getUsage: (app: AppConnection, connectionId: string) =>
|
||||
[...appConnectionKeys.all, "usage", app, connectionId] as const
|
||||
// listByApp: (app: AppConnection) => [...appConnectionKeys.list(), app],
|
||||
// byId: (app: AppConnection, connectionId: string) =>
|
||||
// [...appConnectionKeys.all, app, "by-id", connectionId] as const
|
||||
};
|
||||
|
||||
export const useAppConnectionOptions = (
|
||||
projectType?: ProjectType,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAppConnectionOption[],
|
||||
@@ -39,10 +45,11 @@ export const useAppConnectionOptions = (
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.options(),
|
||||
queryKey: appConnectionKeys.options(projectType),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TAppConnectionOptions>(
|
||||
"/api/v1/app-connections/options"
|
||||
"/api/v1/app-connections/options",
|
||||
{ params: { projectType } }
|
||||
);
|
||||
|
||||
return data.appConnectionOptions;
|
||||
@@ -64,6 +71,7 @@ export const useGetAppConnectionOption = <T extends AppConnection>(app: T) => {
|
||||
};
|
||||
|
||||
export const useListAppConnections = (
|
||||
projectId?: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAppConnection[],
|
||||
@@ -75,10 +83,12 @@ export const useListAppConnections = (
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.list(),
|
||||
queryKey: appConnectionKeys.list(projectId),
|
||||
queryFn: async () => {
|
||||
const { data } =
|
||||
await apiRequest.get<TListAppConnections<TAppConnection>>("/api/v1/app-connections");
|
||||
const { data } = await apiRequest.get<TListAppConnections<TAppConnection>>(
|
||||
"/api/v1/app-connections",
|
||||
{ params: { projectId } }
|
||||
);
|
||||
|
||||
return data.appConnections;
|
||||
},
|
||||
@@ -88,6 +98,7 @@ export const useListAppConnections = (
|
||||
|
||||
export const useListAvailableAppConnections = (
|
||||
app: AppConnection,
|
||||
projectId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAvailableAppConnection[],
|
||||
@@ -99,10 +110,11 @@ export const useListAvailableAppConnections = (
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.listAvailable(app),
|
||||
queryKey: appConnectionKeys.listAvailable(app, projectId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TAvailableAppConnectionsResponse>(
|
||||
`/api/v1/app-connections/${app}/available`
|
||||
`/api/v1/app-connections/${app}/available`,
|
||||
{ params: { projectId } }
|
||||
);
|
||||
|
||||
return data.appConnections;
|
||||
@@ -111,53 +123,80 @@ export const useListAvailableAppConnections = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useListAppConnectionsByApp = <T extends AppConnection>(
|
||||
app: T,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAppConnectionMap[T][],
|
||||
unknown,
|
||||
TAppConnectionMap[T][],
|
||||
ReturnType<typeof appConnectionKeys.listByApp>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.listByApp(app),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TListAppConnections<TAppConnectionMap[T]>>(
|
||||
`/api/v1/app-connections/${app}`
|
||||
);
|
||||
|
||||
return data.appConnections;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetAppConnectionById = <T extends AppConnection>(
|
||||
app: T,
|
||||
export const useGetAppConnectionUsageById = (
|
||||
app: AppConnection,
|
||||
connectionId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAppConnectionMap[T],
|
||||
AppConnectionUsage,
|
||||
unknown,
|
||||
TAppConnectionMap[T],
|
||||
ReturnType<typeof appConnectionKeys.byId>
|
||||
AppConnectionUsage,
|
||||
ReturnType<typeof appConnectionKeys.getUsage>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.byId(app, connectionId),
|
||||
queryKey: appConnectionKeys.getUsage(app, connectionId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TGetAppConnection<TAppConnectionMap[T]>>(
|
||||
`/api/v1/app-connections/${app}/${connectionId}`
|
||||
const { data } = await apiRequest.get<AppConnectionUsage>(
|
||||
`/api/v1/app-connections/${app}/${connectionId}/usage`
|
||||
);
|
||||
|
||||
return data.appConnection;
|
||||
return data;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
// scott: may need these in the future but not using now
|
||||
// export const useListAppConnectionsByApp = <T extends AppConnection>(
|
||||
// app: T,
|
||||
// options?: Omit<
|
||||
// UseQueryOptions<
|
||||
// TAppConnectionMap[T][],
|
||||
// unknown,
|
||||
// TAppConnectionMap[T][],
|
||||
// ReturnType<typeof appConnectionKeys.listByApp>
|
||||
// >,
|
||||
// "queryKey" | "queryFn"
|
||||
// >
|
||||
// ) => {
|
||||
// return useQuery({
|
||||
// queryKey: appConnectionKeys.listByApp(app),
|
||||
// queryFn: async () => {
|
||||
// const { data } = await apiRequest.get<TListAppConnections<TAppConnectionMap[T]>>(
|
||||
// `/api/v1/app-connections/${app}`
|
||||
// );
|
||||
//
|
||||
// return data.appConnections;
|
||||
// },
|
||||
// ...options
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// export const useGetAppConnectionById = <T extends AppConnection>(
|
||||
// app: T,
|
||||
// connectionId: string,
|
||||
// options?: Omit<
|
||||
// UseQueryOptions<
|
||||
// TAppConnectionMap[T],
|
||||
// unknown,
|
||||
// TAppConnectionMap[T],
|
||||
// ReturnType<typeof appConnectionKeys.byId>
|
||||
// >,
|
||||
// "queryKey" | "queryFn"
|
||||
// >
|
||||
// ) => {
|
||||
// return useQuery({
|
||||
// queryKey: appConnectionKeys.byId(app, connectionId),
|
||||
// queryFn: async () => {
|
||||
// const { data } = await apiRequest.get<TGetAppConnection<TAppConnectionMap[T]>>(
|
||||
// `/api/v1/app-connections/${app}/${connectionId}`
|
||||
// );
|
||||
//
|
||||
// return data.appConnection;
|
||||
// },
|
||||
// ...options
|
||||
// });
|
||||
// };
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { AppConnection } from "../enums";
|
||||
import { TOnePassConnection } from "./1password-connection";
|
||||
import { TAppConnectionOption } from "./app-options";
|
||||
@@ -113,7 +115,7 @@ export type TAppConnection =
|
||||
| TNetlifyConnection
|
||||
| TOktaConnection;
|
||||
|
||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id" | "projectId">;
|
||||
|
||||
export type TListAppConnections<T extends TAppConnection> = { appConnections: T[] };
|
||||
export type TGetAppConnection<T extends TAppConnection> = { appConnection: T };
|
||||
@@ -130,7 +132,7 @@ export type TCreateAppConnectionDTO = Pick<
|
||||
| "description"
|
||||
| "isPlatformManagedCredentials"
|
||||
| "gatewayId"
|
||||
>;
|
||||
> & { projectId: string };
|
||||
|
||||
export type TUpdateAppConnectionDTO = Partial<
|
||||
Pick<
|
||||
@@ -147,42 +149,63 @@ export type TDeleteAppConnectionDTO = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type TAppConnectionMap = {
|
||||
[AppConnection.AWS]: TAwsConnection;
|
||||
[AppConnection.GitHub]: TGitHubConnection;
|
||||
[AppConnection.GitHubRadar]: TGitHubRadarConnection;
|
||||
[AppConnection.GCP]: TGcpConnection;
|
||||
[AppConnection.AzureKeyVault]: TAzureKeyVaultConnection;
|
||||
[AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection;
|
||||
[AppConnection.AzureClientSecrets]: TAzureClientSecretsConnection;
|
||||
[AppConnection.AzureDevOps]: TAzureDevOpsConnection;
|
||||
[AppConnection.Databricks]: TDatabricksConnection;
|
||||
[AppConnection.Humanitec]: THumanitecConnection;
|
||||
[AppConnection.TerraformCloud]: TTerraformCloudConnection;
|
||||
[AppConnection.Vercel]: TVercelConnection;
|
||||
[AppConnection.Postgres]: TPostgresConnection;
|
||||
[AppConnection.MsSql]: TMsSqlConnection;
|
||||
[AppConnection.MySql]: TMySqlConnection;
|
||||
[AppConnection.OracleDB]: TOracleDBConnection;
|
||||
[AppConnection.Camunda]: TCamundaConnection;
|
||||
[AppConnection.Windmill]: TWindmillConnection;
|
||||
[AppConnection.Auth0]: TAuth0Connection;
|
||||
[AppConnection.HCVault]: THCVaultConnection;
|
||||
[AppConnection.LDAP]: TLdapConnection;
|
||||
[AppConnection.TeamCity]: TTeamCityConnection;
|
||||
[AppConnection.OCI]: TOCIConnection;
|
||||
[AppConnection.OnePass]: TOnePassConnection;
|
||||
[AppConnection.Heroku]: THerokuConnection;
|
||||
[AppConnection.Render]: TRenderConnection;
|
||||
[AppConnection.Flyio]: TFlyioConnection;
|
||||
[AppConnection.GitLab]: TGitLabConnection;
|
||||
[AppConnection.Cloudflare]: TCloudflareConnection;
|
||||
[AppConnection.Bitbucket]: TBitbucketConnection;
|
||||
[AppConnection.Zabbix]: TZabbixConnection;
|
||||
[AppConnection.Railway]: TRailwayConnection;
|
||||
[AppConnection.Checkly]: TChecklyConnection;
|
||||
[AppConnection.Supabase]: TSupabaseConnection;
|
||||
[AppConnection.DigitalOcean]: TDigitalOceanConnection;
|
||||
[AppConnection.Netlify]: TNetlifyConnection;
|
||||
[AppConnection.Okta]: TOktaConnection;
|
||||
export type TMigrateAppConnectionDTO = {
|
||||
app: AppConnection;
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
// scott: may need this once we have individual page
|
||||
// export type TAppConnectionMap = {
|
||||
// [AppConnection.AWS]: TAwsConnection;
|
||||
// [AppConnection.GitHub]: TGitHubConnection;
|
||||
// [AppConnection.GitHubRadar]: TGitHubRadarConnection;
|
||||
// [AppConnection.GCP]: TGcpConnection;
|
||||
// [AppConnection.AzureKeyVault]: TAzureKeyVaultConnection;
|
||||
// [AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection;
|
||||
// [AppConnection.AzureClientSecrets]: TAzureClientSecretsConnection;
|
||||
// [AppConnection.AzureDevOps]: TAzureDevOpsConnection;
|
||||
// [AppConnection.Databricks]: TDatabricksConnection;
|
||||
// [AppConnection.Humanitec]: THumanitecConnection;
|
||||
// [AppConnection.TerraformCloud]: TTerraformCloudConnection;
|
||||
// [AppConnection.Vercel]: TVercelConnection;
|
||||
// [AppConnection.Postgres]: TPostgresConnection;
|
||||
// [AppConnection.MsSql]: TMsSqlConnection;
|
||||
// [AppConnection.MySql]: TMySqlConnection;
|
||||
// [AppConnection.OracleDB]: TOracleDBConnection;
|
||||
// [AppConnection.Camunda]: TCamundaConnection;
|
||||
// [AppConnection.Windmill]: TWindmillConnection;
|
||||
// [AppConnection.Auth0]: TAuth0Connection;
|
||||
// [AppConnection.HCVault]: THCVaultConnection;
|
||||
// [AppConnection.LDAP]: TLdapConnection;
|
||||
// [AppConnection.TeamCity]: TTeamCityConnection;
|
||||
// [AppConnection.OCI]: TOCIConnection;
|
||||
// [AppConnection.OnePass]: TOnePassConnection;
|
||||
// [AppConnection.Heroku]: THerokuConnection;
|
||||
// [AppConnection.Render]: TRenderConnection;
|
||||
// [AppConnection.Flyio]: TFlyioConnection;
|
||||
// [AppConnection.GitLab]: TGitLabConnection;
|
||||
// [AppConnection.Cloudflare]: TCloudflareConnection;
|
||||
// [AppConnection.Bitbucket]: TBitbucketConnection;
|
||||
// [AppConnection.Zabbix]: TZabbixConnection;
|
||||
// [AppConnection.Railway]: TRailwayConnection;
|
||||
// [AppConnection.Checkly]: TChecklyConnection;
|
||||
// [AppConnection.Supabase]: TSupabaseConnection;
|
||||
// [AppConnection.DigitalOcean]: TDigitalOceanConnection;
|
||||
// [AppConnection.Netlify]: TNetlifyConnection;
|
||||
// [AppConnection.Okta]: TOktaConnection;
|
||||
// };
|
||||
|
||||
export type AppConnectionUsage = {
|
||||
projects: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
type: ProjectType;
|
||||
resources: {
|
||||
secretSyncs: Array<{ id: string; name: string }>;
|
||||
externalCas: Array<{ id: string; name: string }>;
|
||||
secretRotations: Array<{ id: string; name: string }>;
|
||||
dataSources: Array<{ id: string; name: string }>;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
|
@@ -8,4 +8,5 @@ export type TRootAppConnection = {
|
||||
updatedAt: string;
|
||||
isPlatformManagedCredentials?: boolean;
|
||||
gatewayId?: string | null;
|
||||
projectId?: string | null;
|
||||
};
|
||||
|
@@ -131,6 +131,8 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.CREATE_APP_CONNECTION]: "Create App Connection",
|
||||
[EventType.UPDATE_APP_CONNECTION]: "Update App Connection",
|
||||
[EventType.DELETE_APP_CONNECTION]: "Delete App Connection",
|
||||
[EventType.GET_APP_CONNECTION_USAGE]: "Get App Connection Usage",
|
||||
[EventType.MIGRATE_APP_CONNECTION]: "Migrate App Connection",
|
||||
[EventType.GET_SECRET_SYNCS]: "List secret syncs",
|
||||
[EventType.GET_SECRET_SYNC]: "Get Secret Sync",
|
||||
[EventType.CREATE_SECRET_SYNC]: "Create Secret Sync",
|
||||
|
@@ -139,6 +139,8 @@ export enum EventType {
|
||||
CREATE_APP_CONNECTION = "create-app-connection",
|
||||
UPDATE_APP_CONNECTION = "update-app-connection",
|
||||
DELETE_APP_CONNECTION = "delete-app-connection",
|
||||
GET_APP_CONNECTION_USAGE = "get-app-connection-usage",
|
||||
MIGRATE_APP_CONNECTION = "migrate-app-connection",
|
||||
GET_SECRET_SYNCS = "get-secret-syncs",
|
||||
GET_SECRET_SYNC = "get-secret-sync",
|
||||
CREATE_SECRET_SYNC = "create-secret-sync",
|
||||
|
@@ -16,9 +16,17 @@ import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
||||
import { Menu, MenuGroup, MenuItem, Tooltip } from "@app/components/v2";
|
||||
import { useOrganization, useSubscription, useUser } from "@app/context";
|
||||
import {
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useOrgPermission,
|
||||
useSubscription,
|
||||
useUser
|
||||
} from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useGetOrgTrialUrl } from "@app/hooks/api";
|
||||
import { useListAppConnections } from "@app/hooks/api/appConnections";
|
||||
|
||||
type Props = {
|
||||
isHidden?: boolean;
|
||||
@@ -34,6 +42,17 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
|
||||
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
|
||||
|
||||
const { permission } = useOrgPermission();
|
||||
|
||||
const canReadAppConnections = permission.can(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
|
||||
const { data: appConnections = [] } = useListAppConnections(undefined, {
|
||||
enabled: canReadAppConnections
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence mode="popLayout">
|
||||
@@ -112,18 +131,20 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Resources">
|
||||
<Link to="/organization/app-connections">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} className="mr-4" />
|
||||
{appConnections.length > 0 && (
|
||||
<Link to="/organization/app-connections">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} className="mr-4" />
|
||||
</div>
|
||||
App Connections
|
||||
</div>
|
||||
App Connections
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
<Link to="/organization/gateways">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
faFileLines,
|
||||
faHome,
|
||||
faMobile,
|
||||
faPlug,
|
||||
faSitemap,
|
||||
faStamp,
|
||||
faUsers
|
||||
@@ -130,6 +131,23 @@ export const PkiManagerLayout = () => {
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/app-connections"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} />
|
||||
</div>
|
||||
App Connections
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Others">
|
||||
<Link
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
faCog,
|
||||
faHome,
|
||||
faMobile,
|
||||
faPlug,
|
||||
faPuzzlePiece,
|
||||
faUsers,
|
||||
faVault
|
||||
@@ -148,6 +149,23 @@ export const SecretManagerLayout = () => {
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/app-connections"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} />
|
||||
</div>
|
||||
App Connections
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Others">
|
||||
<Link
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
faDatabase,
|
||||
faHome,
|
||||
faMagnifyingGlass,
|
||||
faPlug,
|
||||
faUsers
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -100,6 +101,23 @@ export const SecretScanningLayout = () => {
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/projects/secret-scanning/$projectId/app-connections"
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} />
|
||||
</div>
|
||||
App Connections
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Others">
|
||||
<Link
|
||||
|
@@ -4,6 +4,7 @@ import { SingleValue } from "react-select";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AppConnectionOption } from "@app/components/app-connections";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
@@ -119,12 +120,12 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const dnsProvider = watch("configuration.dnsProviderConfig.provider");
|
||||
|
||||
const { data: availableRoute53Connections, isPending: isRoute53Pending } =
|
||||
useListAvailableAppConnections(AppConnection.AWS, {
|
||||
useListAvailableAppConnections(AppConnection.AWS, currentWorkspace.id, {
|
||||
enabled: caType === CaType.ACME
|
||||
});
|
||||
|
||||
const { data: availableCloudflareConnections, isPending: isCloudflarePending } =
|
||||
useListAvailableAppConnections(AppConnection.Cloudflare, {
|
||||
useListAvailableAppConnections(AppConnection.Cloudflare, currentWorkspace.id, {
|
||||
enabled: caType === CaType.ACME
|
||||
});
|
||||
|
||||
@@ -322,6 +323,7 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
placeholder="Select connection..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
components={{ Option: AppConnectionOption }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
|
@@ -1,24 +1,17 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, PageHeader } from "@app/components/v2";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionAppConnectionActions,
|
||||
OrgPermissionSubjects
|
||||
} from "@app/context/OrgPermissionContext/types";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
AddAppConnectionModal,
|
||||
AppConnectionsTable
|
||||
} from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
import { AppConnectionsTable } from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
|
||||
export const AppConnectionsPage = withPermission(
|
||||
() => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addConnection"] as const);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800">
|
||||
<Helmet>
|
||||
@@ -30,54 +23,28 @@ export const AppConnectionsPage = withPermission(
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
className="w-full"
|
||||
title={
|
||||
<div className="flex w-full items-center">
|
||||
<span>App Connections</span>
|
||||
<a
|
||||
className="-mt-1.5"
|
||||
href="https://infisical.com/docs/integrations/app-connections/overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="ml-2 inline-block rounded-md bg-yellow/20 px-1.5 text-sm font-normal text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
<span>Docs</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.07rem] ml-1.5 text-[10px]"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionAppConnectionActions.Create}
|
||||
a={OrgPermissionSubjects.AppConnections}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("addConnection");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
className="ml-auto"
|
||||
>
|
||||
Add Connection
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
}
|
||||
description="Create and configure connections with third-party apps for re-use across Infisical projects"
|
||||
title="App Connections"
|
||||
description="Manage organization App Connections"
|
||||
/>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<AppConnectionsTable />
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addConnection", isOpen)}
|
||||
/>
|
||||
<div className="mb-4 flex w-full flex-col rounded-md border border-yellow/50 bg-yellow/30 px-4 py-2 text-sm text-yellow-200">
|
||||
<div className="flex items-center">
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="mr-2 mt-[0.1rem] text-base" />
|
||||
<span className="text-base text-yellow-200">
|
||||
App Connections have moved to projects
|
||||
</span>
|
||||
</div>
|
||||
<div className="ml-[1.6rem]">
|
||||
<p>
|
||||
You can continue to use your existing App Connections but can no longer create
|
||||
them at the organization-level.
|
||||
</p>
|
||||
<p>
|
||||
Organization admins can migrate organization-level App Connections to projects via
|
||||
the dropdown on the connections table.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<AppConnectionsTable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -3,6 +3,7 @@ import { useState } from "react";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { TAppConnection } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { AppConnectionForm } from "./AppConnectionForm";
|
||||
import { AppConnectionsSelect } from "./AppConnectionList";
|
||||
@@ -10,29 +11,45 @@ import { AppConnectionsSelect } from "./AppConnectionList";
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
projectId?: string;
|
||||
projectType?: ProjectType;
|
||||
app?: AppConnection;
|
||||
onComplete?: (appConnection: TAppConnection) => void;
|
||||
};
|
||||
|
||||
type ContentProps = {
|
||||
onComplete: (appConnection: TAppConnection) => void;
|
||||
projectId?: string;
|
||||
projectType?: ProjectType;
|
||||
app?: AppConnection;
|
||||
};
|
||||
|
||||
const Content = ({ onComplete }: ContentProps) => {
|
||||
const Content = ({ onComplete, projectId, projectType, app }: ContentProps) => {
|
||||
const [selectedApp, setSelectedApp] = useState<AppConnection | null>(null);
|
||||
|
||||
if (selectedApp) {
|
||||
if (app ?? selectedApp) {
|
||||
return (
|
||||
<AppConnectionForm
|
||||
onComplete={onComplete}
|
||||
onBack={() => setSelectedApp(null)}
|
||||
app={selectedApp}
|
||||
app={(app ?? selectedApp)!}
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <AppConnectionsSelect onSelect={setSelectedApp} />;
|
||||
return <AppConnectionsSelect onSelect={setSelectedApp} projectType={projectType} />;
|
||||
};
|
||||
|
||||
export const AddAppConnectionModal = ({ isOpen, onOpenChange }: Props) => {
|
||||
export const AddAppConnectionModal = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
projectId,
|
||||
projectType,
|
||||
app,
|
||||
onComplete
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
@@ -40,7 +57,15 @@ export const AddAppConnectionModal = ({ isOpen, onOpenChange }: Props) => {
|
||||
title="Add Connection"
|
||||
subTitle="Select a third-party app to connect to."
|
||||
>
|
||||
<Content onComplete={() => onOpenChange(false)} />
|
||||
<Content
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
app={app}
|
||||
onComplete={(appConnection) => {
|
||||
if (onComplete) onComplete(appConnection);
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
useUpdateAppConnection
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { DiscriminativePick } from "@app/types";
|
||||
|
||||
import { AppConnectionHeader } from "../AppConnectionHeader";
|
||||
@@ -49,14 +50,18 @@ import { ZabbixConnectionForm } from "./ZabbixConnectionForm";
|
||||
|
||||
type FormProps = {
|
||||
onComplete: (appConnection: TAppConnection) => void;
|
||||
projectType?: ProjectType;
|
||||
} & ({ appConnection: TAppConnection } | { app: AppConnection });
|
||||
|
||||
type CreateFormProps = FormProps & { app: AppConnection };
|
||||
type CreateFormProps = FormProps & {
|
||||
app: AppConnection;
|
||||
projectId: string;
|
||||
};
|
||||
type UpdateFormProps = FormProps & {
|
||||
appConnection: TAppConnection;
|
||||
};
|
||||
|
||||
const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
const CreateForm = ({ app, onComplete, projectId, projectType }: CreateFormProps) => {
|
||||
const createAppConnection = useCreateAppConnection();
|
||||
const { name: appName } = APP_CONNECTION_MAP[app];
|
||||
|
||||
@@ -67,7 +72,10 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
>
|
||||
) => {
|
||||
try {
|
||||
const connection = await createAppConnection.mutateAsync(formData);
|
||||
const connection = await createAppConnection.mutateAsync({
|
||||
...formData,
|
||||
projectId: projectId!
|
||||
});
|
||||
createNotification({
|
||||
text: `Successfully added ${appName} Connection`,
|
||||
type: "success"
|
||||
@@ -87,15 +95,27 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
case AppConnection.AWS:
|
||||
return <AwsConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.GitHub:
|
||||
return <GitHubConnectionForm />;
|
||||
return <GitHubConnectionForm projectId={projectId} projectType={projectType} />;
|
||||
case AppConnection.GitHubRadar:
|
||||
return <GitHubRadarConnectionForm />;
|
||||
return <GitHubRadarConnectionForm projectId={projectId} projectType={projectType} />;
|
||||
case AppConnection.GCP:
|
||||
return <GcpConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureKeyVault:
|
||||
return <AzureKeyVaultConnectionForm onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureKeyVaultConnectionForm
|
||||
onSubmit={onSubmit}
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.AzureAppConfiguration:
|
||||
return <AzureAppConfigurationConnectionForm onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureAppConfigurationConnectionForm
|
||||
onSubmit={onSubmit}
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.Databricks:
|
||||
return <DatabricksConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Humanitec:
|
||||
@@ -115,9 +135,21 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
case AppConnection.Camunda:
|
||||
return <CamundaConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureClientSecrets:
|
||||
return <AzureClientSecretsConnectionForm onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureClientSecretsConnectionForm
|
||||
onSubmit={onSubmit}
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.AzureDevOps:
|
||||
return <AzureDevOpsConnectionForm onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureDevOpsConnectionForm
|
||||
onSubmit={onSubmit}
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.Windmill:
|
||||
return <WindmillConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Auth0:
|
||||
@@ -139,7 +171,9 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
case AppConnection.Flyio:
|
||||
return <FlyioConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.GitLab:
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} />;
|
||||
return (
|
||||
<GitLabConnectionForm onSubmit={onSubmit} projectId={projectId} projectType={projectType} />
|
||||
);
|
||||
case AppConnection.Cloudflare:
|
||||
return <CloudflareConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Bitbucket:
|
||||
@@ -163,7 +197,7 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
const UpdateForm = ({ appConnection, onComplete, projectType }: UpdateFormProps) => {
|
||||
const updateAppConnection = useUpdateAppConnection();
|
||||
const { name: appName } = APP_CONNECTION_MAP[appConnection.app];
|
||||
|
||||
@@ -197,16 +231,40 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
case AppConnection.AWS:
|
||||
return <AwsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.GitHub:
|
||||
return <GitHubConnectionForm appConnection={appConnection} />;
|
||||
return (
|
||||
<GitHubConnectionForm
|
||||
appConnection={appConnection}
|
||||
projectId={appConnection.projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.GitHubRadar:
|
||||
return <GitHubRadarConnectionForm appConnection={appConnection} />;
|
||||
return (
|
||||
<GitHubRadarConnectionForm
|
||||
appConnection={appConnection}
|
||||
projectId={appConnection.projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.GCP:
|
||||
return <GcpConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureKeyVault:
|
||||
return <AzureKeyVaultConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureKeyVaultConnectionForm
|
||||
appConnection={appConnection}
|
||||
onSubmit={onSubmit}
|
||||
projectId={appConnection.projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.AzureAppConfiguration:
|
||||
return (
|
||||
<AzureAppConfigurationConnectionForm appConnection={appConnection} onSubmit={onSubmit} />
|
||||
<AzureAppConfigurationConnectionForm
|
||||
appConnection={appConnection}
|
||||
onSubmit={onSubmit}
|
||||
projectId={appConnection.projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.Databricks:
|
||||
return <DatabricksConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
@@ -227,9 +285,23 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
case AppConnection.Camunda:
|
||||
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.AzureClientSecrets:
|
||||
return <AzureClientSecretsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureClientSecretsConnectionForm
|
||||
appConnection={appConnection}
|
||||
onSubmit={onSubmit}
|
||||
projectId={appConnection.projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.AzureDevOps:
|
||||
return <AzureDevOpsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureDevOpsConnectionForm
|
||||
appConnection={appConnection}
|
||||
onSubmit={onSubmit}
|
||||
projectId={appConnection.projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.Windmill:
|
||||
return <WindmillConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Auth0:
|
||||
@@ -251,7 +323,14 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
case AppConnection.Flyio:
|
||||
return <FlyioConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.GitLab:
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
return (
|
||||
<GitLabConnectionForm
|
||||
onSubmit={onSubmit}
|
||||
appConnection={appConnection}
|
||||
projectId={appConnection.projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
);
|
||||
case AppConnection.Cloudflare:
|
||||
return <CloudflareConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Bitbucket:
|
||||
@@ -273,12 +352,15 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
type Props = { onBack?: () => void } & Pick<FormProps, "onComplete"> &
|
||||
type Props = { onBack?: () => void; projectId?: string; projectType?: ProjectType } & Pick<
|
||||
FormProps,
|
||||
"onComplete"
|
||||
> &
|
||||
(
|
||||
| { app: AppConnection; appConnection?: undefined }
|
||||
| { app?: undefined; appConnection: TAppConnection }
|
||||
);
|
||||
export const AppConnectionForm = ({ onBack, ...props }: Props) => {
|
||||
export const AppConnectionForm = ({ onBack, projectId, projectType, ...props }: Props) => {
|
||||
const { app, appConnection } = props;
|
||||
|
||||
return (
|
||||
@@ -289,9 +371,9 @@ export const AppConnectionForm = ({ onBack, ...props }: Props) => {
|
||||
onBack={onBack}
|
||||
/>
|
||||
{appConnection ? (
|
||||
<UpdateForm {...props} appConnection={appConnection} />
|
||||
<UpdateForm {...props} appConnection={appConnection} projectType={projectType} />
|
||||
) : (
|
||||
<CreateForm {...props} app={app} />
|
||||
<CreateForm {...props} app={app} projectId={projectId!} projectType={projectType} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@@ -6,7 +6,11 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
useGetAppConnectionOauthReturnUrl
|
||||
} from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import {
|
||||
AzureAppConfigurationConnectionMethod,
|
||||
@@ -14,7 +18,9 @@ import {
|
||||
useGetAppConnectionOption
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { AzureAppConfigurationFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
@@ -25,6 +31,8 @@ type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
type Props = {
|
||||
appConnection?: TAzureAppConfigurationConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
projectId: string | undefined | null;
|
||||
projectType: ProjectType | undefined | null;
|
||||
};
|
||||
|
||||
const baseSchema = genericAppConnectionFieldsSchema.extend({
|
||||
@@ -96,7 +104,12 @@ const getDefaultValues = (appConnection?: TAzureAppConfigurationConnection): Par
|
||||
return base;
|
||||
};
|
||||
|
||||
export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
export const AzureAppConfigurationConnectionForm = ({
|
||||
appConnection,
|
||||
onSubmit,
|
||||
projectType,
|
||||
projectId
|
||||
}: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -110,6 +123,11 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }:
|
||||
defaultValues: getDefaultValues(appConnection)
|
||||
});
|
||||
|
||||
const returnUrl = useGetAppConnectionOauthReturnUrl({
|
||||
projectId,
|
||||
projectType
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -128,7 +146,12 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }:
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureAppConfigurationConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
connectionId: appConnection?.id,
|
||||
projectId,
|
||||
returnUrl
|
||||
} as AzureAppConfigurationFormData)
|
||||
);
|
||||
window.location.assign(
|
||||
`https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-app-configuration`
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user