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
+19 -19
View File
@@ -11,29 +11,29 @@ import { BadRequestError, MembershipNotFoundError } from "../utils/errors";
* @returns {Membership} membership - membership of user with id [userId] for workspace with id [workspaceId]
*/
export const validateMembership = async ({
userId,
workspaceId,
acceptedRoles,
userId,
workspaceId,
acceptedRoles
}: {
userId: Types.ObjectId | string;
workspaceId: Types.ObjectId | string;
acceptedRoles?: Array<"admin" | "member">;
acceptedRoles?: Array<"admin" | "member" | "custom">;
}) => {
const membership = await Membership.findOne({
user: userId,
workspace: workspaceId,
workspace: workspaceId
}).populate("workspace");
if (!membership) {
throw MembershipNotFoundError({
message: "Failed to find workspace membership",
message: "Failed to find workspace membership"
});
}
if (acceptedRoles) {
if (!acceptedRoles.includes(membership.role)) {
throw BadRequestError({
message: "Failed authorization for membership role",
message: "Failed authorization for membership role"
});
}
}
@@ -47,7 +47,7 @@ export const validateMembership = async ({
* @return {Object} membership - membership
*/
export const findMembership = async (queryObj: any) => {
const membership = await Membership.findOne(queryObj);
const membership = await Membership.findOne(queryObj);
return membership;
};
@@ -60,9 +60,9 @@ export const findMembership = async (queryObj: any) => {
* @param {String[]} obj.roles - roles of users.
*/
export const addMemberships = async ({
userIds,
workspaceId,
roles,
userIds,
workspaceId,
roles
}: {
userIds: string[];
workspaceId: string;
@@ -74,15 +74,15 @@ export const addMemberships = async ({
filter: {
user: userId,
workspace: workspaceId,
role: roles[idx],
role: roles[idx]
},
update: {
user: userId,
workspace: workspaceId,
role: roles[idx],
role: roles[idx]
},
upsert: true,
},
upsert: true
}
};
});
await Membership.bulkWrite(operations as any);
@@ -94,8 +94,8 @@ export const addMemberships = async ({
* @param {String} obj.membershipId - id of membership to delete
*/
export const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
const deletedMembership = await Membership.findOneAndDelete({
_id: membershipId,
const deletedMembership = await Membership.findOneAndDelete({
_id: membershipId
});
// delete keys associated with the membership
@@ -103,9 +103,9 @@ export const deleteMembership = async ({ membershipId }: { membershipId: string
// case: membership had a registered user
await Key.deleteMany({
receiver: deletedMembership.user,
workspace: deletedMembership.workspace,
workspace: deletedMembership.workspace
});
}
return deletedMembership;
return deletedMembership;
};
+60 -68
View File
@@ -1,14 +1,6 @@
import { Types } from "mongoose";
import {
Key,
Membership,
MembershipOrg,
Workspace,
} from "../models";
import {
MembershipOrgNotFoundError,
UnauthorizedRequestError,
} from "../utils/errors";
import { Key, Membership, MembershipOrg, Workspace } from "../models";
import { MembershipOrgNotFoundError, UnauthorizedRequestError } from "../utils/errors";
/**
* Validate that user with id [userId] is a member of organization with id [organizationId]
@@ -19,39 +11,43 @@ import {
* @param {String[]} obj.acceptedRoles
*/
export const validateMembershipOrg = async ({
userId,
organizationId,
acceptedRoles,
acceptedStatuses,
userId,
organizationId,
acceptedRoles,
acceptedStatuses
}: {
userId: Types.ObjectId;
organizationId: Types.ObjectId;
acceptedRoles?: Array<"owner" | "admin" | "member">;
acceptedStatuses?: Array<"invited" | "accepted">;
userId: Types.ObjectId;
organizationId: Types.ObjectId;
acceptedRoles?: Array<"owner" | "admin" | "member" | "custom">;
acceptedStatuses?: Array<"invited" | "accepted">;
}) => {
const membershipOrg = await MembershipOrg.findOne({
user: userId,
organization: organizationId,
});
if (!membershipOrg) {
throw MembershipOrgNotFoundError({ message: "Failed to find organization membership" });
}
if (acceptedRoles) {
if (!acceptedRoles.includes(membershipOrg.role)) {
throw UnauthorizedRequestError({ message: "Failed to validate organization membership role" });
}
}
if (acceptedStatuses) {
if (!acceptedStatuses.includes(membershipOrg.status)) {
throw UnauthorizedRequestError({ message: "Failed to validate organization membership status" });
}
}
return membershipOrg;
}
const membershipOrg = await MembershipOrg.findOne({
user: userId,
organization: organizationId
});
if (!membershipOrg) {
throw MembershipOrgNotFoundError({ message: "Failed to find organization membership" });
}
if (acceptedRoles) {
if (!acceptedRoles.includes(membershipOrg.role)) {
throw UnauthorizedRequestError({
message: "Failed to validate organization membership role"
});
}
}
if (acceptedStatuses) {
if (!acceptedStatuses.includes(membershipOrg.status)) {
throw UnauthorizedRequestError({
message: "Failed to validate organization membership status"
});
}
}
return membershipOrg;
};
/**
* Return organization membership matching criteria specified in
@@ -60,8 +56,8 @@ export const validateMembershipOrg = async ({
* @return {Object} membershipOrg - membership
*/
export const findMembershipOrg = (queryObj: any) => {
const membershipOrg = MembershipOrg.findOne(queryObj);
return membershipOrg;
const membershipOrg = MembershipOrg.findOne(queryObj);
return membershipOrg;
};
/**
@@ -73,15 +69,15 @@ export const findMembershipOrg = (queryObj: any) => {
* @param {String[]} obj.roles - roles of users.
*/
export const addMembershipsOrg = async ({
userIds,
organizationId,
roles,
statuses,
userIds,
organizationId,
roles,
statuses
}: {
userIds: string[];
organizationId: string;
roles: string[];
statuses: string[];
userIds: string[];
organizationId: string;
roles: string[];
statuses: string[];
}) => {
const operations = userIds.map((userId, idx) => {
return {
@@ -90,16 +86,16 @@ export const addMembershipsOrg = async ({
user: userId,
organization: organizationId,
role: roles[idx],
status: statuses[idx],
status: statuses[idx]
},
update: {
user: userId,
organization: organizationId,
role: roles[idx],
status: statuses[idx],
status: statuses[idx]
},
upsert: true,
},
upsert: true
}
};
});
@@ -111,13 +107,9 @@ export const addMembershipsOrg = async ({
* @param {Object} obj
* @param {String} obj.membershipOrgId - id of organization membership to delete
*/
export const deleteMembershipOrg = async ({
membershipOrgId,
}: {
membershipOrgId: string;
}) => {
export const deleteMembershipOrg = async ({ membershipOrgId }: { membershipOrgId: string }) => {
const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
_id: membershipOrgId,
_id: membershipOrgId
});
if (!deletedMembershipOrg) throw new Error("Failed to delete organization membership");
@@ -128,24 +120,24 @@ export const deleteMembershipOrg = async ({
const workspaces = (
await Workspace.find({
organization: deletedMembershipOrg.organization,
organization: deletedMembershipOrg.organization
})
).map((w) => w._id.toString());
await Membership.deleteMany({
user: deletedMembershipOrg.user,
workspace: {
$in: workspaces,
},
$in: workspaces
}
});
await Key.deleteMany({
receiver: deletedMembershipOrg.user,
workspace: {
$in: workspaces,
},
$in: workspaces
}
});
}
return deletedMembershipOrg;
};
return deletedMembershipOrg;
};
+17
View File
@@ -0,0 +1,17 @@
import type { Request } from "express";
import { AnyZodObject, ZodError, z } from "zod";
import { BadRequestError } from "../utils/errors";
export async function validateRequest<T extends AnyZodObject>(
schema: T,
req: Request
): Promise<z.infer<T>> {
try {
return schema.parseAsync(req);
} catch (error) {
if (error instanceof ZodError) {
throw BadRequestError({ message: error.message });
}
return BadRequestError({ message: JSON.stringify(error) });
}
}