mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +00:00
feat(rbac): implemented project based permission loading and role management
This commit is contained in:
@@ -14,9 +14,15 @@ import {
|
||||
DeleteRoleSchema,
|
||||
GetRoleSchema,
|
||||
GetUserPermission,
|
||||
GetUserProjectPermission,
|
||||
UpdateRoleSchema
|
||||
} from "../../validation";
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
import {
|
||||
adminProjectPermissions,
|
||||
getUserProjectPermissions,
|
||||
viewerProjectPermission
|
||||
} from "../../services/ProjectRoleService";
|
||||
|
||||
export const createRole = async (req: Request, res: Response) => {
|
||||
const {
|
||||
@@ -130,35 +136,45 @@ export const getRoles = async (req: Request, res: Response) => {
|
||||
throw BadRequestError({ message: "User doesn't have the permission." });
|
||||
}
|
||||
|
||||
const roles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
|
||||
const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
|
||||
const roles = [
|
||||
{
|
||||
_id: "admin",
|
||||
name: "Admin",
|
||||
slug: "admin",
|
||||
description: "Complete administration access over the organization",
|
||||
permissions: isOrgRole ? adminPermissions.rules : adminProjectPermissions.rules
|
||||
},
|
||||
{
|
||||
_id: "member",
|
||||
name: "Member",
|
||||
slug: "member",
|
||||
description: "Non-administrative role in an organization",
|
||||
permissions: isOrgRole ? memberPermissions.rules : adminProjectPermissions.rules
|
||||
},
|
||||
{
|
||||
_id: "viewer",
|
||||
name: "Viewer",
|
||||
slug: "viewer",
|
||||
description: "Non-administrative role in an organization",
|
||||
permissions: isOrgRole ? viewerProjectPermission.rules : viewerProjectPermission.rules
|
||||
},
|
||||
...customRoles
|
||||
];
|
||||
if (isOrgRole) {
|
||||
roles.unshift({
|
||||
_id: "owner",
|
||||
name: "Owner",
|
||||
slug: "owner",
|
||||
description: "Complete administration access over the organization.",
|
||||
permissions: adminPermissions.rules
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: "Successfully fetched role list",
|
||||
data: {
|
||||
roles: [
|
||||
{
|
||||
_id: "owner",
|
||||
name: "Owner",
|
||||
slug: "owner",
|
||||
description: "Complete administration access over the organization.",
|
||||
permissions: adminPermissions.rules
|
||||
},
|
||||
{
|
||||
_id: "admin",
|
||||
name: "Admin",
|
||||
slug: "admin",
|
||||
description: "Complete administration access over the organization",
|
||||
permissions: adminPermissions.rules
|
||||
},
|
||||
{
|
||||
_id: "member",
|
||||
name: "Member",
|
||||
slug: "member",
|
||||
description: "Non-administrative role in an organization",
|
||||
permissions: memberPermissions.rules
|
||||
},
|
||||
...roles
|
||||
]
|
||||
roles
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -175,3 +191,16 @@ export const getUserPermissions = async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserWorkspacePermissions = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(GetUserProjectPermission, req);
|
||||
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
|
||||
|
||||
res.status(200).json({
|
||||
data: {
|
||||
permissions: packRules(permission.rules)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ export const validateMembership = async ({
|
||||
}: {
|
||||
userId: Types.ObjectId | string;
|
||||
workspaceId: Types.ObjectId | string;
|
||||
acceptedRoles?: Array<"admin" | "member" | "custom">;
|
||||
acceptedRoles?: Array<"admin" | "member" | "custom" | "viewer">;
|
||||
}) => {
|
||||
const membership = await Membership.findOne({
|
||||
user: userId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
import { ADMIN, CUSTOM, MEMBER } from "../variables";
|
||||
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables";
|
||||
|
||||
export interface IMembershipPermission {
|
||||
environmentSlug: string;
|
||||
@@ -11,7 +11,7 @@ export interface IMembership {
|
||||
user: Types.ObjectId;
|
||||
inviteEmail?: string;
|
||||
workspace: Types.ObjectId;
|
||||
role: "admin" | "member" | "custom";
|
||||
role: "admin" | "member" | "viewer" | "custom";
|
||||
customRole: Types.ObjectId;
|
||||
deniedPermissions: IMembershipPermission[];
|
||||
}
|
||||
@@ -44,7 +44,7 @@ const membershipSchema = new Schema<IMembership>(
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: [ADMIN, MEMBER, CUSTOM],
|
||||
enum: [ADMIN, MEMBER, VIEWER, CUSTOM],
|
||||
required: true
|
||||
},
|
||||
customRole: {
|
||||
|
||||
@@ -19,9 +19,15 @@ router.get("/", requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), roleControll
|
||||
|
||||
// get a user permissions in an org
|
||||
router.get(
|
||||
"/:orgId/permissions",
|
||||
"/organization/:orgId/permissions",
|
||||
requireAuth({ acceptedAuthModes: [AuthMode.JWT] }),
|
||||
roleController.getUserPermissions
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/workspace/:workspaceId/permissions",
|
||||
requireAuth({ acceptedAuthModes: [AuthMode.JWT] }),
|
||||
roleController.getUserWorkspacePermissions
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@casl/ability";
|
||||
import { Membership } from "../models";
|
||||
import { IRole } from "../models/role";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||
|
||||
export enum GeneralPermissionActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete"
|
||||
}
|
||||
|
||||
export enum ProjectPermission {
|
||||
Role = "role",
|
||||
Member = "member",
|
||||
Settings = "settings",
|
||||
Integrations = "integrations",
|
||||
Webhooks = "webhooks",
|
||||
ServiceTokens = "service-tokens",
|
||||
Environments = "environments",
|
||||
Tags = "tags",
|
||||
AuditLogs = "audit-logs",
|
||||
IpAllowList = "ip-allowlist",
|
||||
Workspace = "workspace",
|
||||
Secrets = "secrets",
|
||||
SecretImports = "secret-imports",
|
||||
Folders = "folders"
|
||||
}
|
||||
|
||||
export type ProjectPermissionSet =
|
||||
| [GeneralPermissionActions, ProjectPermission.Secrets]
|
||||
| [GeneralPermissionActions, ProjectPermission.Folders]
|
||||
| [GeneralPermissionActions, ProjectPermission.SecretImports]
|
||||
| [GeneralPermissionActions, ProjectPermission.Role]
|
||||
| [GeneralPermissionActions, ProjectPermission.Tags]
|
||||
| [GeneralPermissionActions, ProjectPermission.Member]
|
||||
| [GeneralPermissionActions, ProjectPermission.Integrations]
|
||||
| [GeneralPermissionActions, ProjectPermission.Webhooks]
|
||||
| [GeneralPermissionActions, ProjectPermission.AuditLogs]
|
||||
| [GeneralPermissionActions, ProjectPermission.Environments]
|
||||
| [GeneralPermissionActions, ProjectPermission.IpAllowList]
|
||||
| [GeneralPermissionActions, ProjectPermission.Settings]
|
||||
| [GeneralPermissionActions, ProjectPermission.ServiceTokens]
|
||||
| [GeneralPermissionActions.Delete, ProjectPermission.Workspace]
|
||||
| [GeneralPermissionActions.Edit, ProjectPermission.Workspace];
|
||||
|
||||
const buildAdminPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Secrets);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Secrets);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Secrets);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Secrets);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Folders);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Folders);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Folders);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Folders);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.SecretImports);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.SecretImports);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.SecretImports);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.SecretImports);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Member);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Member);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Member);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Member);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Role);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Role);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Role);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Role);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Integrations);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Integrations);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Integrations);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Integrations);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Webhooks);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Webhooks);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Webhooks);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Webhooks);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.ServiceTokens);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.ServiceTokens);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.ServiceTokens);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.ServiceTokens);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Settings);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Settings);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Settings);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Settings);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Environments);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Environments);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Environments);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Environments);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Tags);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Tags);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Tags);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Tags);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.AuditLogs);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.AuditLogs);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.AuditLogs);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.AuditLogs);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.IpAllowList);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.IpAllowList);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.IpAllowList);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.IpAllowList);
|
||||
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Workspace);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.IpAllowList);
|
||||
|
||||
return build();
|
||||
};
|
||||
|
||||
export const adminProjectPermissions = buildAdminPermission();
|
||||
|
||||
const buildMemberPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Secrets);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Secrets);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Secrets);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Secrets);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Folders);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.Folders);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.Folders);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.Folders);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.SecretImports);
|
||||
can(GeneralPermissionActions.Create, ProjectPermission.SecretImports);
|
||||
can(GeneralPermissionActions.Edit, ProjectPermission.SecretImports);
|
||||
can(GeneralPermissionActions.Delete, ProjectPermission.SecretImports);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Member);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Role);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Integrations);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Webhooks);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.ServiceTokens);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Settings);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Environments);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Tags);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.AuditLogs);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.IpAllowList);
|
||||
|
||||
return build();
|
||||
};
|
||||
|
||||
export const memberProjectPermissions = buildMemberPermission();
|
||||
|
||||
const buildViewerPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Secrets);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Folders);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.SecretImports);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Member);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Role);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Integrations);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Webhooks);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.ServiceTokens);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Settings);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Environments);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.Tags);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.AuditLogs);
|
||||
can(GeneralPermissionActions.Read, ProjectPermission.IpAllowList);
|
||||
|
||||
return build();
|
||||
};
|
||||
|
||||
export const viewerProjectPermission = buildViewerPermission();
|
||||
|
||||
export const getUserProjectPermissions = async (userId: string, workspaceId: string) => {
|
||||
// TODO(akhilmhdh): speed this up by pulling from cache later
|
||||
const membership = await Membership.findOne({
|
||||
user: userId,
|
||||
workspace: workspaceId
|
||||
})
|
||||
.populate<{
|
||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||
}>("customRole")
|
||||
.exec();
|
||||
|
||||
console.log(membership, userId, workspaceId);
|
||||
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
||||
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
||||
}
|
||||
|
||||
if (membership.role === "admin") return { permission: adminProjectPermissions, membership };
|
||||
if (membership.role === "member") return { permission: memberProjectPermissions, membership };
|
||||
if (membership.role === "viewer") return { permission: memberProjectPermissions, membership };
|
||||
|
||||
if (membership.role === "custom") {
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(membership.customRole.permissions);
|
||||
return { permission, membership };
|
||||
}
|
||||
|
||||
throw BadRequestError({ message: "User role not found" });
|
||||
};
|
||||
@@ -56,3 +56,9 @@ export const GetUserPermission = z.object({
|
||||
orgId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetUserProjectPermission = z.object({
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
export const OWNER = "owner";
|
||||
export const ADMIN = "admin";
|
||||
export const MEMBER = "member";
|
||||
export const VIEWER = "viewer";
|
||||
export const CUSTOM = "custom";
|
||||
|
||||
// membership statuses
|
||||
|
||||
Reference in New Issue
Block a user