feat(rbac): implemented backend base apis for rbac permission system

This commit is contained in:
Akhil Mohan
2023-09-08 21:20:29 +05:30
parent 6bca7dcc58
commit 1bf9041ac9
19 changed files with 833 additions and 327 deletions
+2
View File
@@ -16,6 +16,7 @@ import * as workspaceController from "./workspaceController";
import * as secretScanningController from "./secretScanningController";
import * as webhookController from "./webhookController";
import * as secretImportController from "./secretImportController";
import * as roleController from "./roleController";
export {
authController,
@@ -35,5 +36,6 @@ export {
workspaceController,
secretScanningController,
webhookController,
roleController,
secretImportController
};
@@ -0,0 +1,159 @@
import { Request, Response } from "express";
import { BadRequestError } from "../../utils/errors";
import Role from "../../models/role";
import {
GeneralPermissionActions,
OrgPermissionSubjects,
adminPermissions,
getUserOrgPermissions,
memberPermissions
} from "../../services/RoleService";
import { validateRequest } from "../../helpers/validation";
import {
CreateRoleSchema,
DeleteRoleSchema,
GetRoleSchema,
UpdateRoleSchema
} from "../../validation";
export const createRole = async (req: Request, res: Response) => {
const {
body: { workspaceId, name, description, slug, permissions, orgId }
} = await validateRequest(CreateRoleSchema, req);
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
if (orgPermission.cannot(GeneralPermissionActions.Create, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
const existingRole = await Role.findOne({ organization: orgId, workspace: workspaceId, slug });
if (existingRole) {
throw BadRequestError({ message: "Role already exist" });
}
const role = new Role({
organization: orgId,
workspace: workspaceId,
isOrgRole,
name,
slug,
permissions,
description
});
await role.save();
res.status(200).json({
message: "Successfully created role",
data: {
role
}
});
};
export const updateRole = async (req: Request, res: Response) => {
const {
params: { id },
body: { name, description, slug, permissions, workspaceId, orgId }
} = await validateRequest(UpdateRoleSchema, req);
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
if (orgPermission.cannot(GeneralPermissionActions.Edit, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
if (slug) {
const existingRole = await Role.findOne({
organization: orgId,
slug,
isOrgRole,
workspace: workspaceId
});
if (existingRole && existingRole.id !== id) {
throw BadRequestError({ message: "Role already exist" });
}
}
const role = await Role.findByIdAndUpdate(
id,
{ name, description, slug, permissions },
{ returnDocument: "after" }
);
if (!role) {
throw BadRequestError({ message: "Role not found" });
}
res.status(200).json({
message: "Successfully updated role",
data: {
role
}
});
};
export const deleteRole = async (req: Request, res: Response) => {
const {
params: { id }
} = await validateRequest(DeleteRoleSchema, req);
const role = await Role.findById(id);
if (!role) {
throw BadRequestError({ message: "Role not found" });
}
const orgPermission = await getUserOrgPermissions(req.user.id, role.organization.toString());
if (orgPermission.cannot(GeneralPermissionActions.Delete, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
await Role.findByIdAndDelete(role.id);
res.status(200).json({
message: "Successfully deleted role",
data: {
role
}
});
};
export const getRoles = async (req: Request, res: Response) => {
const {
query: { workspaceId, orgId }
} = await validateRequest(GetRoleSchema, req);
const isOrgRole = !workspaceId;
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
if (orgPermission.cannot(GeneralPermissionActions.Read, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
const roles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
res.status(200).json({
message: "Successfully fetched role list",
data: {
roles: [
{
name: "Owner",
slug: "owner",
description: "Complete administration access over the organization.",
permissions: adminPermissions.rules
},
{
name: "Admin",
slug: "admin",
description: "Complete administration access over the organization",
permissions: adminPermissions.rules
},
{
name: "Member",
slug: "member",
description: "Non-administrative role in an organization",
permissions: memberPermissions.rules
},
...roles
]
}
});
};
@@ -1,21 +1,19 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import {
Membership,
MembershipOrg,
ServiceAccount,
Workspace,
} from "../../models";
import { Membership, MembershipOrg, ServiceAccount, Workspace } from "../../models";
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import Role from "../../models/role";
import { BadRequestError } from "../../utils/errors";
import { CUSTOM } from "../../variables";
/**
* Return memberships for organization with id [organizationId]
* @param req
* @param res
* @param req
* @param res
*/
export const getOrganizationMemberships = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = 'Return organization memberships'
#swagger.description = 'Return organization memberships'
@@ -48,24 +46,24 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
}
}
*/
const { organizationId } = req.params;
const { organizationId } = req.params;
const memberships = await MembershipOrg.find({
organization: organizationId,
}).populate("user", "+publicKey");
return res.status(200).send({
memberships,
});
}
const memberships = await MembershipOrg.find({
organization: organizationId
}).populate("user", "+publicKey");
return res.status(200).send({
memberships
});
};
/**
* Update role of membership with id [membershipId] to role [role]
* @param req
* @param res
* @param req
* @param res
*/
export const updateOrganizationMembership = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = 'Update organization membership'
#swagger.description = 'Update organization membership'
@@ -118,31 +116,46 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
}
}
*/
const { membershipId } = req.params;
const { role } = req.body;
const membership = await MembershipOrg.findByIdAndUpdate(
membershipId,
{
role,
}, {
new: true,
}
);
return res.status(200).send({
membership,
const { membershipId } = req.params;
const { role } = req.body;
const isCustomRole = !["admin", "member", "owner"].includes(role);
if (isCustomRole) {
const orgRole = await Role.findOne({ slug: role, isOrgRole: true });
if (!orgRole) throw BadRequestError({ message: "Role not found" });
const membership = await MembershipOrg.findByIdAndUpdate(membershipId, {
role: CUSTOM,
customRole: orgRole
});
}
return res.status(200).send({
membership
});
}
const membership = await MembershipOrg.findByIdAndUpdate(
membershipId,
{
role
},
{
new: true
}
);
return res.status(200).send({
membership
});
};
/**
* Delete organization membership with id [membershipId]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const deleteOrganizationMembership = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = 'Delete organization membership'
#swagger.description = 'Delete organization membership'
@@ -178,30 +191,30 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
}
}
*/
const { membershipId } = req.params;
// delete organization membership
const membership = await deleteMembershipOrg({
membershipOrgId: membershipId,
});
const { membershipId } = req.params;
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString(),
});
// delete organization membership
const membership = await deleteMembershipOrg({
membershipOrgId: membershipId
});
return res.status(200).send({
membership,
});
}
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString()
});
return res.status(200).send({
membership
});
};
/**
* Return workspaces for organization with id [organizationId] that user has
* access to
* @param req
* @param res
* @param req
* @param res
*/
export const getOrganizationWorkspaces = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = 'Return projects in organization that user is part of'
#swagger.description = 'Return projects in organization that user is part of'
@@ -234,45 +247,45 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
}
}
*/
const { organizationId } = req.params;
const { organizationId } = req.params;
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId,
},
"_id"
)
).map((w) => w._id.toString())
);
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
},
"_id"
)
).map((w) => w._id.toString())
);
const workspaces = (
await Membership.find({
user: req.user._id,
}).populate("workspace")
)
const workspaces = (
await Membership.find({
user: req.user._id
}).populate("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
return res.status(200).send({
workspaces,
});
}
return res.status(200).send({
workspaces
});
};
/**
* Return service accounts for organization with id [organizationId]
* @param req
* @param res
* @param req
* @param res
*/
export const getOrganizationServiceAccounts = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const serviceAccounts = await ServiceAccount.find({
organization: new Types.ObjectId(organizationId),
});
return res.status(200).send({
serviceAccounts,
});
}
const { organizationId } = req.params;
const serviceAccounts = await ServiceAccount.find({
organization: new Types.ObjectId(organizationId)
});
return res.status(200).send({
serviceAccounts
});
};