mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 13:27:22 +00:00
Service account checkpoint
This commit is contained in:
@@ -6,8 +6,17 @@ import {
|
|||||||
ServiceAccountPermission
|
ServiceAccountPermission
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import {
|
import {
|
||||||
CreateServiceAccountDto
|
validateCreateServiceAccountPermission
|
||||||
|
} from '../../helpers/serviceAccount';
|
||||||
|
import {
|
||||||
|
CreateServiceAccountDto,
|
||||||
|
AddServiceAccountPermissionDto
|
||||||
} from '../../interfaces/serviceAccounts/dto';
|
} from '../../interfaces/serviceAccounts/dto';
|
||||||
|
import {
|
||||||
|
PERMISSION_SA_WORKSPACE_SET,
|
||||||
|
PERMISSION_SA_SET
|
||||||
|
} from '../../variables';
|
||||||
|
import { ServiceAccountKeyNotFoundError, ValidationError } from '../../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new service account under organization with id [organizationId]
|
* Create a new service account under organization with id [organizationId]
|
||||||
@@ -70,22 +79,39 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a permission to service account with id [serviceAccountId]
|
* Add a permission to service account with id [serviceAccountId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const addServiceAccountPermission = async (req: Request, res: Response) => {
|
export const addServiceAccountPermission = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment
|
environment
|
||||||
} = req.body; // TODO: add DTO
|
}: AddServiceAccountPermissionDto = req.body;
|
||||||
|
|
||||||
// TODO: validation?
|
if (PERMISSION_SA_WORKSPACE_SET.has(name)) {
|
||||||
|
// case: permission named [name] is workspace-related
|
||||||
|
|
||||||
|
// some such permissions require workspaceId and environment to be present.
|
||||||
|
|
||||||
|
if (!workspaceId || !environment) {
|
||||||
|
throw ValidationError({
|
||||||
|
message: 'Failed validation that is workspace-related permission must specify a workspace and environment'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const serviceAccountKey = await ServiceAccountKey.findOne({
|
||||||
|
serviceAccount: req.serviceAccount._id,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!serviceAccountKey) throw ServiceAccountKeyNotFoundError({ message: 'Failed to find service account key' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const serviceAccountPermission = await new ServiceAccountPermission({
|
const serviceAccountPermission = await new ServiceAccountPermission({
|
||||||
serviceAccount: req.serviceAccount._id,
|
serviceAccount: req.serviceAccount._id,
|
||||||
name,
|
name,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: workspaceId ? new Types.ObjectId(workspaceId) : undefined,
|
||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,19 +126,15 @@ export const addServiceAccountPermission = async (req: Request, res: Response) =
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const deleteServiceAccountPermission = async (req: Request, res: Response) => {
|
export const deleteServiceAccountPermission = async (req: Request, res: Response) => {
|
||||||
const {
|
const { serviceAccountPermissionId } = req.params;
|
||||||
name,
|
|
||||||
workspaceId,
|
// user must either be an admin/owner of the organization or they must
|
||||||
environment
|
// have created the service account in the first place to be able to delete it
|
||||||
} = req.body; // TODO: DTO
|
|
||||||
|
|
||||||
// TODO: how to delete just 1 permission?
|
// TODO: how to delete just 1 permission?
|
||||||
const serviceAccountPermission = await ServiceAccountPermission.findOneAndDelete({
|
|
||||||
serviceAccount: req.serviceAccount._id,
|
|
||||||
name,
|
const serviceAccountPermission = await ServiceAccountPermission.findByIdAndDelete(serviceAccountPermissionId);
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
|
||||||
environment
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceAccountPermission
|
serviceAccountPermission
|
||||||
@@ -130,40 +152,19 @@ export const deleteServiceAccount = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const serviceAccount = await ServiceAccount.findByIdAndDelete(serviceAccountId);
|
const serviceAccount = await ServiceAccount.findByIdAndDelete(serviceAccountId);
|
||||||
|
|
||||||
await ServiceAccountKey.deleteMany({
|
if (serviceAccount) {
|
||||||
serviceAccount: new Types.ObjectId(serviceAccountId)
|
// case: service account with id [serviceAccountId] was deleted
|
||||||
});
|
|
||||||
|
await ServiceAccountKey.deleteMany({
|
||||||
|
serviceAccount: serviceAccount?._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await ServiceAccountPermission.deleteMany({
|
||||||
|
serviceAccount: new Types.ObjectId(serviceAccountId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await ServiceAccountPermission.deleteMany({
|
|
||||||
serviceAccount: new Types.ObjectId(serviceAccountId)
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceAccount
|
serviceAccount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Add a service account key to service account with id [serviceAccountId]
|
|
||||||
// * for workspace with id [workspaceId]
|
|
||||||
// * @param req
|
|
||||||
// * @param res
|
|
||||||
// * @returns
|
|
||||||
// */
|
|
||||||
// export const addServiceAccountKey = async (req: Request, res: Response) => {
|
|
||||||
// const {
|
|
||||||
// workspaceId,
|
|
||||||
// encryptedKey,
|
|
||||||
// nonce
|
|
||||||
// } = req.body;
|
|
||||||
|
|
||||||
// const serviceAccountKey = await new ServiceAccountKey({
|
|
||||||
// encryptedKey,
|
|
||||||
// nonce,
|
|
||||||
// sender: req.user._id,
|
|
||||||
// serviceAccount: req.serviceAccount._d,
|
|
||||||
// workspace: new Types.ObjectId(workspaceId)
|
|
||||||
// }).save();
|
|
||||||
|
|
||||||
// return serviceAccountKey;
|
|
||||||
// }
|
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Membership, Key } from '../models';
|
import { Membership, Key } from '../models';
|
||||||
|
import {
|
||||||
|
MembershipNotFoundError,
|
||||||
|
BadRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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]
|
||||||
@@ -19,23 +23,17 @@ const validateMembership = async ({
|
|||||||
acceptedRoles: string[];
|
acceptedRoles: string[];
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let membership;
|
const membership = await Membership.findOne({
|
||||||
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
|
user: userId,
|
||||||
try {
|
workspace: workspaceId
|
||||||
membership = await Membership.findOne({
|
}).populate("workspace");
|
||||||
user: userId,
|
|
||||||
workspace: workspaceId
|
if (!membership) {
|
||||||
}).populate("workspace");
|
throw MembershipNotFoundError({ message: 'Failed to find workspace membership' });
|
||||||
|
}
|
||||||
if (!membership) throw new Error('Failed to find membership');
|
|
||||||
|
if (!acceptedRoles.includes(membership.role)) {
|
||||||
if (!acceptedRoles.includes(membership.role)) {
|
throw BadRequestError({ message: 'Failed to validate workspace membership role' });
|
||||||
throw new Error('Failed to validate membership role');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to validate membership');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return membership;
|
return membership;
|
||||||
|
|||||||
@@ -1,40 +1,48 @@
|
|||||||
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 } from '../models';
|
||||||
|
import {
|
||||||
|
MembershipOrgNotFoundError,
|
||||||
|
BadRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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]
|
||||||
* and has at least one of the roles in [acceptedRoles]
|
* and has at least one of the roles in [acceptedRoles]
|
||||||
*
|
* @param {Object} obj
|
||||||
|
* @param {Types.ObjectId} obj.userId
|
||||||
|
* @param {Types.ObjectId} obj.organizationId
|
||||||
|
* @param {String[]} obj.acceptedRoles
|
||||||
*/
|
*/
|
||||||
const validateMembership = async ({
|
const validateMembershipOrg = async ({
|
||||||
userId,
|
userId,
|
||||||
organizationId,
|
organizationId,
|
||||||
acceptedRoles
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
}: {
|
}: {
|
||||||
userId: string;
|
userId: Types.ObjectId;
|
||||||
organizationId: string;
|
organizationId: Types.ObjectId;
|
||||||
acceptedRoles: string[];
|
acceptedRoles: string[];
|
||||||
|
acceptedStatuses: string[];
|
||||||
}) => {
|
}) => {
|
||||||
let membership;
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
try {
|
user: userId,
|
||||||
membership = await MembershipOrg.findOne({
|
organization: organizationId
|
||||||
user: new Types.ObjectId(userId),
|
});
|
||||||
organization: new Types.ObjectId(organizationId)
|
|
||||||
});
|
if (!membershipOrg) {
|
||||||
|
throw MembershipOrgNotFoundError({ message: 'Failed to find organization membership' });
|
||||||
if (!membership) throw new Error('Failed to find organization membership');
|
|
||||||
|
|
||||||
if (!acceptedRoles.includes(membership.role)) {
|
|
||||||
throw new Error('Failed to validate organization membership role');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to validate organization membership');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return membership;
|
if (!acceptedRoles.includes(membershipOrg.role)) {
|
||||||
|
throw BadRequestError({ message: 'Failed to validate organization membership role' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
||||||
|
throw BadRequestError({ message: 'Failed to validate organization membership status' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,7 +164,7 @@ const deleteMembershipOrg = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateMembership,
|
validateMembershipOrg,
|
||||||
findMembershipOrg,
|
findMembershipOrg,
|
||||||
addMembershipsOrg,
|
addMembershipsOrg,
|
||||||
deleteMembershipOrg
|
deleteMembershipOrg
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
Workspace,
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceAccountKey
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
WorkspaceNotFoundError,
|
||||||
|
ServiceAccountNotFoundError,
|
||||||
|
ServiceAccountKeyNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
PERMISSION_SA_WORKSPACE_READ,
|
||||||
|
PERMISSION_SA_WORKSPACE_WRITE,
|
||||||
|
PERMISSION_SA_SET
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user with id [userId] can provision the permission
|
||||||
|
* named [name] for a service account with id [serviceAccountId] and
|
||||||
|
* optionally workspace with id [workspaceId] and environment [environment]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.name - name of permission to create
|
||||||
|
* @param {Types.ObjectId} userId - id of user creating the permission
|
||||||
|
* @param {Types.ObjectId} serviceAccountId - id of service account that permission will be bound to
|
||||||
|
* @param {Types.ObjectId} workspaceId - id of workspace that permission concerns
|
||||||
|
* @param {Types.ObjectId} workspaceId - id of service account that permission will be bound to
|
||||||
|
*/
|
||||||
|
const validateCreateServiceAccountPermission = async ({
|
||||||
|
name,
|
||||||
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
userId: Types.ObjectId;
|
||||||
|
serviceAccountId: Types.ObjectId,
|
||||||
|
workspaceId?: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
// TODO: as we upgrade user permissions to be more global, then we should take into account
|
||||||
|
// the user's permissions as it concerns to being able to interact with service accounts
|
||||||
|
|
||||||
|
if (!PERMISSION_SA_SET.has(name)) throw new Error(`${name} is not a valid permission name`);
|
||||||
|
|
||||||
|
if ([
|
||||||
|
PERMISSION_SA_WORKSPACE_READ,
|
||||||
|
PERMISSION_SA_WORKSPACE_WRITE
|
||||||
|
].includes(name)) {
|
||||||
|
if (workspaceId && environment) {
|
||||||
|
// case: either workspace id [workspaceId] or environment name [environment] is being passed in
|
||||||
|
// (i.e. validating a service account permission concerning a workspace and/or environment)
|
||||||
|
const workspace = await Workspace.findById(workspaceId);
|
||||||
|
|
||||||
|
if (!workspace) {
|
||||||
|
// case: workspace does not exist
|
||||||
|
throw WorkspaceNotFoundError({ message: 'Failed to locate workspace' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!workspace.environments.some((env) => env.slug === environment)) {
|
||||||
|
// case: environment name [environment] is not a valid environment slug in workspace
|
||||||
|
throw Error('Failed to locate environment in workspace');
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||||
|
if (!serviceAccount) {
|
||||||
|
// case: service account does not exist
|
||||||
|
throw ServiceAccountNotFoundError({ message: 'Failed to locate service account' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccountKey = await ServiceAccountKey.findOne({
|
||||||
|
serviceAccount: serviceAccount._id,
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!serviceAccountKey) {
|
||||||
|
// case: service account key does not exist
|
||||||
|
throw ServiceAccountKeyNotFoundError({ message: 'Failed to locate service account key' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Failed to validate workspace and environment for workspace-related permission');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateDeleteServiceAccountPermission = async ({
|
||||||
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
name,
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
}: {
|
||||||
|
userId: Types.ObjectId;
|
||||||
|
serviceAccountId: Types.ObjectId;
|
||||||
|
name: string;
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
}) => {
|
||||||
|
// does the user have the authority to delete the permission?
|
||||||
|
// does the service account permission exist?
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
validateCreateServiceAccountPermission
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
interface AddServiceAccountPermissionDto {
|
||||||
|
name: string;
|
||||||
|
workspaceId?: string;
|
||||||
|
environment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddServiceAccountPermissionDto;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import CreateServiceAccountDto from './CreateServiceAccountDto';
|
import CreateServiceAccountDto from './CreateServiceAccountDto';
|
||||||
|
import AddServiceAccountPermissionDto from './AddServiceAccountPermissionDto';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CreateServiceAccountDto
|
CreateServiceAccountDto,
|
||||||
|
AddServiceAccountPermissionDto
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ import { UnauthorizedRequestError } from '../utils/errors';
|
|||||||
import {
|
import {
|
||||||
MembershipOrg
|
MembershipOrg
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { validateMembership } from '../helpers/membershipOrg';
|
import { validateMembershipOrg } from '../helpers/membershipOrg';
|
||||||
|
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
@@ -17,9 +17,11 @@ type req = 'params' | 'body' | 'query';
|
|||||||
*/
|
*/
|
||||||
const requireMembershipOrgAuth = ({
|
const requireMembershipOrgAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
|
acceptedStatuses,
|
||||||
location = 'params'
|
location = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: string[];
|
||||||
|
acceptedStatuses: string[];
|
||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -29,14 +31,13 @@ const requireMembershipOrgAuth = ({
|
|||||||
|
|
||||||
if (!membershipOrg) throw new Error('Failed to find target organization membership');
|
if (!membershipOrg) throw new Error('Failed to find target organization membership');
|
||||||
|
|
||||||
const targetMembership = await validateMembership({
|
req.targetMembership = await validateMembershipOrg({
|
||||||
userId: req.user._id.toString(),
|
userId: req.user._id,
|
||||||
organizationId: membershipOrg.organization.toString(),
|
organizationId: membershipOrg.organization,
|
||||||
acceptedRoles
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
});
|
});
|
||||||
|
|
||||||
req.targetMembership = targetMembership;
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return next(UnauthorizedRequestError({
|
return next(UnauthorizedRequestError({
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
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';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
@@ -9,7 +11,7 @@ type req = 'params' | 'body' | 'query';
|
|||||||
* on request params.
|
* on request params.
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String[]} obj.acceptedRoles - accepted organization roles
|
* @param {String[]} obj.acceptedRoles - accepted organization roles
|
||||||
* @param {String[]} obj.acceptedStatuses - accepted organization statuses
|
* @param {String[]} obj.accepteStatuses - accepted organization statuses
|
||||||
*/
|
*/
|
||||||
const requireOrganizationAuth = ({
|
const requireOrganizationAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
@@ -21,30 +23,13 @@ const requireOrganizationAuth = ({
|
|||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// organization authorization middleware
|
|
||||||
|
|
||||||
const { organizationId } = req[location];
|
const { organizationId } = req[location];
|
||||||
|
req.membershipOrg = await validateMembershipOrg({
|
||||||
// validate organization membership
|
userId: req.user._id,
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
organizationId: new Types.ObjectId(organizationId),
|
||||||
user: req.user._id,
|
acceptedRoles,
|
||||||
organization: organizationId
|
acceptedStatuses
|
||||||
}).populate<{ organization: IOrganization }>('organization');
|
});
|
||||||
|
|
||||||
|
|
||||||
if (!membershipOrg) {
|
|
||||||
return next(UnauthorizedRequestError({message: "You're not a member of this Organization."}))
|
|
||||||
}
|
|
||||||
//TODO is this important to validate? I mean is it possible to save wrong role to database or get wrong role from databse? - Zamion101
|
|
||||||
if (!acceptedRoles.includes(membershipOrg.role)) {
|
|
||||||
return next(ValidationError({message: 'Failed to validate Organization Membership Role'}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
|
||||||
return next(ValidationError({message: 'Failed to validate Organization Membership Status'}))
|
|
||||||
}
|
|
||||||
|
|
||||||
req.membershipOrg = membershipOrg;
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { ServiceAccount } from '../models';
|
import { ServiceAccount } from '../models';
|
||||||
import {
|
import {
|
||||||
AccountNotFoundError,
|
ServiceAccountNotFoundError
|
||||||
UnauthorizedRequestError
|
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateMembershipOrg
|
||||||
|
} from '../helpers/membershipOrg';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
@@ -20,14 +22,19 @@ const requireServiceAccountAuth = ({
|
|||||||
const serviceAccountId = req[location].serviceAccountId;
|
const serviceAccountId = req[location].serviceAccountId;
|
||||||
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||||
|
|
||||||
// TODO: acceptedRoles and acceptedStatuses
|
|
||||||
|
|
||||||
if (!serviceAccount) {
|
if (!serviceAccount) {
|
||||||
return next(AccountNotFoundError({ message: 'Failed to locate Service Account' }));
|
return next(ServiceAccountNotFoundError({ message: 'Failed to locate Service Account' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceAccount.user.toString() !== req.user.id.toString()) {
|
if (serviceAccount.user.toString() !== req.user.id.toString()) {
|
||||||
return next(UnauthorizedRequestError({ message: 'Failed to authenticate the Service Account' }));
|
// 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;
|
req.serviceAccount = serviceAccount;
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ const serviceAccountPermissionSchema = new Schema<IServiceAccountPermission>(
|
|||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'Workspace',
|
ref: 'Workspace',
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
type: 'String'
|
type: 'String',
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ router.patch(
|
|||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
requireMembershipOrgAuth({
|
requireMembershipOrgAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN]
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.updateOrganizationMembership
|
organizationsController.updateOrganizationMembership
|
||||||
);
|
);
|
||||||
@@ -58,7 +59,8 @@ router.delete(
|
|||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
requireMembershipOrgAuth({
|
requireMembershipOrgAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN]
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.deleteOrganizationMembership
|
organizationsController.deleteOrganizationMembership
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,14 +2,17 @@ import express from 'express';
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import {
|
||||||
requireOrganizationAuth,
|
requireOrganizationAuth,
|
||||||
requireServiceAccountAuth
|
requireWorkspaceAuth,
|
||||||
|
requireServiceAccountAuth,
|
||||||
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import {
|
import {
|
||||||
OWNER,
|
OWNER,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
ACCEPTED
|
ACCEPTED,
|
||||||
|
PERMISSION_SA_SET
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { serviceAccountsController } from '../../controllers/v2';
|
import { serviceAccountsController } from '../../controllers/v2';
|
||||||
|
|
||||||
@@ -27,6 +30,19 @@ router.post(
|
|||||||
serviceAccountsController.createServiceAccount
|
serviceAccountsController.createServiceAccount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/serviceAccountId/:serviceAccountId/permissions',
|
||||||
|
body('name').exists().isString().trim().custom((value) => PERMISSION_SA_SET.has(value)),
|
||||||
|
body('workspaceId').optional().isMongoId(),
|
||||||
|
body('environment').optional(),
|
||||||
|
validateRequest,
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
serviceAccountsController.addServiceAccountPermission
|
||||||
|
);
|
||||||
|
|
||||||
// router.post(
|
// router.post(
|
||||||
// '/:serviceAccountId/key',
|
// '/:serviceAccountId/key',
|
||||||
// body('workspaceId').exists().isString().trim(),
|
// body('workspaceId').exists().isString().trim(),
|
||||||
@@ -42,7 +58,7 @@ router.post(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:serviceAccountId/key/:serviceAccountKeyId',
|
'/:serviceAccountId/key/:serviceAccountKeyId',
|
||||||
requireServiceAccountAuth({
|
requireServiceAccountAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
@@ -50,4 +66,17 @@ router.delete(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: create service account permission
|
||||||
|
// router.post(
|
||||||
|
|
||||||
|
// );
|
||||||
|
|
||||||
|
// TODO: delete service account permission
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:serviceAccountId/service-account-permission/:serviceAccountPermissionId',
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -93,6 +93,16 @@ export const WorkspaceNotFoundError = (error?: Partial<RequestErrorContext>) =>
|
|||||||
stack: error?.stack
|
stack: error?.stack
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//* ----->[WORKSPACE MEMBERSHIP ERRORS]<-----
|
||||||
|
export const MembershipNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
|
statusCode: error?.statusCode ?? 404,
|
||||||
|
type: error?.type ?? 'workspace_membership_not_found_error',
|
||||||
|
message: error?.message ?? 'The requested membership was not found',
|
||||||
|
context: error?.context,
|
||||||
|
stack: error?.stack
|
||||||
|
});
|
||||||
|
|
||||||
//* ----->[ORGANIZATION ERRORS]<-----
|
//* ----->[ORGANIZATION ERRORS]<-----
|
||||||
export const OrganizationNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
export const OrganizationNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
@@ -103,6 +113,16 @@ export const OrganizationNotFoundError = (error?: Partial<RequestErrorContext>)
|
|||||||
stack: error?.stack
|
stack: error?.stack
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//* ----->[MEMBERSHIP ORGANIZATION ERRORS]<-----
|
||||||
|
export const MembershipOrgNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
|
statusCode: error?.statusCode ?? 404,
|
||||||
|
type: error?.type ?? 'organization_membership_not_found_error',
|
||||||
|
message: error?.message ?? 'The requested organization membership was not found',
|
||||||
|
context: error?.context,
|
||||||
|
stack: error?.stack
|
||||||
|
});
|
||||||
|
|
||||||
//* ----->[ACCOUNT ERRORS]<-----
|
//* ----->[ACCOUNT ERRORS]<-----
|
||||||
export const AccountNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
export const AccountNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
@@ -157,10 +177,29 @@ export const ServiceTokenDataNotFoundError = (error?: Partial<RequestErrorContex
|
|||||||
export const APIKeyDataNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
export const APIKeyDataNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
statusCode: error?.statusCode ?? 404,
|
statusCode: error?.statusCode ?? 404,
|
||||||
type: error?.type ?? 'service_token_data_not_found_error',
|
type: error?.type ?? 'api_key_data_not_found_error',
|
||||||
message: error?.message ?? 'The requested service token data was not found',
|
message: error?.message ?? 'The requested service token data was not found',
|
||||||
context: error?.context,
|
context: error?.context,
|
||||||
stack: error?.stack
|
stack: error?.stack
|
||||||
|
});
|
||||||
|
|
||||||
|
//* ----->[SERVICE_ACCOUNT ERRORS]<-----
|
||||||
|
export const ServiceAccountNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
|
statusCode: error?.statusCode ?? 404,
|
||||||
|
type: error?.type ?? 'service_account_not_found_error',
|
||||||
|
message: error?.message ?? 'The requested service account was not found',
|
||||||
|
context: error?.context,
|
||||||
|
stack: error?.stack
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ServiceAccountKeyNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
|
statusCode: error?.statusCode ?? 404,
|
||||||
|
type: error?.type ?? 'service_account_key_not_found_error',
|
||||||
|
message: error?.message ?? 'The requested service account key was not found',
|
||||||
|
context: error?.context,
|
||||||
|
stack: error?.stack
|
||||||
})
|
})
|
||||||
|
|
||||||
//* ----->[MISC ERRORS]<-----
|
//* ----->[MISC ERRORS]<-----
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ import {
|
|||||||
TOKEN_EMAIL_ORG_INVITATION,
|
TOKEN_EMAIL_ORG_INVITATION,
|
||||||
TOKEN_EMAIL_PASSWORD_RESET
|
TOKEN_EMAIL_PASSWORD_RESET
|
||||||
} from './token';
|
} from './token';
|
||||||
|
import {
|
||||||
|
PERMISSION_SA_WORKSPACE_READ,
|
||||||
|
PERMISSION_SA_WORKSPACE_WRITE,
|
||||||
|
PERMISSION_SA_WORKSPACE_SET,
|
||||||
|
PERMISSION_SA_SET
|
||||||
|
} from './permissions';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
OWNER,
|
OWNER,
|
||||||
@@ -124,5 +130,9 @@ export {
|
|||||||
TOKEN_EMAIL_CONFIRMATION,
|
TOKEN_EMAIL_CONFIRMATION,
|
||||||
TOKEN_EMAIL_MFA,
|
TOKEN_EMAIL_MFA,
|
||||||
TOKEN_EMAIL_ORG_INVITATION,
|
TOKEN_EMAIL_ORG_INVITATION,
|
||||||
TOKEN_EMAIL_PASSWORD_RESET
|
TOKEN_EMAIL_PASSWORD_RESET,
|
||||||
|
PERMISSION_SA_WORKSPACE_READ,
|
||||||
|
PERMISSION_SA_WORKSPACE_WRITE,
|
||||||
|
PERMISSION_SA_WORKSPACE_SET,
|
||||||
|
PERMISSION_SA_SET
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
const PERMISSION_SA_WORKSPACE_READ = 'read';
|
||||||
|
const PERMISSION_SA_WORKSPACE_WRITE = 'write';
|
||||||
|
|
||||||
|
const PERMISSION_SA_WORKSPACE_SET = new Set([
|
||||||
|
PERMISSION_SA_WORKSPACE_READ,
|
||||||
|
PERMISSION_SA_WORKSPACE_WRITE
|
||||||
|
]);
|
||||||
|
|
||||||
|
const PERMISSION_SA_SET = new Set([
|
||||||
|
PERMISSION_SA_WORKSPACE_READ,
|
||||||
|
PERMISSION_SA_WORKSPACE_WRITE
|
||||||
|
]);
|
||||||
|
|
||||||
|
export {
|
||||||
|
PERMISSION_SA_WORKSPACE_READ,
|
||||||
|
PERMISSION_SA_WORKSPACE_WRITE,
|
||||||
|
PERMISSION_SA_WORKSPACE_SET,
|
||||||
|
PERMISSION_SA_SET
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user