Compare commits

...

20 Commits

Author SHA1 Message Date
Maidul Islam
1c90df9dd4 add log for secrets depth breakout 2024-04-12 10:34:59 -04:00
Daniel Hougaard
702cd0d403 Update secret-fns.ts 2024-04-12 13:31:48 +02:00
Daniel Hougaard
75267987fc Fix: Add recursive search max depth (20) 2024-04-12 13:28:03 +02:00
Daniel Hougaard
d734a3f6f4 Fix: Add hard recursion limit to documentation 2024-04-12 13:15:42 +02:00
vmatsiiako
cbb749e34a Update list-project-integrations.mdx 2024-04-11 23:52:25 -04:00
Maidul Islam
9f23106c6c Update list-project-integrations.mdx 2024-04-11 20:49:31 -04:00
Maidul Islam
1e7744b498 Merge pull request #1679 from Infisical/list-project-integrations-api
Expose List integratiosn API
2024-04-11 20:20:22 -04:00
Daniel Hougaard
44c736facd Fix: Updated descriptions 2024-04-12 02:15:23 +02:00
Daniel Hougaard
51928ddb47 Fix: OpenAPI doc descriptions structure 2024-04-12 02:15:11 +02:00
Daniel Hougaard
c7cded4af6 Merge pull request #1678 from Infisical/daniel/workspace-endpoint-fix
FIx: Fetching workspaces with no environments
2024-04-12 01:54:06 +02:00
Daniel Hougaard
8b56e20b42 Fix: Removed icon 2024-04-12 01:49:59 +02:00
Daniel Hougaard
39c2c37cc0 Remove log 2024-04-12 01:49:28 +02:00
Daniel Hougaard
3131ae7dae Feat: Disable integration creation when no environments are present on project 2024-04-12 01:46:19 +02:00
Daniel Hougaard
5315a67d74 Feat: Disable integration creation when no environments are present on project 2024-04-12 01:46:11 +02:00
Maidul Islam
79de7f9f5b expose list integrations api 2024-04-11 19:41:55 -04:00
Daniel Hougaard
71ffed026d FIx: Fetching workspaces with no environments 2024-04-12 00:52:22 +02:00
Vladyslav Matsiiako
ee98b15e2b fix typo 2024-04-11 17:43:13 -05:00
Maidul Islam
945d81ad4b update aws SES docs 2024-04-11 16:28:02 -04:00
Maidul Islam
d175256bb4 Merge pull request #1677 from Infisical/integration-auth-del-update
Integration Auth deletion upon Integration deletion
2024-04-11 14:30:31 -04:00
Tuan Dang
ee0c79d018 Delete integration auth upon integration deletion if no other integrations share the same auth 2024-04-11 11:25:28 -07:00
12 changed files with 145 additions and 43 deletions

View File

