mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 09:28:36 +00:00
Add required endpoints/functions for service account to create service tokens
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
|||||||
import { SecretVersion } from '../../ee/models';
|
import { SecretVersion } from '../../ee/models';
|
||||||
import { BadRequestError } from '../../utils/errors';
|
import { BadRequestError } from '../../utils/errors';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
|
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new workspace environment named [environmentName] under workspace with id
|
* Create new workspace environment named [environmentName] under workspace with id
|
||||||
@@ -244,8 +244,8 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
|
|||||||
throw BadRequestError()
|
throw BadRequestError()
|
||||||
}
|
}
|
||||||
relatedWorkspace.environments.forEach(environment => {
|
relatedWorkspace.environments.forEach(environment => {
|
||||||
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_READ })
|
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_READ_SECRETS })
|
||||||
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_WRITE })
|
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_WRITE_SECRETS })
|
||||||
if (isReadBlocked && isWriteBlocked) {
|
if (isReadBlocked && isWriteBlocked) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { eventPushSecrets } from '../../events';
|
|||||||
import { EESecretService, EELogService } from '../../ee/services';
|
import { EESecretService, EELogService } from '../../ee/services';
|
||||||
import { TelemetryService } from '../../services';
|
import { TelemetryService } from '../../services';
|
||||||
import { getChannelFromUserAgent } from '../../utils/posthog';
|
import { getChannelFromUserAgent } from '../../utils/posthog';
|
||||||
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
|
import { PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||||
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
||||||
import Tag from '../../models/tag';
|
import Tag from '../../models/tag';
|
||||||
import _, { eq } from 'lodash';
|
import _, { eq } from 'lodash';
|
||||||
@@ -336,7 +336,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body;
|
const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body;
|
||||||
|
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
const hasAccess = await userHasWorkspaceAccess(req.user, new Types.ObjectId(workspaceId), environment, ABILITY_WRITE)
|
const hasAccess = await userHasWorkspaceAccess(req.user, new Types.ObjectId(workspaceId), environment, PERMISSION_WRITE_SECRETS)
|
||||||
if (!hasAccess) {
|
if (!hasAccess) {
|
||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,29 @@ import {
|
|||||||
import { BadRequestError, ServiceAccountNotFoundError } from '../../utils/errors';
|
import { BadRequestError, ServiceAccountNotFoundError } from '../../utils/errors';
|
||||||
import { getSaltRounds } from '../../config';
|
import { getSaltRounds } from '../../config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service account tied to the request (service account) client
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getCurrentServiceAccount = async (req: Request, res: Response) => {
|
||||||
|
const serviceAccount = await ServiceAccount.findById(req.serviceAccount._id);
|
||||||
|
|
||||||
|
if (!serviceAccount) {
|
||||||
|
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return service account with id [serviceAccountId]
|
* Return service account with id [serviceAccountId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const getServiceAccount = async (req: Request, res: Response) => {
|
export const getServiceAccountById = async (req: Request, res: Response) => {
|
||||||
const { serviceAccountId } = req.params;
|
const { serviceAccountId } = req.params;
|
||||||
|
|
||||||
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||||
@@ -136,23 +153,6 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
|
|||||||
return serviceAccountKey;
|
return serviceAccountKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Return organization-level permissions for service account with id [serviceAccountId]
|
|
||||||
// * @param req
|
|
||||||
// * @param res
|
|
||||||
// */
|
|
||||||
// export const getServiceAccountOrganizationPermissions = async (req: Request, res: Response) => {
|
|
||||||
// const { serviceAccountId } = req.params;
|
|
||||||
|
|
||||||
// const permissions = await ServiceAccountOrganizationPermissions.findOne({
|
|
||||||
// serviceAccount: new Types.ObjectId(serviceAccountId),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return res.status(200).send({
|
|
||||||
// permissions
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return workspace-level permission for service account with id [serviceAccountId]
|
* Return workspace-level permission for service account with id [serviceAccountId]
|
||||||
* @param req
|
* @param req
|
||||||
@@ -168,23 +168,6 @@ export const getServiceAccountWorkspacePermissions = async (req: Request, res: R
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Add organization permissions to service account with id [serviceAccountId]
|
|
||||||
// * @param req
|
|
||||||
// * @param res
|
|
||||||
// */
|
|
||||||
// export const addServiceAccountOrganizationPermission = async (req: Request, res: Response) => {
|
|
||||||
// const permissions = ServiceAccountOrganizationPermissions.findOne({
|
|
||||||
// serviceAccount: req.serviceAccount._id
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // TODO
|
|
||||||
|
|
||||||
// return res.status(200).send({
|
|
||||||
// permissions
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a workspace permission to service account with id [serviceAccountId]
|
* Add a workspace permission to service account with id [serviceAccountId]
|
||||||
* @param req
|
* @param req
|
||||||
@@ -301,3 +284,22 @@ export const deleteServiceAccount = async (req: Request, res: Response) => {
|
|||||||
serviceAccount
|
serviceAccount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service account keys for service account with id [serviceAccountId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getServiceAccountKeys = async (req: Request, res: Response) => {
|
||||||
|
const workspaceId = req.query.workspaceId as string;
|
||||||
|
|
||||||
|
const serviceAccountKeys = await ServiceAccountKey.find({
|
||||||
|
serviceAccount: req.serviceAccount._id,
|
||||||
|
...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {})
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccountKeys
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,10 +3,16 @@ import { Request, Response } from 'express';
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import {
|
import {
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
ServiceTokenData
|
ServiceTokenData
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
|
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
|
||||||
import { ABILITY_READ } from '../../variables/organization';
|
import {
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
|
} from '../../variables';
|
||||||
import { getSaltRounds } from '../../config';
|
import { getSaltRounds } from '../../config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,59 +59,57 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||||
let serviceToken, serviceTokenData;
|
let serviceTokenData;
|
||||||
|
|
||||||
try {
|
const {
|
||||||
const {
|
name,
|
||||||
name,
|
workspaceId,
|
||||||
workspaceId,
|
environment,
|
||||||
environment,
|
encryptedKey,
|
||||||
encryptedKey,
|
iv,
|
||||||
iv,
|
tag,
|
||||||
tag,
|
expiresIn,
|
||||||
expiresIn,
|
permissions
|
||||||
permissions
|
} = req.body;
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_READ)
|
const secret = crypto.randomBytes(16).toString('hex');
|
||||||
if (!hasAccess) {
|
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const secret = crypto.randomBytes(16).toString('hex');
|
const expiresAt = new Date();
|
||||||
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
|
||||||
const expiresAt = new Date();
|
let user, serviceAccount;
|
||||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
|
||||||
|
|
||||||
serviceTokenData = await new ServiceTokenData({
|
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
|
||||||
name,
|
user = req.authData.authPayload._id;
|
||||||
workspace: workspaceId,
|
|
||||||
environment,
|
|
||||||
user: req.user._id,
|
|
||||||
expiresAt,
|
|
||||||
secretHash,
|
|
||||||
encryptedKey,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
permissions
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
// return service token data without sensitive data
|
|
||||||
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
|
|
||||||
|
|
||||||
if (!serviceTokenData) throw new Error('Failed to find service token data');
|
|
||||||
|
|
||||||
serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to create service token data'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && req.authData.authPayload instanceof ServiceAccount) {
|
||||||
|
serviceAccount = req.authData.authPayload._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceTokenData = await new ServiceTokenData({
|
||||||
|
name,
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
user,
|
||||||
|
serviceAccount,
|
||||||
|
expiresAt,
|
||||||
|
secretHash,
|
||||||
|
encryptedKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
permissions
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
|
||||||
|
// return service token data without sensitive data
|
||||||
|
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
|
||||||
|
|
||||||
|
if (!serviceTokenData) throw new Error('Failed to find service token data');
|
||||||
|
|
||||||
|
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceToken,
|
serviceToken,
|
||||||
serviceTokenData
|
serviceTokenData
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { Request, Response } from "express";
|
|||||||
import { Membership, Workspace } from "../../../models";
|
import { Membership, Workspace } from "../../../models";
|
||||||
import { IMembershipPermission } from "../../../models/membership";
|
import { IMembershipPermission } from "../../../models/membership";
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
|
||||||
import { ABILITY_READ, ABILITY_WRITE, ADMIN, MEMBER } from "../../../variables/organization";
|
import { ADMIN, MEMBER } from "../../../variables/organization";
|
||||||
|
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../../variables';
|
||||||
import { Builder } from "builder-pattern"
|
import { Builder } from "builder-pattern"
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
|
|||||||
const { membershipId } = req.params;
|
const { membershipId } = req.params;
|
||||||
const { permissions } = req.body;
|
const { permissions } = req.body;
|
||||||
const sanitizedMembershipPermissions: IMembershipPermission[] = permissions.map((permission: IMembershipPermission) => {
|
const sanitizedMembershipPermissions: IMembershipPermission[] = permissions.map((permission: IMembershipPermission) => {
|
||||||
if (!permission.ability || !permission.environmentSlug || ![ABILITY_READ, ABILITY_WRITE].includes(permission.ability)) {
|
if (!permission.ability || !permission.environmentSlug || ![PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS].includes(permission.ability)) {
|
||||||
throw BadRequestError({ message: "One or more required fields are missing from the request or have incorrect type" })
|
throw BadRequestError({ message: "One or more required fields are missing from the request or have incorrect type" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { Membership } from "../../models";
|
import { Membership } from "../../models";
|
||||||
import { ABILITY_READ, ABILITY_WRITE } from "../../variables/organization";
|
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||||
|
|
||||||
export const userHasWorkspaceAccess = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string, action: any) => {
|
export const userHasWorkspaceAccess = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string, action: any) => {
|
||||||
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||||
@@ -26,8 +26,8 @@ export const userHasWriteOnlyAbility = async (userId: Types.ObjectId, workspaceI
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
|
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
|
||||||
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_WRITE });
|
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
const isReadDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_READ });
|
const isReadDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||||
|
|
||||||
// case: you have write only if read is blocked and write is not
|
// case: you have write only if read is blocked and write is not
|
||||||
if (isReadDisallowed && !isWriteDisallowed) {
|
if (isReadDisallowed && !isWriteDisallowed) {
|
||||||
@@ -44,8 +44,8 @@ export const userHasNoAbility = async (userId: Types.ObjectId, workspaceId: Type
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
|
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
|
||||||
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_WRITE });
|
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
const isReadBlocked = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_READ });
|
const isReadBlocked = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||||
|
|
||||||
if (isReadBlocked && isWriteDisallowed) {
|
if (isReadBlocked && isWriteDisallowed) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
ACTION_READ_SECRETS
|
ACTION_READ_SECRETS
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ABILITY_WRITE } from '../variables/organization';
|
|
||||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
interface V1PushSecret {
|
interface V1PushSecret {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
validateServiceAccountClientForSecrets
|
validateServiceAccountClientForSecrets
|
||||||
} from '../helpers/serviceAccount';
|
} from '../helpers/serviceAccount';
|
||||||
import { BadRequestError } from '../utils/errors';
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
@@ -60,21 +60,23 @@ const validateClientForSecrets = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
// TODO
|
|
||||||
await validateUserClientForSecrets({
|
await validateUserClientForSecrets({
|
||||||
user: authData.authPayload,
|
user: authData.authPayload,
|
||||||
secrets,
|
secrets,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
// TODO
|
|
||||||
await validateServiceAccountClientForSecrets({
|
await validateServiceAccountClientForSecrets({
|
||||||
serviceAccount: authData.authPayload,
|
serviceAccount: authData.authPayload,
|
||||||
secrets,
|
secrets,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
@@ -83,18 +85,23 @@ const validateClientForSecrets = async ({
|
|||||||
secrets,
|
secrets,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
// TODO
|
|
||||||
await validateUserClientForSecrets({
|
await validateUserClientForSecrets({
|
||||||
user: authData.authPayload,
|
user: authData.authPayload,
|
||||||
secrets,
|
secrets,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
return secrets;
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for secrets resource'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -1,17 +1,102 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
|
User,
|
||||||
|
IUser,
|
||||||
|
ServiceAccount,
|
||||||
IServiceAccount,
|
IServiceAccount,
|
||||||
|
ServiceTokenData,
|
||||||
|
IServiceTokenData,
|
||||||
ISecret,
|
ISecret,
|
||||||
ServiceAccountWorkspacePermission
|
ServiceAccountWorkspacePermission
|
||||||
} from '../models';
|
} from '../models';
|
||||||
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
ServiceAccountNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
import {
|
||||||
|
validateUserClientForServiceAccount
|
||||||
|
} from '../helpers/user';
|
||||||
|
|
||||||
|
const validateClientForServiceAccount = async ({
|
||||||
|
authData,
|
||||||
|
serviceAccountId,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
},
|
||||||
|
serviceAccountId: Types.ObjectId;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||||
|
|
||||||
|
if (!serviceAccount) {
|
||||||
|
throw ServiceAccountNotFoundError({
|
||||||
|
message: 'Failed to find service account'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForServiceAccount({
|
||||||
|
user: authData.authPayload,
|
||||||
|
serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForServiceAccount({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
targetServiceAccount: serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for service account resource'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForServiceAccount({
|
||||||
|
user: authData.authPayload,
|
||||||
|
serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for service account resource'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that serviceAccount (client) can access workspace
|
* Validate that service account (client) can access workspace
|
||||||
* with id [workspaceId] and its environment [environment] with required permissions
|
* with id [workspaceId] and its environment [environment] with required permissions
|
||||||
* [requiredPermissions]
|
* [requiredPermissions]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {Object} obj.
|
* @param {ServiceAccount} obj.serviceAccount - service account client
|
||||||
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
|
* @param {String} environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
const validateServiceAccountClientForWorkspace = async ({
|
const validateServiceAccountClientForWorkspace = async ({
|
||||||
serviceAccount,
|
serviceAccount,
|
||||||
@@ -21,18 +106,51 @@ import {
|
|||||||
}: {
|
}: {
|
||||||
serviceAccount: IServiceAccount;
|
serviceAccount: IServiceAccount;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment: string;
|
environment?: string;
|
||||||
requiredPermissions: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
// TODO
|
// TODO: add service account API support for workspace-level endpoints that are not
|
||||||
return [];
|
// tied to any specific environment
|
||||||
|
|
||||||
|
if (environment) {
|
||||||
|
const permission = await ServiceAccountWorkspacePermission.findOne({
|
||||||
|
serviceAccount,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permission) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account authorization for the given workspace environment'
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: refactor
|
||||||
|
let runningIsDisallowed = false;
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
switch (requiredPermission) {
|
||||||
|
case PERMISSION_READ_SECRETS:
|
||||||
|
if (!permission.read) runningIsDisallowed = true;
|
||||||
|
break;
|
||||||
|
case PERMISSION_WRITE_SECRETS:
|
||||||
|
if (!permission.write) runningIsDisallowed = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningIsDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that service account (client) can access secrets
|
* Validate that service account (client) can access secrets
|
||||||
* with required permissions [requiredPermissions]
|
* with required permissions [requiredPermissions]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {ServiceTokenData} obj.serviceAccount - service account client
|
* @param {ServiceAccount} obj.serviceAccount - service account client
|
||||||
* @param {Secret[]} secrets - secrets to validate against
|
* @param {Secret[]} secrets - secrets to validate against
|
||||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
@@ -49,15 +167,73 @@ import {
|
|||||||
const permissions = await ServiceAccountWorkspacePermission.find({
|
const permissions = await ServiceAccountWorkspacePermission.find({
|
||||||
serviceAccount: serviceAccount._id
|
serviceAccount: serviceAccount._id
|
||||||
});
|
});
|
||||||
|
|
||||||
const permissionsObj = _.keyBy(permissions, (p) => {
|
const permissionsObj = _.keyBy(permissions, (p) => {
|
||||||
return `${p.workspace.toString()}-${p.environment}`
|
return `${p.workspace.toString()}-${p.environment}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
secrets.forEach((secret: ISecret) => {
|
||||||
|
const permission = permissionsObj[`${secret.workspace.toString()}-${secret.environment}`];
|
||||||
|
|
||||||
|
if (!permission) throw BadRequestError({
|
||||||
|
message: 'Failed to find any permission for the secret workspace and environment'
|
||||||
|
});
|
||||||
|
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
// TODO: refactor
|
||||||
|
let runningIsDisallowed = false;
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
switch (requiredPermission) {
|
||||||
|
case PERMISSION_READ_SECRETS:
|
||||||
|
if (!permission.read) runningIsDisallowed = true;
|
||||||
|
break;
|
||||||
|
case PERMISSION_WRITE_SECRETS:
|
||||||
|
if (!permission.write) runningIsDisallowed = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningIsDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service account (client) can access target service
|
||||||
|
* account [serviceAccount] with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {SerivceAccount} obj.serviceAccount - service account client
|
||||||
|
* @param {ServiceAccount} targetServiceAccount - target service account to validate against
|
||||||
|
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateServiceAccountClientForServiceAccount = ({
|
||||||
|
serviceAccount,
|
||||||
|
targetServiceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
targetServiceAccount: IServiceAccount;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
if (!serviceAccount.organization.equals(targetServiceAccount.organization)) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account authorization for the given service account'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForServiceAccount,
|
||||||
validateServiceAccountClientForWorkspace,
|
validateServiceAccountClientForWorkspace,
|
||||||
validateServiceAccountClientForSecrets
|
validateServiceAccountClientForSecrets,
|
||||||
|
validateServiceAccountClientForServiceAccount
|
||||||
}
|
}
|
||||||
+67
-16
@@ -3,6 +3,7 @@ import { Types } from 'mongoose';
|
|||||||
import {
|
import {
|
||||||
IUser,
|
IUser,
|
||||||
ISecret,
|
ISecret,
|
||||||
|
IServiceAccount,
|
||||||
User,
|
User,
|
||||||
Membership
|
Membership
|
||||||
} from '../models';
|
} from '../models';
|
||||||
@@ -11,8 +12,12 @@ import { validateMembership } from './membership';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
ABILITY_WRITE
|
validateMembershipOrg
|
||||||
} from '../variables/organization';
|
} from '../helpers/membershipOrg';
|
||||||
|
import {
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a user under email [email]
|
* Initialize a user under email [email]
|
||||||
@@ -180,24 +185,38 @@ const validateUserClientForWorkspace = async ({
|
|||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
// org-level and workspace-level permissions? - workspace-level env scoped?
|
|
||||||
|
|
||||||
// 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
|
||||||
});
|
});
|
||||||
|
|
||||||
// validate user permission
|
// TODO: refactor
|
||||||
|
let runningIsDisallowed = false;
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
switch (requiredPermission) {
|
||||||
|
case PERMISSION_READ_SECRETS:
|
||||||
|
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||||
|
break;
|
||||||
|
case PERMISSION_WRITE_SECRETS:
|
||||||
|
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningIsDisallowed) {
|
||||||
// TODO: validate that user can perform action on environment in workspace
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return membership;
|
return membership;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user (client) can access secrets with ids [secretIds]
|
* Validate that user (client) can access secrets [secrets]
|
||||||
* with required permissions [requiredPermissions]
|
* with required permissions [requiredPermissions]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {User} obj.user - user client
|
* @param {User} obj.user - user client
|
||||||
@@ -213,7 +232,7 @@ const validateUserClientForWorkspace = async ({
|
|||||||
secrets: ISecret[];
|
secrets: ISecret[];
|
||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: consider refactor
|
// TODO: refactor
|
||||||
|
|
||||||
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');
|
||||||
@@ -221,27 +240,59 @@ const validateUserClientForWorkspace = async ({
|
|||||||
|
|
||||||
// for each secret check if the secret belongs to a workspace the user is a member of
|
// for each secret check if the secret belongs to a workspace the user is a member of
|
||||||
secrets.forEach((secret: ISecret) => {
|
secrets.forEach((secret: ISecret) => {
|
||||||
if (workspaceIdsSet.has(secret.workspace.toString())) {
|
if (!workspaceIdsSet.has(secret.workspace.toString())) {
|
||||||
|
throw BadRequestError({
|
||||||
|
message: 'Failed authorization for the secret'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||||
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
||||||
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: ABILITY_WRITE });
|
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
|
||||||
if (isDisallowed) {
|
if (isDisallowed) {
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: 'You do not have the required permissions to perform this action'
|
message: 'You do not have the required permissions to perform this action'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw BadRequestError({
|
|
||||||
message: 'You cannot edit secrets of a workspace you are not a member of'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access service account [serviceAccount]
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {ServiceAccount} obj.serviceAccount - service account to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateUserClientForServiceAccount = async ({
|
||||||
|
user,
|
||||||
|
serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
if (!serviceAccount.user.equals(user._id)) {
|
||||||
|
// case: user who created service account is not the
|
||||||
|
// same user that is on the request
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: user._id,
|
||||||
|
organizationId: serviceAccount.organization,
|
||||||
|
acceptedRoles: [],
|
||||||
|
acceptedStatuses: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
setupAccount,
|
setupAccount,
|
||||||
completeAccount,
|
completeAccount,
|
||||||
checkUserDevice,
|
checkUserDevice,
|
||||||
validateUserClientForWorkspace,
|
validateUserClientForWorkspace,
|
||||||
validateUserClientForSecrets
|
validateUserClientForSecrets,
|
||||||
|
validateUserClientForServiceAccount
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,30 +53,26 @@ const validateClientForWorkspace = async ({
|
|||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let membership;
|
|
||||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
membership = await validateUserClientForWorkspace({
|
const membership = await validateUserClientForWorkspace({
|
||||||
user: authData.authPayload,
|
user: authData.authPayload,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: validate user against [requiredPermissions]
|
return ({ membership });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
const permission = await ServiceAccountWorkspacePermission.findOne({
|
await validateServiceAccountClientForWorkspace({
|
||||||
serviceAccount: authData.authPayload._id,
|
serviceAccount: authData.authPayload,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspaceId,
|
||||||
environment
|
environment,
|
||||||
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!permission) throw UnauthorizedRequestError({
|
return {};
|
||||||
message: 'Failed service account authorization for the given workspace environment'
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: validate [requiredPermissions] against [permission]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
@@ -87,20 +83,22 @@ const validateClientForWorkspace = async ({
|
|||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: validate [requiredPermissions] against [permission]
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
membership = await validateUserClientForWorkspace({
|
const membership = await validateUserClientForWorkspace({
|
||||||
user: authData.authPayload,
|
user: authData.authPayload,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return ({ membership });
|
||||||
}
|
}
|
||||||
|
|
||||||
return ({
|
throw UnauthorizedRequestError({
|
||||||
membership
|
message: 'Failed client authorization for workspace resource'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { ServiceAccount } from '../models';
|
import { ServiceAccount } from '../models';
|
||||||
import {
|
import {
|
||||||
ServiceAccountNotFoundError
|
ServiceAccountNotFoundError
|
||||||
@@ -6,38 +7,31 @@ import {
|
|||||||
import {
|
import {
|
||||||
validateMembershipOrg
|
validateMembershipOrg
|
||||||
} from '../helpers/membershipOrg';
|
} from '../helpers/membershipOrg';
|
||||||
|
import {
|
||||||
|
validateClientForServiceAccount
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
const requireServiceAccountAuth = ({
|
const requireServiceAccountAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses,
|
acceptedStatuses,
|
||||||
location = 'params'
|
locationServiceAccountId = 'params',
|
||||||
|
requiredPermissions = []
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: string[];
|
||||||
acceptedStatuses: string[];
|
acceptedStatuses: string[];
|
||||||
location?: req;
|
locationServiceAccountId?: req;
|
||||||
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const serviceAccountId = req[location].serviceAccountId;
|
const serviceAccountId = req[locationServiceAccountId].serviceAccountId;
|
||||||
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
|
||||||
|
|
||||||
if (!serviceAccount) {
|
req.serviceAccount = await validateClientForServiceAccount({
|
||||||
return next(ServiceAccountNotFoundError({ message: 'Failed to locate Service Account' }));
|
authData: req.authData,
|
||||||
}
|
serviceAccountId: new Types.ObjectId(serviceAccountId),
|
||||||
|
requiredPermissions
|
||||||
if (serviceAccount.user.toString() !== req.user.id.toString()) {
|
});
|
||||||
// case: creator of the service account is different from
|
|
||||||
// the user on the request -> apply middleware role/status validation
|
|
||||||
await validateMembershipOrg({
|
|
||||||
userId: req.user._id,
|
|
||||||
organizationId: serviceAccount.organization,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
req.serviceAccount = serviceAccount;
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export interface IServiceTokenData extends Document {
|
|||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: string;
|
environment: string;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
|
serviceAccount: Types.ObjectId;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
secretHash: string;
|
secretHash: string;
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
@@ -31,8 +32,11 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
|||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'User',
|
ref: 'User'
|
||||||
required: true
|
},
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount'
|
||||||
},
|
},
|
||||||
expiresAt: {
|
expiresAt: {
|
||||||
type: Date
|
type: Date
|
||||||
|
|||||||
@@ -198,4 +198,3 @@ router.delete(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ import {
|
|||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { serviceAccountsController } from '../../controllers/v2';
|
import { serviceAccountsController } from '../../controllers/v2';
|
||||||
|
|
||||||
|
router.get( // TODO: check
|
||||||
|
'/me',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['serviceAccount']
|
||||||
|
}),
|
||||||
|
serviceAccountsController.getCurrentServiceAccount
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:serviceAccountId',
|
'/:serviceAccountId',
|
||||||
param('serviceAccountId').exists().isString().trim(),
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
@@ -27,7 +35,7 @@ router.get(
|
|||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
serviceAccountsController.getServiceAccount
|
serviceAccountsController.getServiceAccountById
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
@@ -76,22 +84,6 @@ router.delete(
|
|||||||
serviceAccountsController.deleteServiceAccount
|
serviceAccountsController.deleteServiceAccount
|
||||||
);
|
);
|
||||||
|
|
||||||
// router.get(
|
|
||||||
// '/:serviceAccountId/permissions/organization',
|
|
||||||
// param('serviceAccountId').exists().isString().trim(),
|
|
||||||
// query('offset').exists(),
|
|
||||||
// query('limit').exists(),
|
|
||||||
// validateRequest,
|
|
||||||
// requireAuth({
|
|
||||||
// acceptedAuthModes: ['jwt']
|
|
||||||
// }),
|
|
||||||
// requireServiceAccountAuth({
|
|
||||||
// acceptedRoles: [OWNER, ADMIN],
|
|
||||||
// acceptedStatuses: [ACCEPTED]
|
|
||||||
// }),
|
|
||||||
// serviceAccountsController.getServiceAccountOrganizationPermission
|
|
||||||
// );
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:serviceAccountId/permissions/workspace',
|
'/:serviceAccountId/permissions/workspace',
|
||||||
param('serviceAccountId').exists().isString().trim(),
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
@@ -106,20 +98,6 @@ router.get(
|
|||||||
serviceAccountsController.getServiceAccountWorkspacePermissions
|
serviceAccountsController.getServiceAccountWorkspacePermissions
|
||||||
);
|
);
|
||||||
|
|
||||||
// router.post(
|
|
||||||
// '/:serviceAccountId/permissions/organization',
|
|
||||||
// param('serviceAccountId').exists().isString().trim(),
|
|
||||||
// validateRequest,
|
|
||||||
// requireAuth({
|
|
||||||
// acceptedAuthModes: ['jwt']
|
|
||||||
// }),
|
|
||||||
// requireServiceAccountAuth({
|
|
||||||
// acceptedRoles: [OWNER, ADMIN],
|
|
||||||
// acceptedStatuses: [ACCEPTED]
|
|
||||||
// }),
|
|
||||||
// serviceAccountsController.addServiceAccountOrganizationPermission
|
|
||||||
// );
|
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:serviceAccountId/permissions/workspace',
|
'/:serviceAccountId/permissions/workspace',
|
||||||
param('serviceAccountId').exists().isString().trim(),
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
@@ -163,16 +141,17 @@ router.delete(
|
|||||||
serviceAccountsController.deleteServiceAccountWorkspacePermission
|
serviceAccountsController.deleteServiceAccountWorkspacePermission
|
||||||
);
|
);
|
||||||
|
|
||||||
// router.post(
|
router.get(
|
||||||
// '/:serviceAccountId/key',
|
'/:serviceAccountId/keys',
|
||||||
// body('workspaceId').exists().isString().trim(),
|
query('workspaceId').optional().isString(),
|
||||||
// body('encryptedKey').exists().isString().trim(),
|
requireAuth({
|
||||||
// body('nonce').exists().isString().trim(),
|
acceptedAuthModes: ['jwt', 'serviceAccount']
|
||||||
// requireServiceAccountAuth({
|
}),
|
||||||
// acceptedRoles: [OWNER, ADMIN, MEMBER],
|
requireServiceAccountAuth({
|
||||||
// acceptedStatuses: [ACCEPTED]
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
// }),
|
acceptedStatuses: [ACCEPTED]
|
||||||
// serviceAccountsController.addServiceAccountKey
|
}),
|
||||||
// );
|
serviceAccountsController.getServiceAccountKeys
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -10,6 +10,7 @@ import { param, body } from 'express-validator';
|
|||||||
import {
|
import {
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { serviceTokenDataController } from '../../controllers/v2';
|
import { serviceTokenDataController } from '../../controllers/v2';
|
||||||
|
|
||||||
@@ -24,11 +25,13 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt', 'serviceAccount']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
locationWorkspaceId: 'body'
|
locationWorkspaceId: 'body',
|
||||||
|
locationEnvironment: 'body',
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
body('name').exists().isString().trim(),
|
body('name').exists().isString().trim(),
|
||||||
body('workspaceId').exists().isString().trim(),
|
body('workspaceId').exists().isString().trim(),
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ const MEMBER = "member";
|
|||||||
// membership statuses
|
// membership statuses
|
||||||
const INVITED = "invited";
|
const INVITED = "invited";
|
||||||
|
|
||||||
// membership permissions ability
|
|
||||||
const ABILITY_READ = "read";
|
|
||||||
const ABILITY_WRITE = "write";
|
|
||||||
|
|
||||||
// -- organization
|
// -- organization
|
||||||
const ACCEPTED = "accepted";
|
const ACCEPTED = "accepted";
|
||||||
|
|
||||||
export { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED, ABILITY_READ, ABILITY_WRITE };
|
export { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED };
|
||||||
|
|||||||
+1
-3
@@ -117,7 +117,7 @@ export const SAProjectLevelPermissionsTable = ({
|
|||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
publicKey: serviceAccount.publicKey,
|
publicKey: serviceAccount.publicKey,
|
||||||
privateKey: PRIVATE_KEY
|
privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
await createServiceAccountProjectLevelPermission.mutateAsync({
|
await createServiceAccountProjectLevelPermission.mutateAsync({
|
||||||
@@ -171,8 +171,6 @@ export const SAProjectLevelPermissionsTable = ({
|
|||||||
<Th>Environment</Th>
|
<Th>Environment</Th>
|
||||||
<Th>Read</Th>
|
<Th>Read</Th>
|
||||||
<Th>Write</Th>
|
<Th>Write</Th>
|
||||||
<Th>Update</Th>
|
|
||||||
<Th>Delete</Th>
|
|
||||||
<Th aria-label="actions" />
|
<Th aria-label="actions" />
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
|
|||||||
Reference in New Issue
Block a user