mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Updated bot, integration, and integrationAuth middlewares to support multiple clients
This commit is contained in:
@@ -1,17 +1,42 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Bot,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData
|
||||
} from '../models';
|
||||
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
||||
import { BotService } from '../services';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY
|
||||
} from '../variables';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
IntegrationAuthNotFoundError,
|
||||
IntegrationNotFoundError
|
||||
} from '../utils/errors';
|
||||
import RequestError from '../utils/requestError';
|
||||
import {
|
||||
validateClientForIntegrationAuth
|
||||
} from '../helpers/integrationAuth';
|
||||
import {
|
||||
validateUserClientForWorkspace
|
||||
} from '../helpers/user';
|
||||
import {
|
||||
validateServiceAccountClientForWorkspace
|
||||
} from '../helpers/serviceAccount';
|
||||
import { IntegrationService } from '../services';
|
||||
|
||||
interface Update {
|
||||
workspace: string;
|
||||
@@ -20,6 +45,84 @@ interface Update {
|
||||
accountId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for integration with id [integrationId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.integrationId - id of integration to validate against
|
||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateClientForIntegration = async ({
|
||||
authData,
|
||||
integrationId,
|
||||
acceptedRoles
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
integrationId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
}) => {
|
||||
|
||||
const integration = await Integration.findById(integrationId);
|
||||
if (!integration) throw IntegrationNotFoundError();
|
||||
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integrationId)
|
||||
.select(
|
||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||
|
||||
const accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id
|
||||
})).accessToken;
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integration.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: integration.workspace
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for integration'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integration.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for integration'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform OAuth2 code-token exchange for workspace with id [workspaceId] and integration
|
||||
* named [integration]
|
||||
@@ -140,7 +243,7 @@ const syncIntegrationsHelper = async ({
|
||||
|
||||
// get integration auth access token
|
||||
const access = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integration.integrationAuth.toString()
|
||||
integrationAuthId: integration.integrationAuth
|
||||
});
|
||||
|
||||
// sync secrets to integration
|
||||
@@ -167,7 +270,7 @@ const syncIntegrationsHelper = async ({
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} refreshToken - decrypted refresh token
|
||||
*/
|
||||
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
||||
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||
let refreshToken;
|
||||
|
||||
try {
|
||||
@@ -204,7 +307,7 @@ const syncIntegrationsHelper = async ({
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @returns {String} accessToken - decrypted access token
|
||||
*/
|
||||
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
||||
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||
let accessId;
|
||||
let accessToken;
|
||||
try {
|
||||
@@ -367,6 +470,7 @@ const setIntegrationAuthAccessHelper = async ({
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForIntegration,
|
||||
handleOAuthExchangeHelper,
|
||||
syncIntegrationsHelper,
|
||||
getIntegrationAuthRefreshHelper,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IntegrationAuth,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData,
|
||||
IWorkspace
|
||||
} from '../models';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import {
|
||||
IntegrationAuthNotFoundError,
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import { IntegrationService } from '../services';
|
||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for integration authorization with id [integrationAuthId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.integrationAuthId - id of integration authorization to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateClientForIntegrationAuth = async ({
|
||||
authData,
|
||||
integrationId,
|
||||
acceptedRoles
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
integrationId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
}) => {
|
||||
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integrationId)
|
||||
.populate<{ workspace: IWorkspace }>('workspace')
|
||||
.select(
|
||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||
|
||||
const accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id
|
||||
})).accessToken;
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integrationAuth.workspace._id,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: integrationAuth.workspace._id
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for integration authorization'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integrationAuth.workspace._id,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integrationAuth, accessToken });
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for integration authorization'
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForIntegrationAuth
|
||||
};
|
||||
@@ -1,34 +1,28 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { Bot } from '../models';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { validateClientForBot } from '../helpers/bot';
|
||||
import { AccountNotFoundError } from '../utils/errors';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
// TODO: transform
|
||||
|
||||
const requireBotAuth = ({
|
||||
acceptedRoles,
|
||||
location = 'params'
|
||||
locationBotId = 'params'
|
||||
}: {
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
location?: req;
|
||||
locationBotId?: req;
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const bot = await Bot.findById(req[location].botId);
|
||||
const { botId } = req[locationBotId];
|
||||
|
||||
if (!bot) {
|
||||
return next(AccountNotFoundError({message: 'Failed to locate Bot account'}))
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id,
|
||||
workspaceId: bot.workspace,
|
||||
req.bot = await validateClientForBot({
|
||||
authData: req.authData,
|
||||
botId: new Types.ObjectId(botId),
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
req.bot = bot;
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { Integration, IntegrationAuth } from '../models';
|
||||
import { IntegrationService } from '../services';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { validateClientForIntegration } from '../helpers/integration';
|
||||
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||
|
||||
/**
|
||||
@@ -19,36 +21,20 @@ const requireIntegrationAuth = ({
|
||||
// integration authorization middleware
|
||||
|
||||
const { integrationId } = req.params;
|
||||
|
||||
// validate integration accessibility
|
||||
const integration = await Integration.findOne({
|
||||
_id: integrationId
|
||||
});
|
||||
|
||||
if (!integration) {
|
||||
return next(IntegrationNotFoundError({message: 'Failed to locate Integration'}))
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id,
|
||||
workspaceId: integration.workspace,
|
||||
|
||||
const { integration, accessToken } = await validateClientForIntegration({
|
||||
authData: req.authData,
|
||||
integrationId: new Types.ObjectId(integrationId),
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findOne({
|
||||
_id: integration.integrationAuth
|
||||
}).select(
|
||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||
);
|
||||
|
||||
if (!integrationAuth) {
|
||||
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}))
|
||||
if (integration) {
|
||||
req.integration = integration;
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
req.accessToken = accessToken;
|
||||
}
|
||||
|
||||
req.integration = integration;
|
||||
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString()
|
||||
});
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { IntegrationAuth, IWorkspace } from '../models';
|
||||
import { IntegrationService } from '../services';
|
||||
import { validateClientForIntegrationAuth } from '../helpers/integrationAuth';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
|
||||
@@ -25,30 +27,19 @@ const requireIntegrationAuthorizationAuth = ({
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { integrationAuthId } = req[location];
|
||||
const integrationAuth = await IntegrationAuth.findOne({
|
||||
_id: integrationAuthId
|
||||
})
|
||||
.populate<{ workspace: IWorkspace }>('workspace')
|
||||
.select(
|
||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||
);
|
||||
|
||||
if (!integrationAuth) {
|
||||
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authorization credentials'}))
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id,
|
||||
workspaceId: integrationAuth.workspace._id,
|
||||
const { integrationAuth, accessToken } = await validateClientForIntegrationAuth({
|
||||
authData: req.authData,
|
||||
integrationId: new Types.ObjectId(integrationAuthId),
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
if (integrationAuth) {
|
||||
req.integrationAuth = integrationAuth;
|
||||
}
|
||||
|
||||
req.integrationAuth = integrationAuth;
|
||||
if (attachAccessToken) {
|
||||
const access = await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString()
|
||||
});
|
||||
req.accessToken = access.accessToken;
|
||||
if (accessToken) {
|
||||
req.accessToken = accessToken;
|
||||
}
|
||||
|
||||
return next();
|
||||
|
||||
@@ -25,7 +25,6 @@ const requireWorkspaceAuth = ({
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Schema, model, Types } from "mongoose";
|
||||
import { Schema, model, Types, Document } from "mongoose";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
INTEGRATION_TRAVISCI,
|
||||
} from "../variables";
|
||||
|
||||
export interface IIntegrationAuth {
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'aws-parameter-store' | 'aws-secret-manager';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
handleOAuthExchangeHelper,
|
||||
syncIntegrationsHelper,
|
||||
@@ -67,7 +68,7 @@ class IntegrationService {
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} refreshToken - decrypted refresh token
|
||||
*/
|
||||
static async getIntegrationAuthRefresh({ integrationAuthId }: { integrationAuthId: string}) {
|
||||
static async getIntegrationAuthRefresh({ integrationAuthId }: { integrationAuthId: Types.ObjectId}) {
|
||||
return await getIntegrationAuthRefreshHelper({
|
||||
integrationAuthId
|
||||
});
|
||||
@@ -80,7 +81,7 @@ class IntegrationService {
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} accessToken - decrypted access token
|
||||
*/
|
||||
static async getIntegrationAuthAccess({ integrationAuthId }: { integrationAuthId: string}) {
|
||||
static async getIntegrationAuthAccess({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) {
|
||||
return await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId
|
||||
});
|
||||
|
||||
@@ -73,6 +73,16 @@ export const ValidationError = (error?: Partial<RequestErrorContext>) => new Req
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
//* ----->[INTEGRATION AUTH ERRORS]<-----
|
||||
export const IntegrationAuthNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||
statusCode: error?.statusCode ?? 404,
|
||||
type: error?.type ?? 'integration_auth_not_found_error',
|
||||
message: error?.message ?? 'The requested integration authorization was not found',
|
||||
context: error?.context,
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
//* ----->[INTEGRATION ERRORS]<-----
|
||||
export const IntegrationNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||
|
||||
Reference in New Issue
Block a user