mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-22 13:29:55 +00:00
Compare commits
20 Commits
infisical/
...
daniel/rec
Author | SHA1 | Date | |
---|---|---|---|
1c90df9dd4 | |||
702cd0d403 | |||
75267987fc | |||
d734a3f6f4 | |||
cbb749e34a | |||
9f23106c6c | |||
1e7744b498 | |||
44c736facd | |||
51928ddb47 | |||
c7cded4af6 | |||
8b56e20b42 | |||
39c2c37cc0 | |||
3131ae7dae | |||
5315a67d74 | |||
79de7f9f5b | |||
71ffed026d | |||
ee98b15e2b | |||
945d81ad4b | |||
d175256bb4 | |||
ee0c79d018 |
@ -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: {
|
||||
|
@ -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({
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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 |
@ -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"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -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.
|
||||

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

|
||||

|
||||
|
||||

|
||||

|
||||
</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.
|
||||
|
||||
3. With your AWS SES SMTP credentials, you can now set up your SMTP environment variables:
|
||||
|
||||
```
|
||||
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_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
|
||||
SMTP_FROM_NAME=Infisical
|
||||
```
|
||||
```
|
||||
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=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.
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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) => (
|
||||
|
@ -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't have an acount yet? {t("login.create-account")}
|
||||
Don't have an account yet? {t("login.create-account")}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user