@@ -141,6 +141,12 @@ export const PROJECTS = {
},
ROLLBACK_TO_SNAPSHOT: {
secretSnapshotId: "The ID of the snapshot to rollback to."
},
LIST_INTEGRATION: {
workspaceId: "The ID of the project to list integrations for."
},
LIST_INTEGRATION_AUTHORIZATION: {
workspaceId: "The ID of the project to list integration auths for."
}
} as const;
@@ -215,7 +221,8 @@ export const SECRETS = {
export const RAW_SECRETS = {
LIST: {
recursive: "Whether or not to fetch all secrets from the specified base path, and all of its subdirectories.",
recursive:
"Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.",
workspaceId: "The ID of the project to list secrets from.",
workspaceSlug: "The slug of the project to list secrets from. This parameter is only usable by machine identities.",
environment: "The slug of the environment to list secrets from.",
@@ -502,11 +509,8 @@ export const INTEGRATION_AUTH = {
url: "",
namespace: "",
refreshToken: "The refresh token for integration authorization."
},
LIST_AUTHORIZATION: {
workspaceId: "The ID of the project to list integration auths for."
}
};
} as const;
export const INTEGRATION = {
CREATE: {

View File

@@ -7,7 +7,7 @@ import {
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { INTEGRATION_AUTH, PROJECTS } from "@app/lib/api-docs";
import { PROJECTS } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -326,8 +326,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
rateLimit: readLimit
},
schema: {
description: "List integrations for a project.",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION.workspaceId)
}),
response: {
200: z.object({
@@ -370,7 +376,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim().describe(INTEGRATION_AUTH.LIST_AUTHORIZATION.workspaceId)
workspaceId: z.string().trim().describe(PROJECTS.LIST_INTEGRATION_AUTHORIZATION.workspaceId)
}),
response: {
200: z.object({

View File

@@ -146,7 +146,27 @@ export const integrationServiceFactory = ({
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const deletedIntegration = await integrationDAL.deleteById(id);
const deletedIntegration = await integrationDAL.transaction(async (tx) => {
// delete integration
const deletedIntegrationResult = await integrationDAL.deleteById(id, tx);
// check if there are other integrations that share the same integration auth
const integrations = await integrationDAL.find(
{
integrationAuthId: integration.integrationAuthId
},
tx
);
if (integrations.length === 0) {
// no other integration shares the same integration auth
// -> delete the integration auth
await integrationAuthDAL.deleteById(integration.integrationAuthId, tx);
}
return deletedIntegrationResult;
});
return { ...integration, ...deletedIntegration };
};

View File

@@ -126,13 +126,11 @@ export const projectDALFactory = (db: TDbClient) => {
const findProjectById = async (id: string) => {
try {
const workspaces = await db(TableName.ProjectMembership)
const workspaces = await db(TableName.Project)
.where(`${TableName.Project}.id`, id)
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.join(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
db.ref("id").withSchema(TableName.Project).as("_id"),
db.ref("id").withSchema(TableName.Environment).as("envId"),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("name").withSchema(TableName.Environment).as("envName")
@@ -141,10 +139,11 @@ export const projectDALFactory = (db: TDbClient) => {
{ column: `${TableName.Project}.name`, order: "asc" },
{ column: `${TableName.Environment}.position`, order: "asc" }
]);
const project = sqlNestRelationships({
data: workspaces,
key: "id",
parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }),
parentMapper: ({ ...el }) => ({ _id: el.id, ...ProjectsSchema.parse(el) }),
childrenMapper: [
{
key: "envId",
@@ -174,14 +173,12 @@ export const projectDALFactory = (db: TDbClient) => {
throw new BadRequestError({ message: "Organization ID is required when querying with slugs" });
}
const projects = await db(TableName.ProjectMembership)
const projects = await db(TableName.Project)
.where(`${TableName.Project}.slug`, slug)
.where(`${TableName.Project}.orgId`, orgId)
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.join(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
db.ref("id").withSchema(TableName.Project).as("_id"),
db.ref("id").withSchema(TableName.Environment).as("envId"),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("name").withSchema(TableName.Environment).as("envName")
@@ -194,7 +191,7 @@ export const projectDALFactory = (db: TDbClient) => {
const project = sqlNestRelationships({
data: projects,
key: "id",
parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }),
parentMapper: ({ ...el }) => ({ _id: el.id, ...ProjectsSchema.parse(el) }),
childrenMapper: [
{
key: "envId",

View File

@@ -21,6 +21,7 @@ import {
} from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
import { groupBy, unique } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
import { getBotKeyFnFactory } from "../project-bot/project-bot-fns";
@@ -92,7 +93,8 @@ const buildHierarchy = (folders: TSecretFolders[]): FolderMap => {
const generatePaths = (
map: FolderMap,
parentId: string = "null",
basePath: string = ""
basePath: string = "",
currentDepth: number = 0
): { path: string; folderId: string }[] => {
const children = map[parentId || "null"] || [];
let paths: { path: string; folderId: string }[] = [];
@@ -105,13 +107,20 @@ const generatePaths = (
// eslint-disable-next-line no-nested-ternary
const currPath = basePath === "" ? (isRootFolder ? "/" : `/${child.name}`) : `${basePath}/${child.name}`;
// Add the current path
paths.push({
path: currPath,
folderId: child.id
}); // Add the current path
});
// Recursively generate paths for children, passing down the formatted pathh
const childPaths = generatePaths(map, child.id, currPath);
// We make sure that the recursion depth doesn't exceed 20.
// We do this to create "circuit break", basically to ensure that we can't encounter any potential memory leaks.
if (currentDepth >= 20) {
logger.info(`generatePaths: Recursion depth exceeded 20, breaking out of recursion [map=${JSON.stringify(map)}]`);
return;
}
// Recursively generate paths for children, passing down the formatted path
const childPaths = generatePaths(map, child.id, currPath, currentDepth + 1);
paths = paths.concat(
childPaths.map((p) => ({
path: p.path,

View File

@@ -0,0 +1,4 @@
---
title: "List Project Integrations"
openapi: "GET /api/v1/workspace/{workspaceId}/integrations"
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

View File

@@ -519,7 +519,8 @@
"api-reference/endpoints/integrations/delete-auth-by-id",
"api-reference/endpoints/integrations/create",
"api-reference/endpoints/integrations/update",
"api-reference/endpoints/integrations/delete"
"api-reference/endpoints/integrations/delete",
"api-reference/endpoints/integrations/list-project-integrations"
]
},
{

View File

@@ -121,24 +121,35 @@ Without email configuration, Infisical's core functions like sign-up/login and s
</Accordion>
<Accordion title="AWS SES">
1. Create an account and [configure AWS SES](https://aws.amazon.com/premiumsupport/knowledge-center/ses-set-up-connect-smtp/) to send emails in the Amazon SES console.
2. Create an IAM user for SMTP authentication and obtain SMTP credentials in SMTP settings > Create SMTP credentials
<Steps>
<Step title="Create a verifed identity">
This will be used to verify the email you are sending from.
![Create SES identity](../../images/self-hosting/configuration/email/ses-create-identity.png)
<Info>
If you AWS SES is under sandbox mode, you will only be able to send emails to verified identies.
</Info>
</Step>
<Step title="Create an account and configure AWS SES">
Create an IAM user for SMTP authentication and obtain SMTP credentials in SMTP settings > Create SMTP credentials
![opening AWS SES console](../../images/self-hosting/configuration/email/email-aws-ses-console.png)
![creating AWS IAM SES user](../../images/self-hosting/configuration/email/email-aws-ses-user.png)
3. With your AWS SES SMTP credentials, you can now set up your SMTP environment variables:
</Step>
<Step title="Set up your SMTP environment variables">
With your AWS SES SMTP credentials, you can now set up your SMTP environment variables for your Infisical instance.
```
SMTP_HOST=email-smtp.ap-northeast-1.amazonaws.com # SMTP endpoint obtained from SMTP settings
SMTP_USERNAME=xxx # your SMTP username
SMTP_PASSWORD=xxx # your SMTP password
SMTP_PORT=587
SMTP_SECURE=true
SMTP_SECURE=false
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
SMTP_FROM_NAME=Infisical
```
</Step>
</Steps>
<Info>
Remember that you will need to restart Infisical for this to work properly.

View File

@@ -0,0 +1,29 @@
import { useRouter } from "next/router";
import { Button } from "../v2";
interface IProps {
projectId: string;
}
export const NoEnvironmentsBanner = ({ projectId }: IProps) => {
const router = useRouter();
return (
<div className="mt-4 flex w-full flex-row items-center rounded-md border border-primary-600/70 bg-primary/[.07] p-4 text-base text-white">
<div className="flex w-full flex-col text-sm">
<span className="mb-2 text-lg font-semibold">
No environments in your project was found
</span>
<p className="prose">
In order to use integrations, you need to create at least one environment in your project.
</p>
</div>
<div className="my-2">
<Button onClick={() => router.push(`/project/${projectId}/settings#environments`)}>
Add environments
</Button>
</div>
</div>
);
};

View File

@@ -1,10 +1,17 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { NoEnvironmentsBanner } from "@app/components/integrations/NoEnvironmentsBanner";
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, Skeleton, Tooltip } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types";
@@ -31,18 +38,32 @@ export const CloudIntegrationSection = ({
"deleteConfirmation"
] as const);
const { permission } = useProjectPermission();
const { currentWorkspace } = useWorkspace();
const isEmpty = !isLoading && !cloudIntegrations?.length;
const sortedCloudIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name));
const sortedCloudIntegrations = useMemo(() => {
const sortedIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name));
if (currentWorkspace?.environments.length === 0) {
return sortedIntegrations.map((integration) => ({ ...integration, isAvailable: false }));
}
return sortedIntegrations;
}, [cloudIntegrations, currentWorkspace?.environments]);
return (
<div>
<div className="px-5">
{currentWorkspace?.environments.length === 0 && (
<NoEnvironmentsBanner projectId={currentWorkspace.id} />
)}
</div>
<div className="m-4 mt-7 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
<h1 className="text-3xl font-semibold">{t("integrations.cloud-integrations")}</h1>
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
</div>
<div className="mx-6 grid grid-cols-2 gap-4 lg:grid-cols-3 2xl:grid-cols-4">
{isLoading &&
Array.from({ length: 12 }).map((_, index) => (

View File

@@ -251,7 +251,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
<div className="mt-6 flex flex-row text-sm text-bunker-400">
<Link href="/signup">
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
Don&apos;t have an acount yet? {t("login.create-account")}
Don&apos;t have an account yet? {t("login.create-account")}
</span>
</Link>
</div>