mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #1284 from Infisical/multi-integration-auth
Enable new integration auth credential for each new integration
This commit is contained in:
@@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { standardRequest } from "../../config/request";
|
||||
import { getApps, getTeams, revokeAccess } from "../../integrations";
|
||||
import { Bot, IntegrationAuth, Workspace } from "../../models";
|
||||
import { Bot, IIntegrationAuth, Integration, IntegrationAuth, Workspace } from "../../models";
|
||||
import { EventType } from "../../ee/models";
|
||||
import { IntegrationService } from "../../services";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
@@ -130,7 +130,6 @@ export const oAuthExchange = async (req: Request, res: Response) => {
|
||||
export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||
// TODO: refactor
|
||||
// TODO: check if access token is valid for each integration
|
||||
let integrationAuth;
|
||||
const {
|
||||
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
|
||||
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
|
||||
@@ -152,31 +151,21 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||
|
||||
if (!bot) throw new Error("Bot must be enabled to save integration access token");
|
||||
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||
{
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration
|
||||
},
|
||||
{
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration,
|
||||
url,
|
||||
namespace,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
...(integration === INTEGRATION_GCP_SECRET_MANAGER
|
||||
? {
|
||||
metadata: {
|
||||
authMethod: "serviceAccount"
|
||||
}
|
||||
let integrationAuth = await new IntegrationAuth({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration,
|
||||
url,
|
||||
namespace,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
...(integration === INTEGRATION_GCP_SECRET_MANAGER
|
||||
? {
|
||||
metadata: {
|
||||
authMethod: "serviceAccount"
|
||||
}
|
||||
: {})
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
}
|
||||
: {})
|
||||
}).save();
|
||||
|
||||
// encrypt and save integration access details
|
||||
if (refreshToken) {
|
||||
@@ -188,12 +177,12 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||
|
||||
// encrypt and save integration access details
|
||||
if (accessId || accessToken) {
|
||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuth = (await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt: undefined
|
||||
});
|
||||
})) as IIntegrationAuth;
|
||||
}
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to save integration access token");
|
||||
@@ -1208,13 +1197,64 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all integration authorizations and integrations for workspace with id [workspaceId]
|
||||
* with integration name [integration]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const deleteIntegrationAuths = async (req: Request, res: Response) => {
|
||||
const {
|
||||
query: { integration, workspaceId }
|
||||
} = await validateRequest(reqValidator.DeleteIntegrationAuthsV1, req);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
const integrationAuths = await IntegrationAuth.deleteMany({
|
||||
integration,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const integrations = await Integration.deleteMany({
|
||||
integration,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UNAUTHORIZE_INTEGRATION,
|
||||
metadata: {
|
||||
integration
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
integrationAuths,
|
||||
integrations
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete integration authorization with id [integrationAuthId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
||||
export const deleteIntegrationAuthById = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId }
|
||||
} = await validateRequest(reqValidator.DeleteIntegrationAuthV1, req);
|
||||
|
||||
@@ -251,6 +251,21 @@ export const deleteIntegration = async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
if (!deletedIntegration) throw new Error("Failed to find integration");
|
||||
|
||||
const numOtherIntegrationsUsingSameAuth = await Integration.countDocuments({
|
||||
integrationAuth: deletedIntegration.integrationAuth,
|
||||
_id: {
|
||||
$nin: [deletedIntegration._id]
|
||||
}
|
||||
});
|
||||
|
||||
if (numOtherIntegrationsUsingSameAuth === 0) {
|
||||
// no other integrations are using the same integration auth
|
||||
// -> delete integration auth associated with the integration being deleted
|
||||
await IntegrationAuth.deleteOne({
|
||||
_id: deletedIntegration.integrationAuth
|
||||
});
|
||||
}
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
|
||||
@@ -156,12 +156,20 @@ router.get(
|
||||
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.deleteIntegrationAuths
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:integrationAuthId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.deleteIntegrationAuth
|
||||
integrationAuthController.deleteIntegrationAuthById
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -192,6 +192,13 @@ export const GetIntegrationAuthNorthflankSecretGroupsV1 = z.object({
|
||||
})
|
||||
});
|
||||
|
||||
export const DeleteIntegrationAuthsV1 = z.object({
|
||||
query: z.object({
|
||||
integration: z.string().trim(),
|
||||
workspaceId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const DeleteIntegrationAuthV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export {
|
||||
useAuthorizeIntegration,
|
||||
useDeleteIntegrationAuth,
|
||||
useDeleteIntegrationAuths,
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthBitBucketWorkspaces,
|
||||
useGetIntegrationAuthById,
|
||||
|
||||
@@ -699,7 +699,22 @@ export const useSaveIntegrationAccessToken = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIntegrationAuth = () => {
|
||||
export const useDeleteIntegrationAuths = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, { integration: string; workspaceId: string }>({
|
||||
mutationFn: ({ integration, workspaceId }) => apiRequest.delete(`/api/v1/integration-auth?${new URLSearchParams({
|
||||
integration,
|
||||
workspaceId
|
||||
})}`),
|
||||
onSuccess: (_, { workspaceId }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceAuthorization(workspaceId));
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(workspaceId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIntegrationAuth = () => { // not used
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, { id: string; workspaceId: string }>({
|
||||
|
||||
@@ -96,6 +96,7 @@ export const useDeleteIntegration = () => {
|
||||
mutationFn: ({ id }) => apiRequest.delete(`/api/v1/integration/${id}`),
|
||||
onSuccess: (_, { workspaceId }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(workspaceId));
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceAuthorization(workspaceId));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -126,6 +126,7 @@ const fetchWorkspaceAuthorization = async (workspaceId: string) => {
|
||||
const { data } = await apiRequest.get<{ authorizations: IntegrationAuth[] }>(
|
||||
`/api/v1/workspace/${workspaceId}/authorizations`
|
||||
);
|
||||
|
||||
return data.authorizations;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { Button, Modal, ModalContent } from "@app/components/v2";
|
||||
@@ -9,7 +8,7 @@ import { withProjectPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
useDeleteIntegration,
|
||||
useDeleteIntegrationAuth,
|
||||
useDeleteIntegrationAuths,
|
||||
useGetCloudIntegrations,
|
||||
useGetUserWsKey,
|
||||
useGetWorkspaceAuthorizations,
|
||||
@@ -24,8 +23,7 @@ import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSe
|
||||
import { IntegrationsSection } from "./components/IntegrationsSection";
|
||||
import {
|
||||
generateBotKey,
|
||||
redirectForProviderAuth,
|
||||
redirectToIntegrationAppConfigScreen
|
||||
redirectForProviderAuth
|
||||
} from "./IntegrationPage.utils";
|
||||
|
||||
type Props = {
|
||||
@@ -36,7 +34,6 @@ export const IntegrationsPage = withProjectPermission(
|
||||
({ frameworkIntegrations }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const router = useRouter();
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?._id || "";
|
||||
@@ -65,6 +62,7 @@ export const IntegrationsPage = withProjectPermission(
|
||||
return groupBy;
|
||||
}, [])
|
||||
);
|
||||
|
||||
// mutation
|
||||
const {
|
||||
data: integrations,
|
||||
@@ -78,11 +76,11 @@ export const IntegrationsPage = withProjectPermission(
|
||||
const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } =
|
||||
useUpdateBotActiveStatus();
|
||||
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
||||
const {
|
||||
mutateAsync: deleteIntegrationAuth,
|
||||
const {
|
||||
mutateAsync: deleteIntegrationAuths,
|
||||
isSuccess: isDeleteIntegrationAuthSuccess,
|
||||
reset: resetDeleteIntegrationAuth
|
||||
} = useDeleteIntegrationAuth();
|
||||
reset: resetDeleteIntegrationAuths
|
||||
} = useDeleteIntegrationAuths();
|
||||
|
||||
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
|
||||
const isIntegrationsEmpty = !integrations?.length;
|
||||
@@ -103,7 +101,7 @@ export const IntegrationsPage = withProjectPermission(
|
||||
botId: bot._id,
|
||||
workspaceId
|
||||
});
|
||||
resetDeleteIntegrationAuth();
|
||||
resetDeleteIntegrationAuths();
|
||||
}
|
||||
}, [
|
||||
isIntegrationFetching,
|
||||
@@ -116,7 +114,7 @@ export const IntegrationsPage = withProjectPermission(
|
||||
const handleProviderIntegration = async (provider: string) => {
|
||||
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
|
||||
if (!selectedCloudIntegration) return;
|
||||
|
||||
|
||||
try {
|
||||
if (bot && !bot.isActive) {
|
||||
const botKey = generateBotKey(bot.publicKey, latestWsKey!);
|
||||
@@ -127,14 +125,8 @@ export const IntegrationsPage = withProjectPermission(
|
||||
botId: bot._id
|
||||
});
|
||||
}
|
||||
const integrationAuthForProvider = integrationAuths?.[provider];
|
||||
if (!integrationAuthForProvider) {
|
||||
redirectForProviderAuth(selectedCloudIntegration);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id);
|
||||
router.push(url);
|
||||
redirectForProviderAuth(selectedCloudIntegration);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@@ -176,9 +168,10 @@ export const IntegrationsPage = withProjectPermission(
|
||||
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
|
||||
const integrationAuthForProvider = integrationAuths?.[provider];
|
||||
if (!integrationAuthForProvider) return;
|
||||
|
||||
try {
|
||||
await deleteIntegrationAuth({
|
||||
id: integrationAuthForProvider._id,
|
||||
await deleteIntegrationAuths({
|
||||
integration: provider,
|
||||
workspaceId
|
||||
});
|
||||
if (cb) cb();
|
||||
|
||||
Reference in New Issue
Block a user