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 { Types } from "mongoose";
|
||||||
import { standardRequest } from "../../config/request";
|
import { standardRequest } from "../../config/request";
|
||||||
import { getApps, getTeams, revokeAccess } from "../../integrations";
|
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 { EventType } from "../../ee/models";
|
||||||
import { IntegrationService } from "../../services";
|
import { IntegrationService } from "../../services";
|
||||||
import { EEAuditLogService } from "../../ee/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) => {
|
export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
// TODO: check if access token is valid for each integration
|
// TODO: check if access token is valid for each integration
|
||||||
let integrationAuth;
|
|
||||||
const {
|
const {
|
||||||
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
|
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
|
||||||
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
|
} = 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");
|
if (!bot) throw new Error("Bot must be enabled to save integration access token");
|
||||||
|
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
let integrationAuth = await new IntegrationAuth({
|
||||||
{
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
integration,
|
||||||
integration
|
url,
|
||||||
},
|
namespace,
|
||||||
{
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
integration,
|
...(integration === INTEGRATION_GCP_SECRET_MANAGER
|
||||||
url,
|
? {
|
||||||
namespace,
|
metadata: {
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
authMethod: "serviceAccount"
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
|
||||||
...(integration === INTEGRATION_GCP_SECRET_MANAGER
|
|
||||||
? {
|
|
||||||
metadata: {
|
|
||||||
authMethod: "serviceAccount"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
: {})
|
}
|
||||||
},
|
: {})
|
||||||
{
|
}).save();
|
||||||
new: true,
|
|
||||||
upsert: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// encrypt and save integration access details
|
// encrypt and save integration access details
|
||||||
if (refreshToken) {
|
if (refreshToken) {
|
||||||
@@ -188,12 +177,12 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// encrypt and save integration access details
|
// encrypt and save integration access details
|
||||||
if (accessId || accessToken) {
|
if (accessId || accessToken) {
|
||||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
integrationAuth = (await IntegrationService.setIntegrationAuthAccess({
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
accessId,
|
accessId,
|
||||||
accessToken,
|
accessToken,
|
||||||
accessExpiresAt: undefined
|
accessExpiresAt: undefined
|
||||||
});
|
})) as IIntegrationAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error("Failed to save integration access token");
|
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]
|
* Delete integration authorization with id [integrationAuthId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
export const deleteIntegrationAuthById = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
params: { integrationAuthId }
|
params: { integrationAuthId }
|
||||||
} = await validateRequest(reqValidator.DeleteIntegrationAuthV1, req);
|
} = 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");
|
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(
|
await EEAuditLogService.createAuditLog(
|
||||||
req.authData,
|
req.authData,
|
||||||
|
|||||||
@@ -156,12 +156,20 @@ router.get(
|
|||||||
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
|
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
integrationAuthController.deleteIntegrationAuths
|
||||||
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:integrationAuthId",
|
"/:integrationAuthId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
integrationAuthController.deleteIntegrationAuth
|
integrationAuthController.deleteIntegrationAuthById
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
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({
|
export const DeleteIntegrationAuthV1 = z.object({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
integrationAuthId: z.string().trim()
|
integrationAuthId: z.string().trim()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export {
|
export {
|
||||||
useAuthorizeIntegration,
|
useAuthorizeIntegration,
|
||||||
useDeleteIntegrationAuth,
|
useDeleteIntegrationAuth,
|
||||||
|
useDeleteIntegrationAuths,
|
||||||
useGetIntegrationAuthApps,
|
useGetIntegrationAuthApps,
|
||||||
useGetIntegrationAuthBitBucketWorkspaces,
|
useGetIntegrationAuthBitBucketWorkspaces,
|
||||||
useGetIntegrationAuthById,
|
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();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, { id: string; workspaceId: string }>({
|
return useMutation<{}, {}, { id: string; workspaceId: string }>({
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export const useDeleteIntegration = () => {
|
|||||||
mutationFn: ({ id }) => apiRequest.delete(`/api/v1/integration/${id}`),
|
mutationFn: ({ id }) => apiRequest.delete(`/api/v1/integration/${id}`),
|
||||||
onSuccess: (_, { workspaceId }) => {
|
onSuccess: (_, { workspaceId }) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(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[] }>(
|
const { data } = await apiRequest.get<{ authorizations: IntegrationAuth[] }>(
|
||||||
`/api/v1/workspace/${workspaceId}/authorizations`
|
`/api/v1/workspace/${workspaceId}/authorizations`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.authorizations;
|
return data.authorizations;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { Button, Modal, ModalContent } from "@app/components/v2";
|
import { Button, Modal, ModalContent } from "@app/components/v2";
|
||||||
@@ -9,7 +8,7 @@ import { withProjectPermission } from "@app/hoc";
|
|||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useDeleteIntegration,
|
useDeleteIntegration,
|
||||||
useDeleteIntegrationAuth,
|
useDeleteIntegrationAuths,
|
||||||
useGetCloudIntegrations,
|
useGetCloudIntegrations,
|
||||||
useGetUserWsKey,
|
useGetUserWsKey,
|
||||||
useGetWorkspaceAuthorizations,
|
useGetWorkspaceAuthorizations,
|
||||||
@@ -24,8 +23,7 @@ import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSe
|
|||||||
import { IntegrationsSection } from "./components/IntegrationsSection";
|
import { IntegrationsSection } from "./components/IntegrationsSection";
|
||||||
import {
|
import {
|
||||||
generateBotKey,
|
generateBotKey,
|
||||||
redirectForProviderAuth,
|
redirectForProviderAuth
|
||||||
redirectToIntegrationAppConfigScreen
|
|
||||||
} from "./IntegrationPage.utils";
|
} from "./IntegrationPage.utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -36,7 +34,6 @@ export const IntegrationsPage = withProjectPermission(
|
|||||||
({ frameworkIntegrations }: Props) => {
|
({ frameworkIntegrations }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const workspaceId = currentWorkspace?._id || "";
|
const workspaceId = currentWorkspace?._id || "";
|
||||||
@@ -65,6 +62,7 @@ export const IntegrationsPage = withProjectPermission(
|
|||||||
return groupBy;
|
return groupBy;
|
||||||
}, [])
|
}, [])
|
||||||
);
|
);
|
||||||
|
|
||||||
// mutation
|
// mutation
|
||||||
const {
|
const {
|
||||||
data: integrations,
|
data: integrations,
|
||||||
@@ -78,11 +76,11 @@ export const IntegrationsPage = withProjectPermission(
|
|||||||
const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } =
|
const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } =
|
||||||
useUpdateBotActiveStatus();
|
useUpdateBotActiveStatus();
|
||||||
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
||||||
const {
|
const {
|
||||||
mutateAsync: deleteIntegrationAuth,
|
mutateAsync: deleteIntegrationAuths,
|
||||||
isSuccess: isDeleteIntegrationAuthSuccess,
|
isSuccess: isDeleteIntegrationAuthSuccess,
|
||||||
reset: resetDeleteIntegrationAuth
|
reset: resetDeleteIntegrationAuths
|
||||||
} = useDeleteIntegrationAuth();
|
} = useDeleteIntegrationAuths();
|
||||||
|
|
||||||
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
|
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
|
||||||
const isIntegrationsEmpty = !integrations?.length;
|
const isIntegrationsEmpty = !integrations?.length;
|
||||||
@@ -103,7 +101,7 @@ export const IntegrationsPage = withProjectPermission(
|
|||||||
botId: bot._id,
|
botId: bot._id,
|
||||||
workspaceId
|
workspaceId
|
||||||
});
|
});
|
||||||
resetDeleteIntegrationAuth();
|
resetDeleteIntegrationAuths();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isIntegrationFetching,
|
isIntegrationFetching,
|
||||||
@@ -116,7 +114,7 @@ export const IntegrationsPage = withProjectPermission(
|
|||||||
const handleProviderIntegration = async (provider: string) => {
|
const handleProviderIntegration = async (provider: string) => {
|
||||||
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
|
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
|
||||||
if (!selectedCloudIntegration) return;
|
if (!selectedCloudIntegration) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (bot && !bot.isActive) {
|
if (bot && !bot.isActive) {
|
||||||
const botKey = generateBotKey(bot.publicKey, latestWsKey!);
|
const botKey = generateBotKey(bot.publicKey, latestWsKey!);
|
||||||
@@ -127,14 +125,8 @@ export const IntegrationsPage = withProjectPermission(
|
|||||||
botId: bot._id
|
botId: bot._id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const integrationAuthForProvider = integrationAuths?.[provider];
|
|
||||||
if (!integrationAuthForProvider) {
|
|
||||||
redirectForProviderAuth(selectedCloudIntegration);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id);
|
redirectForProviderAuth(selectedCloudIntegration);
|
||||||
router.push(url);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
@@ -176,9 +168,10 @@ export const IntegrationsPage = withProjectPermission(
|
|||||||
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
|
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
|
||||||
const integrationAuthForProvider = integrationAuths?.[provider];
|
const integrationAuthForProvider = integrationAuths?.[provider];
|
||||||
if (!integrationAuthForProvider) return;
|
if (!integrationAuthForProvider) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteIntegrationAuth({
|
await deleteIntegrationAuths({
|
||||||
id: integrationAuthForProvider._id,
|
integration: provider,
|
||||||
workspaceId
|
workspaceId
|
||||||
});
|
});
|
||||||
if (cb) cb();
|
if (cb) cb();
|
||||||
|
|||||||
Reference in New Issue
Block a user