Merge pull request #495 from Infisical/improve-service-accounts
Improve service accounts / middleware + revamp images in documentation
@@ -15,32 +15,28 @@ import {
|
|||||||
const requireSecretSnapshotAuth = ({
|
const requireSecretSnapshotAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const { secretSnapshotId } = req.params;
|
||||||
const { secretSnapshotId } = req.params;
|
|
||||||
|
|
||||||
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId);
|
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId);
|
||||||
|
|
||||||
if (!secretSnapshot) {
|
if (!secretSnapshot) {
|
||||||
return next(SecretSnapshotNotFoundError({
|
return next(SecretSnapshotNotFoundError({
|
||||||
message: 'Failed to find secret snapshot'
|
message: 'Failed to find secret snapshot'
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id,
|
|
||||||
workspaceId: secretSnapshot.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
req.secretSnapshot = secretSnapshot as any;
|
|
||||||
|
|
||||||
next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret snapshot' }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId: secretSnapshot.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
req.secretSnapshot = secretSnapshot as any;
|
||||||
|
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import {
|
|||||||
} from '../../../middleware';
|
} from '../../../middleware';
|
||||||
import { query, param, body } from 'express-validator';
|
import { query, param, body } from 'express-validator';
|
||||||
import { secretController } from '../../controllers/v1';
|
import { secretController } from '../../controllers/v1';
|
||||||
import { ADMIN, MEMBER } from '../../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
|
} from '../../../variables';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:secretId/secret-versions',
|
'/:secretId/secret-versions',
|
||||||
@@ -15,7 +20,8 @@ router.get(
|
|||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
}),
|
}),
|
||||||
param('secretId').exists().trim(),
|
param('secretId').exists().trim(),
|
||||||
query('offset').exists().isInt(),
|
query('offset').exists().isInt(),
|
||||||
@@ -30,7 +36,8 @@ router.post(
|
|||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
param('secretId').exists().trim(),
|
param('secretId').exists().trim(),
|
||||||
body('version').exists().isInt(),
|
body('version').exists().isInt(),
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
BotKey,
|
BotKey,
|
||||||
Secret,
|
Secret,
|
||||||
ISecret,
|
ISecret,
|
||||||
IUser
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
generateKeyPair,
|
generateKeyPair,
|
||||||
@@ -12,8 +18,88 @@ import {
|
|||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
decryptAsymmetric
|
decryptAsymmetric
|
||||||
} from '../utils/crypto';
|
} from '../utils/crypto';
|
||||||
import { SECRET_SHARED } from '../variables';
|
import {
|
||||||
|
SECRET_SHARED,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
import { getEncryptionKey } from '../config';
|
import { getEncryptionKey } from '../config';
|
||||||
|
import { BotNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateMembership
|
||||||
|
} from '../helpers/membership';
|
||||||
|
import {
|
||||||
|
validateUserClientForWorkspace
|
||||||
|
} from '../helpers/user';
|
||||||
|
import {
|
||||||
|
validateServiceAccountClientForWorkspace
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for bot with id [botId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.botId - id of bot to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
*/
|
||||||
|
const validateClientForBot = async ({
|
||||||
|
authData,
|
||||||
|
botId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
botId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
const bot = await Bot.findById(botId);
|
||||||
|
|
||||||
|
if (!bot) throw BotNotFoundError();
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for bot'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw BotNotFoundError({
|
||||||
|
message: 'Failed client authorization for bot'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
||||||
@@ -222,6 +308,7 @@ const decryptSymmetricHelper = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForBot,
|
||||||
createBot,
|
createBot,
|
||||||
getSecretsHelper,
|
getSecretsHelper,
|
||||||
encryptSymmetricHelper,
|
encryptSymmetricHelper,
|
||||||
|
|||||||
@@ -1,17 +1,42 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
Integration,
|
Integration,
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
||||||
import { BotService } from '../services';
|
import { BotService } from '../services';
|
||||||
import {
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import {
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
IntegrationAuthNotFoundError,
|
||||||
|
IntegrationNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
import RequestError from '../utils/requestError';
|
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 {
|
interface Update {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
@@ -20,6 +45,84 @@ interface Update {
|
|||||||
accountId?: string;
|
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(integration.integrationAuth)
|
||||||
|
.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
|
* Perform OAuth2 code-token exchange for workspace with id [workspaceId] and integration
|
||||||
* named [integration]
|
* named [integration]
|
||||||
@@ -140,7 +243,7 @@ const syncIntegrationsHelper = async ({
|
|||||||
|
|
||||||
// get integration auth access token
|
// get integration auth access token
|
||||||
const access = await getIntegrationAuthAccessHelper({
|
const access = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: integration.integrationAuth.toString()
|
integrationAuthId: integration.integrationAuth
|
||||||
});
|
});
|
||||||
|
|
||||||
// sync secrets to integration
|
// sync secrets to integration
|
||||||
@@ -167,7 +270,7 @@ const syncIntegrationsHelper = async ({
|
|||||||
* @param {String} obj.integrationAuthId - id of integration auth
|
* @param {String} obj.integrationAuthId - id of integration auth
|
||||||
* @param {String} refreshToken - decrypted refresh token
|
* @param {String} refreshToken - decrypted refresh token
|
||||||
*/
|
*/
|
||||||
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||||
let refreshToken;
|
let refreshToken;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -204,7 +307,7 @@ const syncIntegrationsHelper = async ({
|
|||||||
* @param {String} obj.integrationAuthId - id of integration auth
|
* @param {String} obj.integrationAuthId - id of integration auth
|
||||||
* @returns {String} accessToken - decrypted access token
|
* @returns {String} accessToken - decrypted access token
|
||||||
*/
|
*/
|
||||||
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||||
let accessId;
|
let accessId;
|
||||||
let accessToken;
|
let accessToken;
|
||||||
try {
|
try {
|
||||||
@@ -367,6 +470,7 @@ const setIntegrationAuthAccessHelper = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForIntegration,
|
||||||
handleOAuthExchangeHelper,
|
handleOAuthExchangeHelper,
|
||||||
syncIntegrationsHelper,
|
syncIntegrationsHelper,
|
||||||
getIntegrationAuthRefreshHelper,
|
getIntegrationAuthRefreshHelper,
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
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,
|
||||||
|
integrationAuthId,
|
||||||
|
acceptedRoles,
|
||||||
|
attachAccessToken
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
integrationAuthId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
attachAccessToken?: boolean;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const integrationAuth = await IntegrationAuth
|
||||||
|
.findById(integrationAuthId)
|
||||||
|
.populate<{ workspace: IWorkspace }>('workspace')
|
||||||
|
.select(
|
||||||
|
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||||
|
|
||||||
|
let accessToken;
|
||||||
|
if (attachAccessToken) {
|
||||||
|
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,10 +1,106 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Membership, Key } from '../models';
|
import {
|
||||||
|
Membership,
|
||||||
|
Key,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
MembershipNotFoundError,
|
MembershipNotFoundError,
|
||||||
BadRequestError
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
import {
|
||||||
|
validateUserClientForWorkspace
|
||||||
|
} from '../helpers/user';
|
||||||
|
import {
|
||||||
|
validateServiceAccountClientForWorkspace
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
import {
|
||||||
|
validateServiceTokenDataClientForWorkspace
|
||||||
|
} from '../helpers/serviceTokenData';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for membership with id [membershipId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.membershipId - id of membership to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspaceRoles
|
||||||
|
* @returns {Membership} - validated membership
|
||||||
|
*/
|
||||||
|
const validateClientForMembership = async ({
|
||||||
|
authData,
|
||||||
|
membershipId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
membershipId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const membership = await Membership.findById(membershipId);
|
||||||
|
|
||||||
|
if (!membership) throw MembershipNotFoundError({
|
||||||
|
message: 'Failed to find membership'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
workspaceId: new Types.ObjectId(membership.workspace)
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode == AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
||||||
@@ -21,7 +117,7 @@ const validateMembership = async ({
|
|||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId: Types.ObjectId;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
acceptedRoles?: string[];
|
acceptedRoles?: Array<'admin' | 'member'>;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const membership = await Membership.findOne({
|
const membership = await Membership.findOne({
|
||||||
@@ -35,7 +131,7 @@ const validateMembership = async ({
|
|||||||
|
|
||||||
if (acceptedRoles) {
|
if (acceptedRoles) {
|
||||||
if (!acceptedRoles.includes(membership.role)) {
|
if (!acceptedRoles.includes(membership.role)) {
|
||||||
throw BadRequestError({ message: 'Failed to validate workspace membership role' });
|
throw BadRequestError({ message: 'Failed authorization for membership role' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +230,7 @@ const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForMembership,
|
||||||
validateMembership,
|
validateMembership,
|
||||||
addMemberships,
|
addMemberships,
|
||||||
findMembership,
|
findMembership,
|
||||||
|
|||||||
@@ -1,10 +1,98 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { MembershipOrg, Workspace, Membership, Key } from '../models';
|
import {
|
||||||
|
MembershipOrg,
|
||||||
|
Workspace,
|
||||||
|
Membership,
|
||||||
|
Key,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
MembershipOrgNotFoundError,
|
MembershipOrgNotFoundError,
|
||||||
BadRequestError
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.membershipOrgId - id of organization membership to validate against
|
||||||
|
* @param {Array<'owner' | 'admin' | 'member'>} obj.acceptedRoles - accepted organization roles
|
||||||
|
* @param {MembershipOrg} - validated organization membership
|
||||||
|
*/
|
||||||
|
const validateClientForMembershipOrg = async ({
|
||||||
|
authData,
|
||||||
|
membershipOrgId,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
membershipOrgId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
}) => {
|
||||||
|
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||||
|
|
||||||
|
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
||||||
|
message: 'Failed to find organization membership '
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: authData.authPayload._id,
|
||||||
|
organizationId: membershipOrg.organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
if (!authData.authPayload.organization.equals(membershipOrg.organization)) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account client authorization for organization membership'
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account client authorization for organization membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: authData.authPayload._id,
|
||||||
|
organizationId: membershipOrg.organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for organization membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
||||||
@@ -22,8 +110,8 @@ const validateMembershipOrg = async ({
|
|||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId: Types.ObjectId;
|
||||||
organizationId: Types.ObjectId;
|
organizationId: Types.ObjectId;
|
||||||
acceptedRoles: string[];
|
acceptedRoles?: Array<'owner' | 'admin' | 'member'>;
|
||||||
acceptedStatuses: string[];
|
acceptedStatuses?: Array<'invited' | 'accepted'>;
|
||||||
}) => {
|
}) => {
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
user: userId,
|
user: userId,
|
||||||
@@ -34,12 +122,16 @@ const validateMembershipOrg = async ({
|
|||||||
throw MembershipOrgNotFoundError({ message: 'Failed to find organization membership' });
|
throw MembershipOrgNotFoundError({ message: 'Failed to find organization membership' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!acceptedRoles.includes(membershipOrg.role)) {
|
if (acceptedRoles) {
|
||||||
throw BadRequestError({ message: 'Failed to validate organization membership role' });
|
if (!acceptedRoles.includes(membershipOrg.role)) {
|
||||||
|
throw UnauthorizedRequestError({ message: 'Failed to validate organization membership role' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
if (acceptedStatuses) {
|
||||||
throw BadRequestError({ message: 'Failed to validate organization membership status' });
|
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
||||||
|
throw UnauthorizedRequestError({ message: 'Failed to validate organization membership status' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return membershipOrg;
|
return membershipOrg;
|
||||||
@@ -164,6 +256,7 @@ const deleteMembershipOrg = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForMembershipOrg,
|
||||||
validateMembershipOrg,
|
validateMembershipOrg,
|
||||||
findMembershipOrg,
|
findMembershipOrg,
|
||||||
addMembershipsOrg,
|
addMembershipsOrg,
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import {
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_API_KEY
|
AUTH_MODE_API_KEY,
|
||||||
|
OWNER
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
getStripeSecretKey,
|
getStripeSecretKey,
|
||||||
@@ -24,8 +25,15 @@ import {
|
|||||||
getStripeProductStarter
|
getStripeProductStarter
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import {
|
import {
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError,
|
||||||
|
OrganizationNotFoundError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateUserClientForOrganization
|
||||||
|
} from '../helpers/user';
|
||||||
|
import {
|
||||||
|
validateServiceAccountClientForOrganization
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate accepted clients for organization with id [organizationId]
|
* Validate accepted clients for organization with id [organizationId]
|
||||||
@@ -35,34 +43,66 @@ import {
|
|||||||
*/
|
*/
|
||||||
const validateClientForOrganization = async ({
|
const validateClientForOrganization = async ({
|
||||||
authData,
|
authData,
|
||||||
organizationId
|
organizationId,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
}: {
|
}: {
|
||||||
authData: {
|
authData: {
|
||||||
authMode: string;
|
authMode: string;
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
},
|
},
|
||||||
organizationId: string;
|
organizationId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
}) => {
|
}) => {
|
||||||
// TODO
|
|
||||||
|
const organization = await Organization.findById(organizationId);
|
||||||
|
|
||||||
|
if (!organization) {
|
||||||
|
throw OrganizationNotFoundError({
|
||||||
|
message: 'Failed to find organization'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
// TODO
|
const membershipOrg = await validateUserClientForOrganization({
|
||||||
|
user: authData.authPayload,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ organization, membershipOrg });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
// TODO
|
await validateServiceAccountClientForOrganization({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
organization
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ organization });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
// TODO
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for organization'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
// TODO
|
const membershipOrg = await validateUserClientForOrganization({
|
||||||
|
user: authData.authPayload,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ organization, membershipOrg });
|
||||||
}
|
}
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: 'Failed client authorization for organization resource'
|
message: 'Failed client authorization for organization'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +268,7 @@ const updateSubscriptionOrgQuantity = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForOrganization,
|
||||||
createOrganization,
|
createOrganization,
|
||||||
initSubscriptionOrg,
|
initSubscriptionOrg,
|
||||||
updateSubscriptionOrgQuantity
|
updateSubscriptionOrgQuantity
|
||||||
|
|||||||
@@ -10,15 +10,24 @@ import {
|
|||||||
ISecret
|
ISecret
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
|
validateMembership
|
||||||
|
} from '../helpers/membership';
|
||||||
|
import {
|
||||||
|
validateUserClientForSecret,
|
||||||
validateUserClientForSecrets
|
validateUserClientForSecrets
|
||||||
} from '../helpers/user';
|
} from '../helpers/user';
|
||||||
import {
|
import {
|
||||||
validateServiceTokenDataClientForSecrets
|
validateServiceTokenDataClientForSecrets, validateServiceTokenDataClientForWorkspace
|
||||||
} from '../helpers/serviceTokenData';
|
} from '../helpers/serviceTokenData';
|
||||||
import {
|
import {
|
||||||
validateServiceAccountClientForSecrets
|
validateServiceAccountClientForSecrets,
|
||||||
|
validateServiceAccountClientForWorkspace
|
||||||
} from '../helpers/serviceAccount';
|
} from '../helpers/serviceAccount';
|
||||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
SecretNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
@@ -27,12 +36,91 @@ import {
|
|||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate accepted clients for secrets with ids [secretIds]
|
* Validate authenticated clients for secrets with id [secretId] based
|
||||||
|
* on any known permissions.
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {User} obj.user - user client
|
* @param {Object} obj.authData - authenticated client details
|
||||||
* @param {ServiceAccount} obj.serviceAccount - service account client
|
* @param {Types.ObjectId} obj.secretId - id of secret to validate against
|
||||||
* @param {ServiceTokenData} obj.service - service token client
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
* @param {String[]} obj.secretIds - ids of secrets to validate against
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateClientForSecret = async ({
|
||||||
|
authData,
|
||||||
|
secretId,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
},
|
||||||
|
secretId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions: string[];
|
||||||
|
}) => {
|
||||||
|
const secret = await Secret.findById(secretId);
|
||||||
|
|
||||||
|
if (!secret) throw SecretNotFoundError({
|
||||||
|
message: 'Failed to find secret'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecret({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
environment: secret.environment,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
environment: secret.environment
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecret({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for secret'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for secrets with ids [secretIds] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId[]} obj.secretIds - id of workspace 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 validateClientForSecrets = async ({
|
const validateClientForSecrets = async ({
|
||||||
authData,
|
authData,
|
||||||
@@ -43,7 +131,7 @@ const validateClientForSecrets = async ({
|
|||||||
authMode: string;
|
authMode: string;
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
},
|
},
|
||||||
secretIds: string[];
|
secretIds: Types.ObjectId[];
|
||||||
requiredPermissions: string[];
|
requiredPermissions: string[];
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
@@ -51,7 +139,7 @@ const validateClientForSecrets = async ({
|
|||||||
|
|
||||||
secrets = await Secret.find({
|
secrets = await Secret.find({
|
||||||
_id: {
|
_id: {
|
||||||
$in: secretIds.map((secretId: string) => new Types.ObjectId(secretId))
|
$in: secretIds
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -105,5 +193,6 @@ const validateClientForSecrets = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForSecret,
|
||||||
validateClientForSecrets
|
validateClientForSecrets
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
IServiceTokenData,
|
IServiceTokenData,
|
||||||
ISecret,
|
ISecret,
|
||||||
|
IOrganization,
|
||||||
|
IServiceAccountWorkspacePermission,
|
||||||
ServiceAccountWorkspacePermission
|
ServiceAccountWorkspacePermission
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
@@ -109,10 +111,10 @@ const validateClientForServiceAccount = async ({
|
|||||||
environment?: string;
|
environment?: string;
|
||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: add service account API support for workspace-level endpoints that are not
|
|
||||||
// tied to any specific environment
|
|
||||||
|
|
||||||
if (environment) {
|
if (environment) {
|
||||||
|
// case: environment specified ->
|
||||||
|
// evaluate service account authorization for workspace
|
||||||
|
// in the context of a specific environment [environment]
|
||||||
const permission = await ServiceAccountWorkspacePermission.findOne({
|
const permission = await ServiceAccountWorkspacePermission.findOne({
|
||||||
serviceAccount,
|
serviceAccount,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
@@ -123,7 +125,6 @@ const validateClientForServiceAccount = async ({
|
|||||||
message: 'Failed service account authorization for the given workspace environment'
|
message: 'Failed service account authorization for the given workspace environment'
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: refactor
|
|
||||||
let runningIsDisallowed = false;
|
let runningIsDisallowed = false;
|
||||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
switch (requiredPermission) {
|
switch (requiredPermission) {
|
||||||
@@ -143,6 +144,20 @@ const validateClientForServiceAccount = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// case: no environment specified ->
|
||||||
|
// evaluate service account authorization for workspace
|
||||||
|
// without need of environment [environment]
|
||||||
|
|
||||||
|
const permission = await ServiceAccountWorkspacePermission.findOne({
|
||||||
|
serviceAccount,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permission) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account authorization for the given workspace'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +195,6 @@ const validateClientForServiceAccount = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
// TODO: refactor
|
|
||||||
let runningIsDisallowed = false;
|
let runningIsDisallowed = false;
|
||||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
switch (requiredPermission) {
|
switch (requiredPermission) {
|
||||||
@@ -202,9 +216,6 @@ const validateClientForServiceAccount = async ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,9 +242,30 @@ const validateServiceAccountClientForServiceAccount = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service account (client) can access organization [organization]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - service account client
|
||||||
|
* @param {Organization} obj.organization - organization to validate against
|
||||||
|
*/
|
||||||
|
const validateServiceAccountClientForOrganization = async ({
|
||||||
|
serviceAccount,
|
||||||
|
organization
|
||||||
|
}: {
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
organization: IOrganization;
|
||||||
|
}) => {
|
||||||
|
if (!serviceAccount.organization.equals(organization._id)) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account authorization for the given organization'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateClientForServiceAccount,
|
validateClientForServiceAccount,
|
||||||
validateServiceAccountClientForWorkspace,
|
validateServiceAccountClientForWorkspace,
|
||||||
validateServiceAccountClientForSecrets,
|
validateServiceAccountClientForSecrets,
|
||||||
validateServiceAccountClientForServiceAccount
|
validateServiceAccountClientForServiceAccount,
|
||||||
|
validateServiceAccountClientForOrganization
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,94 @@
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
ISecret,
|
ISecret,
|
||||||
IServiceTokenData
|
IServiceTokenData,
|
||||||
|
ServiceTokenData,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import {
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
ServiceTokenDataNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for service token with id [serviceTokenId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.serviceTokenData - id of service token to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
*/
|
||||||
|
const validateClientForServiceTokenData = async ({
|
||||||
|
authData,
|
||||||
|
serviceTokenDataId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
serviceTokenDataId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
const serviceTokenData = await ServiceTokenData
|
||||||
|
.findById(serviceTokenDataId)
|
||||||
|
.select('+encryptedKey +iv +tag')
|
||||||
|
.populate<{ user: IUser }>('user');
|
||||||
|
|
||||||
|
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({
|
||||||
|
message: 'Failed to find service token data'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: serviceTokenData.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceTokenData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: serviceTokenData.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceTokenData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for service token data'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: serviceTokenData.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceTokenData;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for service token data'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that service token (client) can access workspace
|
* Validate that service token (client) can access workspace
|
||||||
@@ -34,20 +119,24 @@ import { UnauthorizedRequestError } from '../utils/errors';
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceTokenData.environment !== environment) {
|
if (environment) {
|
||||||
// case: invalid environment passed
|
// case: environment is specified
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed service token authorization for the given workspace environment'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
requiredPermissions?.forEach((permission) => {
|
if (serviceTokenData.environment !== environment) {
|
||||||
if (!serviceTokenData.permissions.includes(permission)) {
|
// case: invalid environment passed
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: `Failed service token authorization for the given workspace environment action: ${permission}`
|
message: 'Failed service token authorization for the given workspace environment'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
requiredPermissions?.forEach((permission) => {
|
||||||
|
if (!serviceTokenData.permissions.includes(permission)) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed service token authorization for the given workspace environment action: ${permission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,6 +183,7 @@ import { UnauthorizedRequestError } from '../utils/errors';
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForServiceTokenData,
|
||||||
validateServiceTokenDataClientForWorkspace,
|
validateServiceTokenDataClientForWorkspace,
|
||||||
validateServiceTokenDataClientForSecrets
|
validateServiceTokenDataClientForSecrets
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,9 @@ import {
|
|||||||
ISecret,
|
ISecret,
|
||||||
IServiceAccount,
|
IServiceAccount,
|
||||||
User,
|
User,
|
||||||
Membership
|
Membership,
|
||||||
|
IOrganization,
|
||||||
|
Organization,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { sendMail } from './nodemailer';
|
import { sendMail } from './nodemailer';
|
||||||
import { validateMembership } from './membership';
|
import { validateMembership } from './membership';
|
||||||
@@ -177,21 +179,23 @@ const validateUserClientForWorkspace = async ({
|
|||||||
user,
|
user,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
}: {
|
}: {
|
||||||
user: IUser;
|
user: IUser;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
// validate user membership in workspace
|
// validate user membership in workspace
|
||||||
const membership = await validateMembership({
|
const membership = await validateMembership({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
workspaceId
|
workspaceId,
|
||||||
|
acceptedRoles
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: refactor
|
|
||||||
let runningIsDisallowed = false;
|
let runningIsDisallowed = false;
|
||||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
switch (requiredPermission) {
|
switch (requiredPermission) {
|
||||||
@@ -215,6 +219,42 @@ const validateUserClientForWorkspace = async ({
|
|||||||
return membership;
|
return membership;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access secret [secret]
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateUserClientForSecret = async ({
|
||||||
|
user,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
secret: ISecret;
|
||||||
|
acceptedRoles?: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
const membership = await validateMembership({
|
||||||
|
userId: user._id,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||||
|
const isDisallowed = _.some(membership.deniedPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
|
||||||
|
if (isDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'You do not have the required permissions to perform this action'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user (client) can access secrets [secrets]
|
* Validate that user (client) can access secrets [secrets]
|
||||||
* with required permissions [requiredPermissions]
|
* with required permissions [requiredPermissions]
|
||||||
@@ -232,7 +272,8 @@ const validateUserClientForWorkspace = async ({
|
|||||||
secrets: ISecret[];
|
secrets: ISecret[];
|
||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: refactor
|
|
||||||
|
// TODO: add acceptedRoles?
|
||||||
|
|
||||||
const userMemberships = await Membership.find({ user: user._id })
|
const userMemberships = await Membership.find({ user: user._id })
|
||||||
const userMembershipById = _.keyBy(userMemberships, 'workspace');
|
const userMembershipById = _.keyBy(userMemberships, 'workspace');
|
||||||
@@ -288,11 +329,40 @@ const validateUserClientForServiceAccount = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access organization [organization]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Organization} obj.organization - organization to validate against
|
||||||
|
*/
|
||||||
|
const validateUserClientForOrganization = async ({
|
||||||
|
user,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
organization: IOrganization;
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
}) => {
|
||||||
|
const membershipOrg = await validateMembershipOrg({
|
||||||
|
userId: user._id,
|
||||||
|
organizationId: organization._id,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
setupAccount,
|
setupAccount,
|
||||||
completeAccount,
|
completeAccount,
|
||||||
checkUserDevice,
|
checkUserDevice,
|
||||||
validateUserClientForWorkspace,
|
validateUserClientForWorkspace,
|
||||||
validateUserClientForSecrets,
|
validateUserClientForSecrets,
|
||||||
validateUserClientForServiceAccount
|
validateUserClientForServiceAccount,
|
||||||
|
validateUserClientForOrganization,
|
||||||
|
validateUserClientForSecret
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { validateUserClientForWorkspace } from '../helpers/user';
|
|||||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||||
import { validateServiceTokenDataClientForWorkspace } from '../helpers/serviceTokenData';
|
import { validateServiceTokenDataClientForWorkspace } from '../helpers/serviceTokenData';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError, WorkspaceNotFoundError } from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
@@ -34,28 +34,38 @@ import {
|
|||||||
* @param {Object} obj.authData - authenticated client details
|
* @param {Object} obj.authData - authenticated client details
|
||||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
* @param {String} obj.environment - (optional) environment in workspace 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
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
const validateClientForWorkspace = async ({
|
const validateClientForWorkspace = async ({
|
||||||
authData,
|
authData,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
}: {
|
}: {
|
||||||
authData: {
|
authData: {
|
||||||
authMode: string;
|
authMode: string;
|
||||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
},
|
};
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
const workspace = await Workspace.findById(workspaceId);
|
||||||
|
|
||||||
|
if (!workspace) throw WorkspaceNotFoundError({
|
||||||
|
message: 'Failed to find workspace'
|
||||||
|
});
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
const membership = await validateUserClientForWorkspace({
|
const membership = await validateUserClientForWorkspace({
|
||||||
user: authData.authPayload,
|
user: authData.authPayload,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,6 +99,7 @@ const validateClientForWorkspace = async ({
|
|||||||
user: authData.authPayload,
|
user: authData.authPayload,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,7 +107,7 @@ const validateClientForWorkspace = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: 'Failed client authorization for workspace resource'
|
message: 'Failed client authorization for workspace'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const requireAuth = ({
|
|||||||
acceptedAuthModes: string[];
|
acceptedAuthModes: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|
||||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||||
// and return token type [authTokenType] and value [authTokenValue]
|
// and return token type [authTokenType] and value [authTokenValue]
|
||||||
const { authMode, authTokenValue } = validateAuthMode({
|
const { authMode, authTokenValue } = validateAuthMode({
|
||||||
@@ -87,7 +88,7 @@ const requireAuth = ({
|
|||||||
|
|
||||||
req.authData = {
|
req.authData = {
|
||||||
authMode,
|
authMode,
|
||||||
authPayload
|
authPayload // User, ServiceAccount, ServiceTokenData
|
||||||
}
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -1,32 +1,28 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { Bot } from '../models';
|
import { Bot } from '../models';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { validateClientForBot } from '../helpers/bot';
|
||||||
import { AccountNotFoundError } from '../utils/errors';
|
import { AccountNotFoundError } from '../utils/errors';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
const requireBotAuth = ({
|
const requireBotAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
location = 'params'
|
locationBotId = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
location?: req;
|
locationBotId?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const bot = await Bot.findById(req[location].botId);
|
const { botId } = req[locationBotId];
|
||||||
|
|
||||||
if (!bot) {
|
req.bot = await validateClientForBot({
|
||||||
return next(AccountNotFoundError({message: 'Failed to locate Bot account'}))
|
authData: req.authData,
|
||||||
}
|
botId: new Types.ObjectId(botId),
|
||||||
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id,
|
|
||||||
workspaceId: bot.workspace,
|
|
||||||
acceptedRoles
|
acceptedRoles
|
||||||
});
|
});
|
||||||
|
|
||||||
req.bot = bot;
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { Integration, IntegrationAuth } from '../models';
|
import { Integration, IntegrationAuth } from '../models';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../services';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { validateClientForIntegration } from '../helpers/integration';
|
||||||
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,42 +15,24 @@ import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/err
|
|||||||
const requireIntegrationAuth = ({
|
const requireIntegrationAuth = ({
|
||||||
acceptedRoles
|
acceptedRoles
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// integration authorization middleware
|
|
||||||
|
|
||||||
const { integrationId } = req.params;
|
const { integrationId } = req.params;
|
||||||
|
|
||||||
// validate integration accessibility
|
const { integration, accessToken } = await validateClientForIntegration({
|
||||||
const integration = await Integration.findOne({
|
authData: req.authData,
|
||||||
_id: integrationId
|
integrationId: new Types.ObjectId(integrationId),
|
||||||
});
|
|
||||||
|
|
||||||
if (!integration) {
|
|
||||||
return next(IntegrationNotFoundError({message: 'Failed to locate Integration'}))
|
|
||||||
}
|
|
||||||
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id,
|
|
||||||
workspaceId: integration.workspace,
|
|
||||||
acceptedRoles
|
acceptedRoles
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth.findOne({
|
if (integration) {
|
||||||
_id: integration.integrationAuth
|
req.integration = integration;
|
||||||
}).select(
|
|
||||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!integrationAuth) {
|
|
||||||
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.integration = integration;
|
if (accessToken) {
|
||||||
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
|
req.accessToken = accessToken;
|
||||||
integrationAuthId: integrationAuth._id.toString()
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { IntegrationAuth, IWorkspace } from '../models';
|
import { IntegrationAuth, IWorkspace } from '../models';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../services';
|
||||||
|
import { validateClientForIntegrationAuth } from '../helpers/integrationAuth';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
@@ -19,36 +21,26 @@ const requireIntegrationAuthorizationAuth = ({
|
|||||||
attachAccessToken = true,
|
attachAccessToken = true,
|
||||||
location = 'params'
|
location = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
attachAccessToken?: boolean;
|
attachAccessToken?: boolean;
|
||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { integrationAuthId } = req[location];
|
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) {
|
const { integrationAuth, accessToken } = await validateClientForIntegrationAuth({
|
||||||
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authorization credentials'}))
|
authData: req.authData,
|
||||||
}
|
integrationAuthId: new Types.ObjectId(integrationAuthId),
|
||||||
|
acceptedRoles,
|
||||||
await validateMembership({
|
attachAccessToken
|
||||||
userId: req.user._id,
|
|
||||||
workspaceId: integrationAuth.workspace._id,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
});
|
||||||
|
|
||||||
req.integrationAuth = integrationAuth;
|
if (integrationAuth) {
|
||||||
if (attachAccessToken) {
|
req.integrationAuth = integrationAuth;
|
||||||
const access = await IntegrationService.getIntegrationAuthAccess({
|
}
|
||||||
integrationAuthId: integrationAuth._id.toString()
|
|
||||||
});
|
if (accessToken) {
|
||||||
req.accessToken = access.accessToken;
|
req.accessToken = accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
Membership,
|
Membership,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import {
|
||||||
|
validateClientForMembership,
|
||||||
|
validateMembership
|
||||||
|
} from '../helpers/membership';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
@@ -16,43 +20,25 @@ type req = 'params' | 'body' | 'query';
|
|||||||
*/
|
*/
|
||||||
const requireMembershipAuth = ({
|
const requireMembershipAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
location = 'params'
|
locationMembershipId = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
location?: req;
|
locationMembershipId: req
|
||||||
}) => {
|
}) => {
|
||||||
return async (
|
return async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
try {
|
const { membershipId } = req[locationMembershipId];
|
||||||
const { membershipId } = req[location];
|
|
||||||
|
|
||||||
const membership = await Membership.findById(membershipId);
|
req.targetMembership = await validateClientForMembership({
|
||||||
|
authData: req.authData,
|
||||||
|
membershipId: new Types.ObjectId(membershipId),
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
if (!membership) throw new Error('Failed to find target membership');
|
return next();
|
||||||
|
|
||||||
const userMembership = await Membership.findOne({
|
|
||||||
workspace: membership.workspace
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!userMembership) throw new Error('Failed to validate own membership')
|
|
||||||
|
|
||||||
const targetMembership = await validateMembership({
|
|
||||||
userId: req.user._id,
|
|
||||||
workspaceId: membership.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
req.targetMembership = targetMembership;
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({
|
|
||||||
message: 'Unable to validate workspace membership'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
MembershipOrg
|
MembershipOrg
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { validateMembershipOrg } from '../helpers/membershipOrg';
|
import {
|
||||||
|
validateClientForMembershipOrg,
|
||||||
|
validateMembershipOrg
|
||||||
|
} from '../helpers/membershipOrg';
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: transform
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,32 +24,23 @@ type req = 'params' | 'body' | 'query';
|
|||||||
const requireMembershipOrgAuth = ({
|
const requireMembershipOrgAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses,
|
acceptedStatuses,
|
||||||
location = 'params'
|
locationMembershipOrgId = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
acceptedStatuses: string[];
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
location?: req;
|
locationMembershipOrgId?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const { membershipId } = req[locationMembershipOrgId];
|
||||||
const { membershipId } = req[location];
|
|
||||||
const membershipOrg = await MembershipOrg.findById(membershipId);
|
|
||||||
|
|
||||||
if (!membershipOrg) throw new Error('Failed to find target organization membership');
|
req.membershipOrg = await validateClientForMembershipOrg({
|
||||||
|
authData: req.authData,
|
||||||
|
membershipOrgId: new Types.ObjectId(membershipId),
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
req.targetMembership = await validateMembershipOrg({
|
return next();
|
||||||
userId: req.user._id,
|
|
||||||
organizationId: membershipOrg.organization,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
});
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({
|
|
||||||
message: 'Unable to validate organization membership'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Types } from 'mongoose';
|
|||||||
import { IOrganization, MembershipOrg } from '../models';
|
import { IOrganization, MembershipOrg } from '../models';
|
||||||
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
||||||
import { validateMembershipOrg } from '../helpers/membershipOrg';
|
import { validateMembershipOrg } from '../helpers/membershipOrg';
|
||||||
|
import { validateClientForOrganization } from '../helpers/organization';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
@@ -16,21 +17,30 @@ type req = 'params' | 'body' | 'query';
|
|||||||
const requireOrganizationAuth = ({
|
const requireOrganizationAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses,
|
acceptedStatuses,
|
||||||
location = 'params'
|
locationOrganizationId = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
acceptedStatuses: string[];
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
location?: req;
|
locationOrganizationId?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { organizationId } = req[location];
|
const { organizationId } = req[locationOrganizationId];
|
||||||
req.membershipOrg = await validateMembershipOrg({
|
|
||||||
userId: req.user._id,
|
const { organization, membershipOrg } = await validateClientForOrganization({
|
||||||
|
authData: req.authData,
|
||||||
organizationId: new Types.ObjectId(organizationId),
|
organizationId: new Types.ObjectId(organizationId),
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses
|
acceptedStatuses
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (organization) {
|
||||||
|
req.organization = organization;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (membershipOrg) {
|
||||||
|
req.membershipOrg = membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { UnauthorizedRequestError, SecretNotFoundError } from '../utils/errors';
|
import { UnauthorizedRequestError, SecretNotFoundError } from '../utils/errors';
|
||||||
import { Secret } from '../models';
|
import { Secret } from '../models';
|
||||||
import {
|
import {
|
||||||
validateMembership
|
validateMembership
|
||||||
} from '../helpers/membership';
|
} from '../helpers/membership';
|
||||||
|
import {
|
||||||
|
validateClientForSecret
|
||||||
|
} from '../helpers/secrets';
|
||||||
|
|
||||||
// note: used for old /v1/secret and /v2/secret routes.
|
// note: used for old /v1/secret and /v2/secret routes.
|
||||||
// newer /v2/secrets routes use [requireSecretsAuth] middleware
|
// newer /v2/secrets routes use [requireSecretsAuth] middleware with the exception
|
||||||
|
// of some /ee endpoints
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if user on request has proper membership to modify secret.
|
* Validate if user on request has proper membership to modify secret.
|
||||||
@@ -15,34 +20,25 @@ import {
|
|||||||
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
|
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
|
||||||
*/
|
*/
|
||||||
const requireSecretAuth = ({
|
const requireSecretAuth = ({
|
||||||
acceptedRoles
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const { secretId } = req.params;
|
||||||
const { secretId } = req.params;
|
|
||||||
|
|
||||||
const secret = await Secret.findById(secretId);
|
const secret = await validateClientForSecret({
|
||||||
|
authData: req.authData,
|
||||||
|
secretId: new Types.ObjectId(secretId),
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
if (!secret) {
|
req._secret = secret;
|
||||||
return next(SecretNotFoundError({
|
|
||||||
message: 'Failed to find secret'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
await validateMembership({
|
next();
|
||||||
userId: req.user._id,
|
|
||||||
workspaceId: secret.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
req._secret = secret;
|
|
||||||
|
|
||||||
next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret' }));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
import { Secret, Membership } from '../models';
|
import { Secret, Membership } from '../models';
|
||||||
import { validateClientForSecrets } from '../helpers/secrets';
|
import { validateClientForSecrets } from '../helpers/secrets';
|
||||||
@@ -24,7 +25,7 @@ const requireSecretsAuth = ({
|
|||||||
|
|
||||||
req.secrets = await validateClientForSecrets({
|
req.secrets = await validateClientForSecrets({
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
secretIds: [req.body.secretIds],
|
secretIds: secretIds.map((secretId: string) => new Types.ObjectId(secretId)),
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ const requireServiceAccountWorkspacePermissionAuth = ({
|
|||||||
acceptedStatuses,
|
acceptedStatuses,
|
||||||
location = 'params'
|
location = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
acceptedStatuses: string[];
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { ServiceToken, ServiceTokenData } from '../models';
|
import { ServiceToken, ServiceTokenData } from '../models';
|
||||||
|
import { validateClientForServiceTokenData } from '../helpers/serviceTokenData';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
@@ -9,30 +11,17 @@ const requireServiceTokenDataAuth = ({
|
|||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
location = 'params'
|
location = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { serviceTokenDataId } = req[location];
|
const { serviceTokenDataId } = req[location];
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenData
|
req.serviceTokenData = await validateClientForServiceTokenData({
|
||||||
.findById(req[location].serviceTokenDataId)
|
authData: req.authData,
|
||||||
.select('+encryptedKey +iv +tag').populate('user');
|
serviceTokenDataId: new Types.ObjectId(serviceTokenDataId),
|
||||||
|
acceptedRoles
|
||||||
if (!serviceTokenData) {
|
});
|
||||||
return next(AccountNotFoundError({ message: 'Failed to locate service token data' }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.user) {
|
|
||||||
// case: jwt auth
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id,
|
|
||||||
workspaceId: serviceTokenData.workspace,
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
req.serviceTokenData = serviceTokenData;
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,12 @@ const requireWorkspaceAuth = ({
|
|||||||
locationEnvironment = undefined,
|
locationEnvironment = undefined,
|
||||||
requiredPermissions = []
|
requiredPermissions = []
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
locationWorkspaceId: req;
|
locationWorkspaceId: req;
|
||||||
locationEnvironment?: req | undefined;
|
locationEnvironment?: req | undefined;
|
||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|
||||||
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||||
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
||||||
|
|
||||||
@@ -34,6 +33,7 @@ const requireWorkspaceAuth = ({
|
|||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Schema, model, Types } from "mongoose";
|
import { Schema, model, Types, Document } from "mongoose";
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
export interface IIntegrationAuth {
|
export interface IIntegrationAuth extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager';
|
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types, Document } from 'mongoose';
|
||||||
import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from '../variables';
|
import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from '../variables';
|
||||||
|
|
||||||
export interface IMembershipOrg {
|
export interface IMembershipOrg extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
inviteEmail: string;
|
inviteEmail: string;
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import {
|
|||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_SERVICE_TOKEN
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { CreateSecretRequestBody, ModifySecretRequestBody } from '../../types/secret';
|
import { CreateSecretRequestBody, ModifySecretRequestBody } from '../../types/secret';
|
||||||
import { secretController } from '../../controllers/v2';
|
import { secretController } from '../../controllers/v2';
|
||||||
@@ -75,7 +77,8 @@ router.get(
|
|||||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_TOKEN]
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
secretController.getSecret
|
secretController.getSecret
|
||||||
@@ -103,7 +106,8 @@ router.delete(
|
|||||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
param('secretId').isMongoId(),
|
param('secretId').isMongoId(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
@@ -47,7 +48,7 @@ router.post(
|
|||||||
if (secretIds.length > 0) {
|
if (secretIds.length > 0) {
|
||||||
req.secrets = await validateClientForSecrets({
|
req.secrets = await validateClientForSecrets({
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
secretIds,
|
secretIds: secretIds.map((secretId: string) => new Types.ObjectId(secretId)),
|
||||||
requiredPermissions: []
|
requiredPermissions: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ router.post(
|
|||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED],
|
acceptedStatuses: [ACCEPTED],
|
||||||
location: 'body'
|
locationOrganizationId: 'body'
|
||||||
}),
|
}),
|
||||||
serviceAccountsController.createServiceAccount
|
serviceAccountsController.createServiceAccount
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ router.patch( // TODO - rewire dashboard to this route
|
|||||||
locationWorkspaceId: 'params'
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
requireMembershipAuth({
|
requireMembershipAuth({
|
||||||
acceptedRoles: [ADMIN]
|
acceptedRoles: [ADMIN],
|
||||||
|
locationMembershipId: 'params'
|
||||||
}),
|
}),
|
||||||
workspaceController.updateWorkspaceMembership
|
workspaceController.updateWorkspaceMembership
|
||||||
);
|
);
|
||||||
@@ -124,7 +125,8 @@ router.delete( // TODO - rewire dashboard to this route
|
|||||||
locationWorkspaceId: 'params'
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
requireMembershipAuth({
|
requireMembershipAuth({
|
||||||
acceptedRoles: [ADMIN]
|
acceptedRoles: [ADMIN],
|
||||||
|
locationMembershipId: 'params'
|
||||||
}),
|
}),
|
||||||
workspaceController.deleteWorkspaceMembership
|
workspaceController.deleteWorkspaceMembership
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
handleOAuthExchangeHelper,
|
handleOAuthExchangeHelper,
|
||||||
syncIntegrationsHelper,
|
syncIntegrationsHelper,
|
||||||
@@ -67,7 +68,7 @@ class IntegrationService {
|
|||||||
* @param {String} obj.integrationAuthId - id of integration auth
|
* @param {String} obj.integrationAuthId - id of integration auth
|
||||||
* @param {String} refreshToken - decrypted refresh token
|
* @param {String} refreshToken - decrypted refresh token
|
||||||
*/
|
*/
|
||||||
static async getIntegrationAuthRefresh({ integrationAuthId }: { integrationAuthId: string}) {
|
static async getIntegrationAuthRefresh({ integrationAuthId }: { integrationAuthId: Types.ObjectId}) {
|
||||||
return await getIntegrationAuthRefreshHelper({
|
return await getIntegrationAuthRefreshHelper({
|
||||||
integrationAuthId
|
integrationAuthId
|
||||||
});
|
});
|
||||||
@@ -80,7 +81,7 @@ class IntegrationService {
|
|||||||
* @param {String} obj.integrationAuthId - id of integration auth
|
* @param {String} obj.integrationAuthId - id of integration auth
|
||||||
* @param {String} accessToken - decrypted access token
|
* @param {String} accessToken - decrypted access token
|
||||||
*/
|
*/
|
||||||
static async getIntegrationAuthAccess({ integrationAuthId }: { integrationAuthId: string}) {
|
static async getIntegrationAuthAccess({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) {
|
||||||
return await getIntegrationAuthAccessHelper({
|
return await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId
|
integrationAuthId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -73,6 +73,16 @@ export const ValidationError = (error?: Partial<RequestErrorContext>) => new Req
|
|||||||
stack: error?.stack
|
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]<-----
|
//* ----->[INTEGRATION ERRORS]<-----
|
||||||
export const IntegrationNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
export const IntegrationNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
@@ -202,4 +212,13 @@ export const ServiceAccountKeyNotFoundError = (error?: Partial<RequestErrorConte
|
|||||||
stack: error?.stack
|
stack: error?.stack
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const BotNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
|
statusCode: error?.statusCode ?? 404,
|
||||||
|
type: error?.type ?? 'bot_not_found_error',
|
||||||
|
message: error?.message ?? 'The requested bot was not found',
|
||||||
|
context: error?.context,
|
||||||
|
stack: error?.stack
|
||||||
|
})
|
||||||
|
|
||||||
//* ----->[MISC ERRORS]<-----
|
//* ----->[MISC ERRORS]<-----
|
||||||
|
|||||||
@@ -1,25 +1,51 @@
|
|||||||
---
|
---
|
||||||
title: "Authentication"
|
title: "Authentication"
|
||||||
|
description: "How to authenticate with the Infisical Public API"
|
||||||
---
|
---
|
||||||
|
|
||||||
To authenticate requests with Infisical, you can either use an API Key or [Infisical Token](../../../getting-started/dashboard/token); certain endpoints will accept either one or both.
|
## Essentials
|
||||||
- API Key: This general-purpose authentication token provides user access to most endpoints in this reference.
|
|
||||||
- [Infisical Token](../../../getting-started/dashboard/token): This authentication token (also referred to as the service token) is scoped to a specific project and environment and used for CRUD secret operations.
|
The Public API accepts multiple modes of authentication being via API Key, Service Account credentials, or [Infisical Token](../../../getting-started/dashboard/token).
|
||||||
|
|
||||||
|
- API Key: Provides full access to all endpoints representing the user.
|
||||||
|
- [Service Account](): Provides scoped access to an organization and select projects representing a machine such as a VM or application client.
|
||||||
|
- [Infisical Token](../../../getting-started/dashboard/token): Provides short-lived, scoped CRUD access to the secrets of a specific project and environment.
|
||||||
|
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
<Accordion title="API Key">
|
<Accordion title="API Key">
|
||||||
|
The API key mode uses an API key to authenticate with the API.
|
||||||
|
|
||||||
To authenticate requests with Infisical using the API Key, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform.
|
To authenticate requests with Infisical using the API Key, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform.
|
||||||
|
|
||||||
You can obtain an API key in User Settings > API Keys
|
You can obtain an API key in User Settings > API Keys
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Service Account">
|
||||||
|
The Service Account mode uses an Access Key to authenticate with the API and a Public Key and Private Key to perform any cryptographic operations.
|
||||||
|
|
||||||
|
To authenticate requests with Infisical using the Access Key, you must include it in the `Authorization` header of HTTP requests made to the platform with the value `Bearer <access_key>`.
|
||||||
|
|
||||||
|
You can create a Service Account in Organization Settings > Service Accounts
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="Infisical Token">
|
<Accordion title="Infisical Token">
|
||||||
To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer st.<rest_of_your_infisical_token>`.
|
|
||||||
|
The Infisical Token mode uses an Infisical Token to authenticate with the API.
|
||||||
|
|
||||||
|
To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer <infisical_token>`.
|
||||||
|
|
||||||
You can obtain an Infisical Token in Project Settings > Service Tokens.
|
You can obtain an Infisical Token in Project Settings > Service Tokens.
|
||||||
|
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
</AccordionGroup>
|
</AccordionGroup>
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
Depending on your use case, it may make sense to use one or another authentication mode:
|
||||||
|
|
||||||
|
- API Key (not recommended): Use if you need full access to the Public API without needing to access any secrets endpoints (because API keys can't encrypt/decrypt secrets).
|
||||||
|
- Service Account (recommeded): Use if you need access to multiple projects and environments in an organization; service accounts can generate short-lived access tokens, making them useful for some complex setups.
|
||||||
|
- Service Token (recommeded): Use if you need short-lived, scoped CRUD access to the secrets of a specific project and environment.
|
||||||
@@ -2,11 +2,17 @@
|
|||||||
title: "Introduction"
|
title: "Introduction"
|
||||||
---
|
---
|
||||||
|
|
||||||
Infisical's REST API provides users an alternative way to programmatically access and manage
|
Infisical's Public (REST) API provides users an alternative way to programmatically access and manage
|
||||||
secrets via HTTPS requests. This can be useful for automating tasks, such as
|
secrets via HTTPS requests. This can be useful for automating tasks, such as
|
||||||
rotating credentials, or for integrating secret management into a larger system.
|
rotating credentials, or for integrating secret management into a larger system.
|
||||||
|
|
||||||
With the REST API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.
|
With the Public API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
We highly recommend using one of the available SDKs when working with the Infisical API.
|
||||||
|
|
||||||
|
If you decide to make your own requests using the API reference instead, be prepared for a steeper learning curve and more manual work.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
## Concepts
|
## Concepts
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ To add a member to your organization, scroll down to the "Organization Members"
|
|||||||
projects by default.
|
projects by default.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
|
## Service Accounts
|
||||||
|
|
||||||
|
Service accounts represent machine identities such as VMs or application clients that can authenticate with Infisical. They can be provisioned read/write permissions for project(s) and environment(s).
|
||||||
|
|
||||||
|
To add a service account to your organization, scroll down to the "Service Accounts" section and create a service account. Afterwards, you can press on the edit button beside the service account to provision it permissions.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Incident contacts
|
## Incident contacts
|
||||||
|
|
||||||
Incident contacts of an organization are alerted if anything abnormal is detected within the operations of an organization.
|
Incident contacts of an organization are alerted if anything abnormal is detected within the operations of an organization.
|
||||||
|
|||||||
@@ -25,16 +25,16 @@ In most cases, environment variables belong to specific environments: developmen
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Personal/Shared scoping
|
### Personal overrides
|
||||||
|
|
||||||
Every environment variable is classified as either personal or shared.
|
Every environment variable value can be overriden with a custom value.
|
||||||
|
|
||||||
- A personal environment variable is one created by a user of a project to be available for that user only.
|
- An overriden value can only be read and accesssed by the user that overrode the original shared value.
|
||||||
- A shared environment variable is one created by a user of a project to be available for other users of the project.
|
- A (default) shared value can be read and accesssed by other users in a project.
|
||||||
|
|
||||||
You can toggle the classification of an environment variable by pressing on its settings:
|
You can turn overrides on/off by toggling the override/branch icon:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Search
|
### Search
|
||||||
|
|
||||||
@@ -42,12 +42,6 @@ You can search for any environment variable by its key.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Sort
|
|
||||||
|
|
||||||
You can sort environment variables alphabetically by their keys.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Hide/Un-hide
|
### Hide/Un-hide
|
||||||
|
|
||||||
You can hide or un-hide the values of your environment variables. By default, the values are hidden for your privacy.
|
You can hide or un-hide the values of your environment variables. By default, the values are hidden for your privacy.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: "Introduction"
|
|||||||
description: "What is Infisical?"
|
description: "What is Infisical?"
|
||||||
---
|
---
|
||||||
|
|
||||||
Infisical is an [open-source](https://opensource.com/resources/what-open-source), [end-to-end encrypted](https://en.wikipedia.org/wiki/End-to-end_encryption) secret manager that enables teams to easily manage and sync their environment variables.
|
Infisical is an [open-source](https://opensource.com/resources/what-open-source), [end-to-end encrypted](https://en.wikipedia.org/wiki/End-to-end_encryption) secret management platform that enables teams to easily manage and sync their environment variables.
|
||||||
|
|
||||||
Start syncing environment variables with [Infisical Cloud](https://app.infisical.com) or learn how to [host Infisical](/self-hosting/overview) yourself.
|
Start syncing environment variables with [Infisical Cloud](https://app.infisical.com) or learn how to [host Infisical](/self-hosting/overview) yourself.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Quickstart"
|
title: "Quickstart"
|
||||||
description: "Start managing your developer secrets and configs with Infisical in 10 minutes."
|
description: "Start managing developer secrets and configs with Infisical in minutes."
|
||||||
---
|
---
|
||||||
|
|
||||||
These examples demonstrate how to store and fetch environment variables from [Infisical Cloud](https://app.infisical.com) into your application.
|
These examples demonstrate how to store and fetch environment variables from [Infisical Cloud](https://app.infisical.com) into your application.
|
||||||
@@ -9,7 +9,7 @@ These examples demonstrate how to store and fetch environment variables from [In
|
|||||||
|
|
||||||
1. Login or create an account at `app.infisical.com`.
|
1. Login or create an account at `app.infisical.com`.
|
||||||
2. Create a new project.
|
2. Create a new project.
|
||||||
3. Populate your environment variables as in the image below.
|
3. Keep the default environment variables or populate them as in the image below.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 744 KiB |
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 717 KiB |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 538 KiB |
|
After Width: | Height: | Size: 505 KiB |
|
After Width: | Height: | Size: 564 KiB |
|
After Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 622 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 636 KiB |
|
After Width: | Height: | Size: 663 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 727 KiB |
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 669 KiB |
|
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 680 KiB |
|
Before Width: | Height: | Size: 364 KiB After Width: | Height: | Size: 1024 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 668 KiB |
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 686 KiB |
|
Before Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 668 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 668 KiB |
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 718 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 668 KiB |
|
Before Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 337 KiB After Width: | Height: | Size: 1.0 MiB |
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "CircleCI"
|
title: "CircleCI"
|
||||||
description: "How to automatically sync secrets from Infisical into your CircleCI project."
|
description: "How to sync secrets from Infisical to CircleCI"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "GitHub Actions"
|
title: "GitHub Actions"
|
||||||
description: "How to automatically sync secrets from Infisical into your GitHub Actions."
|
description: "How to sync secrets from Infisical to GitHub Actions"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "GitLab"
|
title: "GitLab"
|
||||||
description: "How to automatically sync secrets from Infisical into GitLab."
|
description: "How to sync secrets from Infisical to GitLab"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Travis CI"
|
title: "Travis CI"
|
||||||
description: "How to automatically sync secrets from Infisical to your Travis CI repository."
|
description: "How to sync secrets from Infisical to Travis CI"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "AWS Parameter Store"
|
title: "AWS Parameter Store"
|
||||||
description: "How to automatically sync secrets from Infisical to your AWS Parameter Store."
|
description: "How to sync secrets from Infisical to AWS Parameter Store"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "AWS Secret Manager"
|
title: "AWS Secret Manager"
|
||||||
description: "How to automatically sync secrets from Infisical to your AWS Secret Manager."
|
description: "How to sync secrets from Infisical to AWS Secret Manager"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Azure Key Vault"
|
title: "Azure Key Vault"
|
||||||
description: "How to automatically sync secrets from Infisical into your Azure Key Vault."
|
description: "How to sync secrets from Infisical to Azure Key Vault"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Fly.io"
|
title: "Fly.io"
|
||||||
description: "How to automatically sync secrets from Infisical into your Fly.io project."
|
description: "How to sync secrets from Infisical to Fly.io"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
@@ -11,7 +11,7 @@ Prerequisites:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Authorize Infisical for Fly.io
|
## Enter your Fly.io Access Token
|
||||||
|
|
||||||
Obtain a Fly.io access token in Access Tokens
|
Obtain a Fly.io access token in Access Tokens
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Heroku"
|
title: "Heroku"
|
||||||
description: "How to automatically sync secrets from Infisical into your Heroku project."
|
description: "How to sync secrets from Infisical to Heroku"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Netlify"
|
title: "Netlify"
|
||||||
description: "How to automatically sync secrets from Infisical into your Netlify project."
|
description: "How to sync secrets from Infisical to Netlify"
|
||||||
---
|
---
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Railway"
|
title: "Railway"
|
||||||
description: "How to automatically sync secrets from Infisical into your Railway projects and services"
|
description: "How to sync secrets from Infisical to Railway"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Render"
|
title: "Render"
|
||||||
description: "How to automatically sync secrets from Infisical into your Render project."
|
description: "How to sync secrets from Infisical to Render"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
title: "Supabase"
|
||||||
|
description: "How to sync secrets from Infisical to Supabase"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
The Supabase integration is useful if your Supabase project uses sensitive-information such as [environment variables in edge functions](https://supabase.com/docs/guides/functions/secrets).
|
||||||
|
|
||||||
|
Synced envars can be accessed in edge functions using Deno's built-in handler: `Deno.env.get(MY_SECRET_NAME)`.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Have an account and project set up at [Supabase](https://supabase.com/)
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
|
||||||
|
## Navigate to your project's integrations tab
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Enter your Supabase Access Token
|
||||||
|
|
||||||
|
Obtain a Supabase Access Token in your Supabase [Account > Access Tokens](https://app.supabase.com/account/tokens).
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Press on the Supabase tile and input your Supabase Access Token to grant Infisical access to your Supabase account.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Info>
|
||||||
|
If this is your project's first cloud integration, then you'll have to grant
|
||||||
|
Infisical access to your project's environment variables. Although this step
|
||||||
|
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
||||||
|
the cloud platform.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
## Start integration
|
||||||
|
|
||||||
|
Select which Infisical environment secrets you want to sync to which Supabase project. Lastly, press create integration to start syncing secrets to Supabase.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Vercel"
|
title: "Vercel"
|
||||||
description: "How to automatically sync secrets from Infisical into your Vercel project."
|
description: "How to sync secrets from Infisical to Vercel"
|
||||||
---
|
---
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
|
|||||||
| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
|
| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
|
||||||
| [Netlify](/integrations/cloud/netlify) | Cloud | Available |
|
| [Netlify](/integrations/cloud/netlify) | Cloud | Available |
|
||||||
| [Render](/integrations/cloud/render) | Cloud | Available |
|
| [Render](/integrations/cloud/render) | Cloud | Available |
|
||||||
| [Railway](/integrations/cloud/railway) | Cloud | Available |
|
| [Railway](/integrations/cloud/railway) | Cloud | Available |
|
||||||
| [Fly.io](/integrations/cloud/flyio) | Cloud | Available |
|
| [Fly.io](/integrations/cloud/flyio) | Cloud | Available |
|
||||||
|
| [Supabase](/integrations/cloud/supabase) | Cloud | Available |
|
||||||
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
||||||
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||||
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
|
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
|
||||||
@@ -42,7 +43,5 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
|
|||||||
| [Flask](/integrations/frameworks/flask) | Framework | Available |
|
| [Flask](/integrations/frameworks/flask) | Framework | Available |
|
||||||
| [Laravel](/integrations/frameworks/laravel) | Framework | Available |
|
| [Laravel](/integrations/frameworks/laravel) | Framework | Available |
|
||||||
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
|
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
|
||||||
| GCP | Cloud | Coming soon |
|
| GCP Secret Manager | Cloud | Coming soon |
|
||||||
| DigitalOcean | Cloud | Coming soon |
|
|
||||||
| GitHub Actions | CI/CD | Coming soon |
|
|
||||||
| Jenkins | CI/CD | Coming soon |
|
| Jenkins | CI/CD | Coming soon |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 'Kubernetes'
|
title: 'Kubernetes'
|
||||||
description: "This page explains how to use Infisical to inject secrets into Kubernetes clusters."
|
description: "How to use Infisical to inject secrets into Kubernetes clusters."
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -151,6 +151,7 @@
|
|||||||
"integrations/cloud/render",
|
"integrations/cloud/render",
|
||||||
"integrations/cloud/railway",
|
"integrations/cloud/railway",
|
||||||
"integrations/cloud/flyio",
|
"integrations/cloud/flyio",
|
||||||
|
"integrations/cloud/supabase",
|
||||||
"integrations/cloud/azure-key-vault",
|
"integrations/cloud/azure-key-vault",
|
||||||
"integrations/cicd/githubactions",
|
"integrations/cicd/githubactions",
|
||||||
"integrations/cicd/gitlab",
|
"integrations/cicd/gitlab",
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ const deleteIntegration = ({ integrationId }: Props) =>
|
|||||||
if (res && res.status === 200) {
|
if (res && res.status === 200) {
|
||||||
return (await res.json()).integration;
|
return (await res.json()).integration;
|
||||||
}
|
}
|
||||||
console.log('Failed to delete an integration');
|
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||