mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 16:28:19 +00:00
Merge branch 'main' into feat/secure-mfa-endpoints-with-improved-rate-limiting
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||||
|
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesProjectIdExist) t.index("projectId");
|
||||||
|
if (doesOrgIdExist) t.index("orgId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||||
|
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesProjectIdExist) t.dropIndex("projectId");
|
||||||
|
if (doesOrgIdExist) t.dropIndex("orgId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "envId");
|
||||||
|
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
|
||||||
|
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
|
||||||
|
if (doesEnvIdExist) t.index("envId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "envId");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
|
||||||
|
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
|
||||||
|
if (doesEnvIdExist) t.dropIndex("envId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SecretVersion, "envId");
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretVersion)) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
|
||||||
|
if (doesEnvIdExist) t.index("envId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SecretVersion, "envId");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretVersion)) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
|
||||||
|
if (doesEnvIdExist) t.dropIndex("envId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "snapshotId");
|
||||||
|
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
|
||||||
|
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
|
||||||
|
if (doesSnapshotIdExist) t.index("snapshotId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "snapshotId");
|
||||||
|
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
|
||||||
|
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
|
||||||
|
if (doesSnapshotIdExist) t.dropIndex("snapshotId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotFolder, "snapshotId");
|
||||||
|
if (await knex.schema.hasTable(TableName.SnapshotFolder)) {
|
||||||
|
await knex.schema.alterTable(TableName.SnapshotFolder, (t) => {
|
||||||
|
if (doesSnapshotIdExist) t.index("snapshotId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotFolder, "snapshotId");
|
||||||
|
if (await knex.schema.hasTable(TableName.SnapshotFolder)) {
|
||||||
|
await knex.schema.alterTable(TableName.SnapshotFolder, (t) => {
|
||||||
|
if (doesSnapshotIdExist) t.dropIndex("snapshotId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesFolderIdExist = await knex.schema.hasColumn(TableName.Secret, "folderId");
|
||||||
|
const doesUserIdExist = await knex.schema.hasColumn(TableName.Secret, "userId");
|
||||||
|
if (await knex.schema.hasTable(TableName.Secret)) {
|
||||||
|
await knex.schema.alterTable(TableName.Secret, (t) => {
|
||||||
|
if (doesFolderIdExist && doesUserIdExist) t.index(["folderId", "userId"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesFolderIdExist = await knex.schema.hasColumn(TableName.Secret, "folderId");
|
||||||
|
const doesUserIdExist = await knex.schema.hasColumn(TableName.Secret, "userId");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.Secret)) {
|
||||||
|
await knex.schema.alterTable(TableName.Secret, (t) => {
|
||||||
|
if (doesUserIdExist && doesFolderIdExist) t.dropIndex(["folderId", "userId"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesExpireAtExist = await knex.schema.hasColumn(TableName.AuditLog, "expiresAt");
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesExpireAtExist) t.index("expiresAt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesExpireAtExist = await knex.schema.hasColumn(TableName.AuditLog, "expiresAt");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesExpireAtExist) t.dropIndex("expiresAt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -662,6 +662,7 @@ export const INTEGRATION = {
|
|||||||
secretPrefix: "The prefix for the saved secret. Used by GCP.",
|
secretPrefix: "The prefix for the saved secret. Used by GCP.",
|
||||||
secretSuffix: "The suffix for the saved secret. Used by GCP.",
|
secretSuffix: "The suffix for the saved secret. Used by GCP.",
|
||||||
initialSyncBehavoir: "Type of syncing behavoir with the integration.",
|
initialSyncBehavoir: "Type of syncing behavoir with the integration.",
|
||||||
|
mappingBehavior: "The mapping behavior of the integration.",
|
||||||
shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
|
shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
|
||||||
secretGCPLabel: "The label for GCP secrets.",
|
secretGCPLabel: "The label for GCP secrets.",
|
||||||
secretAWSTag: "The tags for AWS secrets.",
|
secretAWSTag: "The tags for AWS secrets.",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import fp from "fastify-plugin";
|
import fp from "fastify-plugin";
|
||||||
|
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
// inject permission type needed based on auth extracted
|
// inject permission type needed based on auth extracted
|
||||||
@@ -15,6 +16,10 @@ export const injectPermission = fp(async (server) => {
|
|||||||
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
|
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
|
||||||
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
|
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.userId}] [type=${ActorType.USER}]`
|
||||||
|
);
|
||||||
} else if (req.auth.actor === ActorType.IDENTITY) {
|
} else if (req.auth.actor === ActorType.IDENTITY) {
|
||||||
req.permission = {
|
req.permission = {
|
||||||
type: ActorType.IDENTITY,
|
type: ActorType.IDENTITY,
|
||||||
@@ -22,6 +27,10 @@ export const injectPermission = fp(async (server) => {
|
|||||||
orgId: req.auth.orgId,
|
orgId: req.auth.orgId,
|
||||||
authMethod: null
|
authMethod: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.identityId}] [type=${ActorType.IDENTITY}]`
|
||||||
|
);
|
||||||
} else if (req.auth.actor === ActorType.SERVICE) {
|
} else if (req.auth.actor === ActorType.SERVICE) {
|
||||||
req.permission = {
|
req.permission = {
|
||||||
type: ActorType.SERVICE,
|
type: ActorType.SERVICE,
|
||||||
@@ -29,6 +38,10 @@ export const injectPermission = fp(async (server) => {
|
|||||||
orgId: req.auth.orgId,
|
orgId: req.auth.orgId,
|
||||||
authMethod: null
|
authMethod: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.serviceTokenId}] [type=${ActorType.SERVICE}]`
|
||||||
|
);
|
||||||
} else if (req.auth.actor === ActorType.SCIM_CLIENT) {
|
} else if (req.auth.actor === ActorType.SCIM_CLIENT) {
|
||||||
req.permission = {
|
req.permission = {
|
||||||
type: ActorType.SCIM_CLIENT,
|
type: ActorType.SCIM_CLIENT,
|
||||||
@@ -36,6 +49,10 @@ export const injectPermission = fp(async (server) => {
|
|||||||
orgId: req.auth.orgId,
|
orgId: req.auth.orgId,
|
||||||
authMethod: null
|
authMethod: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.scimTokenId}] [type=${ActorType.SCIM_CLIENT}]`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { IntegrationMappingBehavior } from "@app/services/integration-auth/integration-list";
|
||||||
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -49,6 +50,10 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
||||||
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
||||||
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
||||||
|
mappingBehavior: z
|
||||||
|
.nativeEnum(IntegrationMappingBehavior)
|
||||||
|
.optional()
|
||||||
|
.describe(INTEGRATION.CREATE.metadata.mappingBehavior),
|
||||||
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
||||||
secretGCPLabel: z
|
secretGCPLabel: z
|
||||||
.object({
|
.object({
|
||||||
@@ -160,6 +165,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
||||||
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
||||||
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
||||||
|
mappingBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.mappingBehavior),
|
||||||
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
||||||
secretGCPLabel: z
|
secretGCPLabel: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ export enum IntegrationInitialSyncBehavior {
|
|||||||
PREFER_SOURCE = "prefer-source"
|
PREFER_SOURCE = "prefer-source"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum IntegrationMappingBehavior {
|
||||||
|
ONE_TO_ONE = "one-to-one",
|
||||||
|
MANY_TO_ONE = "many-to-one"
|
||||||
|
}
|
||||||
|
|
||||||
export enum IntegrationUrls {
|
export enum IntegrationUrls {
|
||||||
// integration oauth endpoints
|
// integration oauth endpoints
|
||||||
GCP_TOKEN_URL = "https://oauth2.googleapis.com/token",
|
GCP_TOKEN_URL = "https://oauth2.googleapis.com/token",
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ import { BadRequestError } from "@app/lib/errors";
|
|||||||
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
|
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
|
||||||
|
|
||||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||||
import { IntegrationInitialSyncBehavior, Integrations, IntegrationUrls } from "./integration-list";
|
import {
|
||||||
|
IntegrationInitialSyncBehavior,
|
||||||
|
IntegrationMappingBehavior,
|
||||||
|
Integrations,
|
||||||
|
IntegrationUrls
|
||||||
|
} from "./integration-list";
|
||||||
|
|
||||||
const getSecretKeyValuePair = (secrets: Record<string, { value: string | null; comment?: string } | null>) =>
|
const getSecretKeyValuePair = (secrets: Record<string, { value: string | null; comment?: string } | null>) =>
|
||||||
Object.keys(secrets).reduce<Record<string, string | null | undefined>>((prev, key) => {
|
Object.keys(secrets).reduce<Record<string, string | null | undefined>>((prev, key) => {
|
||||||
@@ -570,134 +575,149 @@ const syncSecretsAWSSecretManager = async ({
|
|||||||
accessId: string | null;
|
accessId: string | null;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let secretsManager;
|
|
||||||
const secKeyVal = getSecretKeyValuePair(secrets);
|
|
||||||
const metadata = z.record(z.any()).parse(integration.metadata || {});
|
const metadata = z.record(z.any()).parse(integration.metadata || {});
|
||||||
try {
|
|
||||||
if (!accessId) return;
|
|
||||||
|
|
||||||
secretsManager = new SecretsManagerClient({
|
if (!accessId) return;
|
||||||
region: integration.region as string,
|
|
||||||
credentials: {
|
const secretsManager = new SecretsManagerClient({
|
||||||
accessKeyId: accessId,
|
region: integration.region as string,
|
||||||
secretAccessKey: accessToken
|
credentials: {
|
||||||
|
accessKeyId: accessId,
|
||||||
|
secretAccessKey: accessToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const processAwsSecret = async (
|
||||||
|
secretId: string,
|
||||||
|
secretValue: Record<string, string | null | undefined> | string
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const awsSecretManagerSecret = await secretsManager.send(
|
||||||
|
new GetSecretValueCommand({
|
||||||
|
SecretId: secretId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let secretToCompare;
|
||||||
|
if (awsSecretManagerSecret?.SecretString) {
|
||||||
|
if (typeof secretValue === "string") {
|
||||||
|
secretToCompare = awsSecretManagerSecret.SecretString;
|
||||||
|
} else {
|
||||||
|
secretToCompare = JSON.parse(awsSecretManagerSecret.SecretString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const awsSecretManagerSecret = await secretsManager.send(
|
if (!isEqual(secretToCompare, secretValue)) {
|
||||||
new GetSecretValueCommand({
|
await secretsManager.send(
|
||||||
SecretId: integration.app as string
|
new UpdateSecretCommand({
|
||||||
})
|
SecretId: secretId,
|
||||||
);
|
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let awsSecretManagerSecretObj: { [key: string]: AWS.SecretsManager } = {};
|
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
|
||||||
|
|
||||||
if (awsSecretManagerSecret?.SecretString) {
|
if (secretAWSTag && secretAWSTag.length) {
|
||||||
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
|
const describedSecret = await secretsManager.send(
|
||||||
}
|
// requires secretsmanager:DescribeSecret policy
|
||||||
|
new DescribeSecretCommand({
|
||||||
|
SecretId: secretId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) {
|
if (!describedSecret.Tags) return;
|
||||||
await secretsManager.send(
|
|
||||||
new UpdateSecretCommand({
|
|
||||||
SecretId: integration.app as string,
|
|
||||||
SecretString: JSON.stringify(secKeyVal)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
|
const integrationTagObj = secretAWSTag.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[item.key] = item.value;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
|
||||||
if (secretAWSTag && secretAWSTag.length) {
|
const awsTagObj = (describedSecret.Tags || []).reduce(
|
||||||
const describedSecret = await secretsManager.send(
|
(acc, item) => {
|
||||||
// requires secretsmanager:DescribeSecret policy
|
if (item.Key && item.Value) {
|
||||||
new DescribeSecretCommand({
|
acc[item.Key] = item.Value;
|
||||||
SecretId: integration.app as string
|
}
|
||||||
})
|
return acc;
|
||||||
);
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
|
||||||
if (!describedSecret.Tags) return;
|
const tagsToUpdate: { Key: string; Value: string }[] = [];
|
||||||
|
const tagsToDelete: { Key: string; Value: string }[] = [];
|
||||||
|
|
||||||
const integrationTagObj = secretAWSTag.reduce(
|
describedSecret.Tags?.forEach((tag) => {
|
||||||
(acc, item) => {
|
if (tag.Key && tag.Value) {
|
||||||
acc[item.key] = item.value;
|
if (!(tag.Key in integrationTagObj)) {
|
||||||
return acc;
|
// delete tag from AWS secret manager
|
||||||
},
|
tagsToDelete.push({
|
||||||
{} as Record<string, string>
|
Key: tag.Key,
|
||||||
);
|
Value: tag.Value
|
||||||
|
});
|
||||||
const awsTagObj = (describedSecret.Tags || []).reduce(
|
} else if (tag.Value !== integrationTagObj[tag.Key]) {
|
||||||
(acc, item) => {
|
// update tag in AWS secret manager
|
||||||
if (item.Key && item.Value) {
|
tagsToUpdate.push({
|
||||||
acc[item.Key] = item.Value;
|
Key: tag.Key,
|
||||||
|
Value: integrationTagObj[tag.Key]
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return acc;
|
});
|
||||||
},
|
|
||||||
{} as Record<string, string>
|
|
||||||
);
|
|
||||||
|
|
||||||
const tagsToUpdate: { Key: string; Value: string }[] = [];
|
secretAWSTag?.forEach((tag) => {
|
||||||
const tagsToDelete: { Key: string; Value: string }[] = [];
|
if (!(tag.key in awsTagObj)) {
|
||||||
|
// create tag in AWS secret manager
|
||||||
describedSecret.Tags?.forEach((tag) => {
|
|
||||||
if (tag.Key && tag.Value) {
|
|
||||||
if (!(tag.Key in integrationTagObj)) {
|
|
||||||
// delete tag from AWS secret manager
|
|
||||||
tagsToDelete.push({
|
|
||||||
Key: tag.Key,
|
|
||||||
Value: tag.Value
|
|
||||||
});
|
|
||||||
} else if (tag.Value !== integrationTagObj[tag.Key]) {
|
|
||||||
// update tag in AWS secret manager
|
|
||||||
tagsToUpdate.push({
|
tagsToUpdate.push({
|
||||||
Key: tag.Key,
|
Key: tag.key,
|
||||||
Value: integrationTagObj[tag.Key]
|
Value: tag.value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
secretAWSTag?.forEach((tag) => {
|
if (tagsToUpdate.length) {
|
||||||
if (!(tag.key in awsTagObj)) {
|
await secretsManager.send(
|
||||||
// create tag in AWS secret manager
|
new TagResourceCommand({
|
||||||
tagsToUpdate.push({
|
SecretId: secretId,
|
||||||
Key: tag.key,
|
Tags: tagsToUpdate
|
||||||
Value: tag.value
|
})
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (tagsToUpdate.length) {
|
if (tagsToDelete.length) {
|
||||||
await secretsManager.send(
|
await secretsManager.send(
|
||||||
new TagResourceCommand({
|
new UntagResourceCommand({
|
||||||
SecretId: integration.app as string,
|
SecretId: secretId,
|
||||||
Tags: tagsToUpdate
|
TagKeys: tagsToDelete.map((tag) => tag.Key)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
if (tagsToDelete.length) {
|
// case when AWS manager can't find the specified secret
|
||||||
|
if (err instanceof ResourceNotFoundException && secretsManager) {
|
||||||
await secretsManager.send(
|
await secretsManager.send(
|
||||||
new UntagResourceCommand({
|
new CreateSecretCommand({
|
||||||
SecretId: integration.app as string,
|
Name: secretId,
|
||||||
TagKeys: tagsToDelete.map((tag) => tag.Key)
|
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue),
|
||||||
|
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
|
||||||
|
Tags: metadata.secretAWSTag
|
||||||
|
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
||||||
|
: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
};
|
||||||
// case when AWS manager can't find the specified secret
|
|
||||||
if (err instanceof ResourceNotFoundException && secretsManager) {
|
if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) {
|
||||||
await secretsManager.send(
|
for await (const [key, value] of Object.entries(secrets)) {
|
||||||
new CreateSecretCommand({
|
await processAwsSecret(key, value.value);
|
||||||
Name: integration.app as string,
|
|
||||||
SecretString: JSON.stringify(secKeyVal),
|
|
||||||
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
|
|
||||||
Tags: metadata.secretAWSTag
|
|
||||||
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
|
||||||
: []
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { decryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/en
|
|||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
|
|
||||||
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TGetPrivateKeyDTO } from "./project-bot-types";
|
import { TGetPrivateKeyDTO } from "./project-bot-types";
|
||||||
|
|
||||||
export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
|
export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
|
||||||
@@ -13,11 +14,17 @@ export const getBotPrivateKey = ({ bot }: TGetPrivateKeyDTO) =>
|
|||||||
ciphertext: bot.encryptedPrivateKey
|
ciphertext: bot.encryptedPrivateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getBotKeyFnFactory = (projectBotDAL: TProjectBotDALFactory) => {
|
export const getBotKeyFnFactory = (
|
||||||
|
projectBotDAL: TProjectBotDALFactory,
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findById">
|
||||||
|
) => {
|
||||||
const getBotKeyFn = async (projectId: string) => {
|
const getBotKeyFn = async (projectId: string) => {
|
||||||
const bot = await projectBotDAL.findOne({ projectId });
|
const project = await projectDAL.findById(projectId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found during bot lookup." });
|
||||||
|
|
||||||
if (!bot) throw new BadRequestError({ message: "failed to find bot key" });
|
const bot = await projectBotDAL.findOne({ projectId: project.id });
|
||||||
|
|
||||||
|
if (!bot) throw new BadRequestError({ message: "Failed to find bot key" });
|
||||||
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" });
|
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" });
|
||||||
if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey)
|
if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey)
|
||||||
throw new BadRequestError({ message: "Encryption key missing" });
|
throw new BadRequestError({ message: "Encryption key missing" });
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const projectBotServiceFactory = ({
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService
|
permissionService
|
||||||
}: TProjectBotServiceFactoryDep) => {
|
}: TProjectBotServiceFactoryDep) => {
|
||||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
||||||
|
|
||||||
const getBotKey = async (projectId: string) => {
|
const getBotKey = async (projectId: string) => {
|
||||||
return getBotKeyFn(projectId);
|
return getBotKeyFn(projectId);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
|
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
@@ -104,9 +106,9 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findProjectGhostUser = async (projectId: string) => {
|
const findProjectGhostUser = async (projectId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const ghostUser = await db(TableName.ProjectMembership)
|
const ghostUser = await (tx || db)(TableName.ProjectMembership)
|
||||||
.where({ projectId })
|
.where({ projectId })
|
||||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||||
.select(selectAllTableCols(TableName.Users))
|
.select(selectAllTableCols(TableName.Users))
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ export const projectServiceFactory = ({
|
|||||||
|
|
||||||
const deletedProject = await projectDAL.transaction(async (tx) => {
|
const deletedProject = await projectDAL.transaction(async (tx) => {
|
||||||
const delProject = await projectDAL.deleteById(project.id, tx);
|
const delProject = await projectDAL.deleteById(project.id, tx);
|
||||||
const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id).catch(() => null);
|
const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id, tx).catch(() => null);
|
||||||
|
|
||||||
// Delete the org membership for the ghost user if it's found.
|
// Delete the org membership for the ghost user if it's found.
|
||||||
if (projectGhostUser) {
|
if (projectGhostUser) {
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ export const createManySecretsRawFnFactory = ({
|
|||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
folderDAL
|
folderDAL
|
||||||
}: TCreateManySecretsRawFnFactory) => {
|
}: TCreateManySecretsRawFnFactory) => {
|
||||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
||||||
const createManySecretsRawFn = async ({
|
const createManySecretsRawFn = async ({
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
@@ -706,7 +706,7 @@ export const updateManySecretsRawFnFactory = ({
|
|||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
folderDAL
|
folderDAL
|
||||||
}: TUpdateManySecretsRawFnFactory) => {
|
}: TUpdateManySecretsRawFnFactory) => {
|
||||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL);
|
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
||||||
const updateManySecretsRawFn = async ({
|
const updateManySecretsRawFn = async ({
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
@@ -167,15 +166,11 @@ export const serviceTokenServiceFactory = ({
|
|||||||
|
|
||||||
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceToken.secretHash);
|
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceToken.secretHash);
|
||||||
if (!isMatch) throw new UnauthorizedError();
|
if (!isMatch) throw new UnauthorizedError();
|
||||||
// const updatedToken = await serviceTokenDAL.updateById(serviceToken.id, {
|
const updatedToken = await serviceTokenDAL.updateById(serviceToken.id, {
|
||||||
// lastUsed: new Date()
|
lastUsed: new Date()
|
||||||
// });
|
});
|
||||||
|
|
||||||
logger.info(
|
return { ...serviceToken, lastUsed: updatedToken.lastUsed, orgId: project.orgId };
|
||||||
`fnValidateServiceToken: [serviceToken=${serviceToken.id}] [serviceTokenProjectId=${serviceToken.projectId}]`
|
|
||||||
);
|
|
||||||
|
|
||||||
return { ...serviceToken, lastUsed: serviceToken.lastUsed, orgId: project.orgId };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 142 KiB |
@@ -72,6 +72,9 @@ Prerequisites:
|
|||||||
<ParamField path="AWS Region" type="string" required>
|
<ParamField path="AWS Region" type="string" required>
|
||||||
The region that you want to integrate with in AWS Secrets Manager.
|
The region that you want to integrate with in AWS Secrets Manager.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
<ParamField path="Mapping Behavior" type="string" required>
|
||||||
|
How you want the integration to map the secrets. The selected value could be either one to one or one to many.
|
||||||
|
</ParamField>
|
||||||
<ParamField path="AWS SM Secret Name" type="string" required>
|
<ParamField path="AWS SM Secret Name" type="string" required>
|
||||||
The secret name/path in AWS into which you want to sync the secrets from Infisical.
|
The secret name/path in AWS into which you want to sync the secrets from Infisical.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const useCreateIntegration = () => {
|
|||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
initialSyncBehavior?: string;
|
initialSyncBehavior?: string;
|
||||||
shouldAutoRedeploy?: boolean;
|
shouldAutoRedeploy?: boolean;
|
||||||
|
mappingBehavior?: string;
|
||||||
secretAWSTag?: {
|
secretAWSTag?: {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type TIntegration = {
|
|||||||
metadata?: {
|
metadata?: {
|
||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
syncBehavior?: IntegrationSyncBehavior;
|
syncBehavior?: IntegrationSyncBehavior;
|
||||||
|
mappingBehavior?: IntegrationMappingBehavior;
|
||||||
scope: string;
|
scope: string;
|
||||||
org: string;
|
org: string;
|
||||||
project: string;
|
project: string;
|
||||||
@@ -48,3 +49,8 @@ export enum IntegrationSyncBehavior {
|
|||||||
PREFER_TARGET = "prefer-target",
|
PREFER_TARGET = "prefer-target",
|
||||||
PREFER_SOURCE = "prefer-source"
|
PREFER_SOURCE = "prefer-source"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum IntegrationMappingBehavior {
|
||||||
|
ONE_TO_ONE = "one-to-one",
|
||||||
|
MANY_TO_ONE = "many-to-one"
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import queryString from "query-string";
|
|||||||
|
|
||||||
import { useCreateIntegration } from "@app/hooks/api";
|
import { useCreateIntegration } from "@app/hooks/api";
|
||||||
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
|
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
|
||||||
|
import { IntegrationMappingBehavior } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -70,6 +71,17 @@ const awsRegions = [
|
|||||||
{ name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" }
|
{ name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mappingBehaviors = [
|
||||||
|
{
|
||||||
|
label: "Many to One (All Infisical secrets will be mapped to a single AWS secret)",
|
||||||
|
value: IntegrationMappingBehavior.MANY_TO_ONE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "One to One - (Each Infisical secret will be mapped to its own AWS secret)",
|
||||||
|
value: IntegrationMappingBehavior.ONE_TO_ONE
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
export default function AWSSecretManagerCreateIntegrationPage() {
|
export default function AWSSecretManagerCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutateAsync } = useCreateIntegration();
|
const { mutateAsync } = useCreateIntegration();
|
||||||
@@ -84,6 +96,9 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||||
const [secretPath, setSecretPath] = useState("/");
|
const [secretPath, setSecretPath] = useState("/");
|
||||||
const [selectedAWSRegion, setSelectedAWSRegion] = useState("");
|
const [selectedAWSRegion, setSelectedAWSRegion] = useState("");
|
||||||
|
const [selectedMappingBehavior, setSelectedMappingBehavior] = useState(
|
||||||
|
IntegrationMappingBehavior.MANY_TO_ONE
|
||||||
|
);
|
||||||
const [targetSecretName, setTargetSecretName] = useState("");
|
const [targetSecretName, setTargetSecretName] = useState("");
|
||||||
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
|
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
|
||||||
const [tagKey, setTagKey] = useState("");
|
const [tagKey, setTagKey] = useState("");
|
||||||
@@ -116,7 +131,14 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
try {
|
try {
|
||||||
if (targetSecretName.trim() === "") {
|
if (!selectedMappingBehavior) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedMappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE &&
|
||||||
|
targetSecretName.trim() === ""
|
||||||
|
) {
|
||||||
setTargetSecretName("Secret name cannot be blank");
|
setTargetSecretName("Secret name cannot be blank");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -143,7 +165,8 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(kmsKeyId && { kmsKeyId })
|
...(kmsKeyId && { kmsKeyId }),
|
||||||
|
mappingBehavior: selectedMappingBehavior
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,19 +271,40 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl
|
<FormControl label="Mapping Behavior">
|
||||||
label="AWS SM Secret Name"
|
<Select
|
||||||
errorText={targetSecretNameErrorText}
|
value={selectedMappingBehavior}
|
||||||
isError={targetSecretNameErrorText !== "" ?? false}
|
onValueChange={(val) => {
|
||||||
>
|
setSelectedMappingBehavior(val as IntegrationMappingBehavior);
|
||||||
<Input
|
}}
|
||||||
placeholder={`${workspace.name
|
className="w-full border border-mineshaft-500 text-left"
|
||||||
.toLowerCase()
|
>
|
||||||
.replace(/ /g, "-")}/${selectedSourceEnvironment}`}
|
{mappingBehaviors.map((option) => (
|
||||||
value={targetSecretName}
|
<SelectItem
|
||||||
onChange={(e) => setTargetSecretName(e.target.value)}
|
value={option.value}
|
||||||
/>
|
className="text-left"
|
||||||
|
key={`aws-environment-${option.value}`}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
{selectedMappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE && (
|
||||||
|
<FormControl
|
||||||
|
label="AWS SM Secret Name"
|
||||||
|
errorText={targetSecretNameErrorText}
|
||||||
|
isError={targetSecretNameErrorText !== "" ?? false}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder={`${workspace.name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/ /g, "-")}/${selectedSourceEnvironment}`}
|
||||||
|
value={targetSecretName}
|
||||||
|
onChange={(e) => setTargetSecretName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={TabSections.Options}>
|
<TabPanel value={TabSections.Options}>
|
||||||
|
|||||||
@@ -121,6 +121,14 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
|
// Case: User has no organizations.
|
||||||
|
// This can happen if the user was previously a member, but the organization was deleted or the user was removed.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!organizations.isLoading && organizations.data?.length === 0) {
|
||||||
|
router.push("/org/none");
|
||||||
|
}
|
||||||
|
}, [organizations.isLoading, organizations.data]);
|
||||||
|
|
||||||
if (userLoading || !user) {
|
if (userLoading || !user) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-23
@@ -21,6 +21,7 @@ import {
|
|||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||||
|
import { IntegrationMappingBehavior } from "@app/hooks/api/integrations/types";
|
||||||
import { TIntegration } from "@app/hooks/api/types";
|
import { TIntegration } from "@app/hooks/api/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -131,30 +132,35 @@ export const IntegrationsSection = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="ml-2 flex flex-col">
|
{!(
|
||||||
<FormLabel
|
integration.integration === "aws-secret-manager" &&
|
||||||
label={
|
integration.metadata?.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE
|
||||||
(integration.integration === "qovery" && integration?.scope) ||
|
) && (
|
||||||
(integration.integration === "aws-secret-manager" && "Secret") ||
|
<div className="ml-2 flex flex-col">
|
||||||
(integration.integration === "aws-parameter-store" && "Path") ||
|
<FormLabel
|
||||||
(integration?.integration === "terraform-cloud" && "Project") ||
|
label={
|
||||||
(integration?.scope === "github-org" && "Organization") ||
|
(integration.integration === "qovery" && integration?.scope) ||
|
||||||
(["github-repo", "github-env"].includes(integration?.scope as string) &&
|
(integration.integration === "aws-secret-manager" && "Secret") ||
|
||||||
"Repository") ||
|
(integration.integration === "aws-parameter-store" && "Path") ||
|
||||||
"App"
|
(integration?.integration === "terraform-cloud" && "Project") ||
|
||||||
}
|
(integration?.scope === "github-org" && "Organization") ||
|
||||||
/>
|
(["github-repo", "github-env"].includes(integration?.scope as string) &&
|
||||||
<div className="no-scrollbar::-webkit-scrollbar min-w-[8rem] max-w-[12rem] overflow-scroll whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200 no-scrollbar">
|
"Repository") ||
|
||||||
{(integration.integration === "hashicorp-vault" &&
|
"App"
|
||||||
`${integration.app} - path: ${integration.path}`) ||
|
}
|
||||||
(integration.scope === "github-org" && `${integration.owner}`) ||
|
/>
|
||||||
(integration.integration === "aws-parameter-store" &&
|
<div className="no-scrollbar::-webkit-scrollbar min-w-[8rem] max-w-[12rem] overflow-scroll whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200 no-scrollbar">
|
||||||
`${integration.path}`) ||
|
{(integration.integration === "hashicorp-vault" &&
|
||||||
(integration.scope?.startsWith("github-") &&
|
`${integration.app} - path: ${integration.path}`) ||
|
||||||
`${integration.owner}/${integration.app}`) ||
|
(integration.scope === "github-org" && `${integration.owner}`) ||
|
||||||
integration.app}
|
(integration.integration === "aws-parameter-store" &&
|
||||||
|
`${integration.path}`) ||
|
||||||
|
(integration.scope?.startsWith("github-") &&
|
||||||
|
`${integration.owner}/${integration.app}`) ||
|
||||||
|
integration.app}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
{(integration.integration === "vercel" ||
|
{(integration.integration === "vercel" ||
|
||||||
integration.integration === "netlify" ||
|
integration.integration === "netlify" ||
|
||||||
integration.integration === "railway" ||
|
integration.integration === "railway" ||
|
||||||
|
|||||||
Reference in New Issue
Block a user