feat(permissions): implement role-based access control and refactor user permissions

- Introduced a new Permissions component to manage role-based access across various components.
- Updated user role checks to utilize the new permissions structure, replacing direct role comparisons with permission checks.
- Refactored multiple components to enhance permission handling, ensuring only authorized users can access specific features.
- Removed deprecated add-permissions component and streamlined user permission management.
- Enhanced role management in the backend to support the new permissions schema, improving overall security and maintainability.
This commit is contained in:
Mauricio Siu
2025-07-13 01:52:08 -06:00
parent db221e5cc4
commit 30d45bf2e5
35 changed files with 435 additions and 728 deletions

View File

@@ -3,6 +3,7 @@ import {
invitation,
member,
organization,
role,
users,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
@@ -13,9 +14,6 @@ import { findWebServer } from "./web-server";
export const findUserById = async (userId: string) => {
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
// with: {
// account: true,
// },
});
if (!user) {
throw new TRPCError({
@@ -37,8 +35,12 @@ export const findOrganizationById = async (organizationId: string) => {
};
export const isAdminPresent = async () => {
const ownerRole = await db.query.role.findFirst({
where: eq(role.name, "owner"),
});
const admin = await db.query.member.findFirst({
where: eq(member.role, "owner"),
where: eq(member.roleId, ownerRole?.roleId || ""),
});
if (!admin) {
@@ -48,8 +50,12 @@ export const isAdminPresent = async () => {
};
export const findOwner = async () => {
const ownerRole = await db.query.role.findFirst({
where: eq(role.name, "owner"),
});
const owner = await db.query.member.findFirst({
where: eq(member.role, "owner"),
where: eq(member.roleId, ownerRole?.roleId || ""),
with: {
user: true,
},

View File

@@ -1,15 +1,17 @@
import { eq } from "drizzle-orm";
import { db } from "../db";
import {
adminPermissions,
type createRoleSchema,
member,
memberPermissions,
ownerPermissions,
role,
type updateRoleSchema,
} from "../db/schema";
import type { z } from "zod";
import {
adminPermissions,
memberPermissions,
ownerPermissions,
} from "../lib/permissions";
export const createRole = async (
input: z.infer<typeof createRoleSchema>,

View File

@@ -3,6 +3,7 @@ import { apikey, member, users } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { and, eq } from "drizzle-orm";
import { auth } from "../lib/auth";
import { PERMISSIONS } from "../lib/permissions";
export type User = typeof users.$inferSelect;
@@ -44,13 +45,16 @@ export const canPerformCreationService = async (
projectId: string,
organizationId: string,
) => {
const { accessedProjects, canCreateServices } = await findMemberById(
const { accessedProjects, role } = await findMemberById(
userId,
organizationId,
);
const haveAccessToProject = accessedProjects.includes(projectId);
if (canCreateServices && haveAccessToProject) {
if (
role?.permissions?.includes(PERMISSIONS.SERVICE.CREATE.name) &&
haveAccessToProject
) {
return true;
}
@@ -77,13 +81,16 @@ export const canPeformDeleteService = async (
serviceId: string,
organizationId: string,
) => {
const { accessedServices, canDeleteServices } = await findMemberById(
const { accessedServices, role } = await findMemberById(
userId,
organizationId,
);
const haveAccessToService = accessedServices.includes(serviceId);
if (canDeleteServices && haveAccessToService) {
if (
role?.permissions?.includes(PERMISSIONS.SERVICE.DELETE.name) &&
haveAccessToService
) {
return true;
}
@@ -94,9 +101,9 @@ export const canPerformCreationProject = async (
userId: string,
organizationId: string,
) => {
const { canCreateProjects } = await findMemberById(userId, organizationId);
const { role } = await findMemberById(userId, organizationId);
if (canCreateProjects) {
if (role?.permissions?.includes(PERMISSIONS.PROJECT.CREATE.name)) {
return true;
}
@@ -107,9 +114,9 @@ export const canPerformDeleteProject = async (
userId: string,
organizationId: string,
) => {
const { canDeleteProjects } = await findMemberById(userId, organizationId);
const { role } = await findMemberById(userId, organizationId);
if (canDeleteProjects) {
if (role?.permissions?.includes(PERMISSIONS.PROJECT.DELETE.name)) {
return true;
}
@@ -135,11 +142,8 @@ export const canAccessToTraefikFiles = async (
userId: string,
organizationId: string,
) => {
const { canAccessToTraefikFiles } = await findMemberById(
userId,
organizationId,
);
return canAccessToTraefikFiles;
const { role } = await findMemberById(userId, organizationId);
return role?.permissions?.includes(PERMISSIONS.TRAEFIK.ACCESS.name);
};
export const checkServiceAccess = async (
@@ -183,7 +187,7 @@ export const checkServiceAccess = async (
};
export const checkProjectAccess = async (
authId: string,
userId: string,
action: "create" | "delete" | "access",
organizationId: string,
projectId?: string,
@@ -192,16 +196,16 @@ export const checkProjectAccess = async (
switch (action) {
case "access":
hasPermission = await canPerformAccessProject(
authId,
userId,
projectId as string,
organizationId,
);
break;
case "create":
hasPermission = await canPerformCreationProject(authId, organizationId);
hasPermission = await canPerformCreationProject(userId, organizationId);
break;
case "delete":
hasPermission = await canPerformDeleteProject(authId, organizationId);
hasPermission = await canPerformDeleteProject(userId, organizationId);
break;
default:
hasPermission = false;
@@ -225,6 +229,7 @@ export const findMemberById = async (
),
with: {
user: true,
role: true,
},
});