From a43b8ee2d256b97198fd29de9e3fad1374c68c7b Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 13 Jul 2025 02:05:59 -0600 Subject: [PATCH] feat(permissions): update role-based access checks across dashboard components - Refactored user role checks in various dashboard pages to utilize the new permissions structure. - Replaced direct role comparisons with permission checks for enhanced security and maintainability. - Updated the `validateRequest` function to streamline user role handling and ensure proper access control. - Improved consistency in permission checks across components, ensuring only authorized users can access specific features. --- apps/dokploy/pages/dashboard/docker.tsx | 9 ++---- apps/dokploy/pages/dashboard/schedules.tsx | 2 +- apps/dokploy/pages/dashboard/settings/ai.tsx | 2 +- .../pages/dashboard/settings/billing.tsx | 2 +- .../pages/dashboard/settings/certificates.tsx | 2 +- .../pages/dashboard/settings/cluster.tsx | 2 +- .../pages/dashboard/settings/destinations.tsx | 2 +- .../dashboard/settings/git-providers.tsx | 13 ++++---- .../dashboard/settings/notifications.tsx | 2 +- .../pages/dashboard/settings/registry.tsx | 2 +- .../pages/dashboard/settings/server.tsx | 2 +- .../pages/dashboard/settings/servers.tsx | 2 +- .../pages/dashboard/settings/ssh-keys.tsx | 11 +++---- .../pages/dashboard/settings/users.tsx | 2 +- apps/dokploy/pages/dashboard/swarm.tsx | 9 ++---- apps/dokploy/pages/dashboard/traefik.tsx | 9 ++---- apps/dokploy/pages/swagger.tsx | 25 +++------------ apps/dokploy/server/api/trpc.ts | 1 + packages/server/src/lib/auth.ts | 32 +++++++++---------- 19 files changed, 51 insertions(+), 80 deletions(-) diff --git a/apps/dokploy/pages/dashboard/docker.tsx b/apps/dokploy/pages/dashboard/docker.tsx index 23ef1db14..84f59bf41 100644 --- a/apps/dokploy/pages/dashboard/docker.tsx +++ b/apps/dokploy/pages/dashboard/docker.tsx @@ -54,13 +54,8 @@ export async function getServerSideProps( try { await helpers.project.all.prefetch(); - - if (user.role?.name === "member" || user?.role?.) { - const userR = await helpers.user.one.fetch({ - userId: user.id, - }); - - if (!userR?.role?.permissions?.includes(PERMISSIONS.DOCKER.VIEW.name)) { + if (user.role?.name === "member" || !user?.role?.isSystem) { + if (!user?.role?.permissions?.includes(PERMISSIONS.DOCKER.VIEW.name)) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/schedules.tsx b/apps/dokploy/pages/dashboard/schedules.tsx index c537990c4..7ec8766e6 100644 --- a/apps/dokploy/pages/dashboard/schedules.tsx +++ b/apps/dokploy/pages/dashboard/schedules.tsx @@ -39,7 +39,7 @@ export async function getServerSideProps( }; } const { user } = await validateRequest(ctx.req); - if (!user || user.role !== "owner") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/ai.tsx b/apps/dokploy/pages/dashboard/settings/ai.tsx index 8195ce626..b348793a6 100644 --- a/apps/dokploy/pages/dashboard/settings/ai.tsx +++ b/apps/dokploy/pages/dashboard/settings/ai.tsx @@ -44,7 +44,7 @@ export async function getServerSideProps( await helpers.user.get.prefetch(); - if (!user || user.role === "member") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/billing.tsx b/apps/dokploy/pages/dashboard/settings/billing.tsx index 7ba5717e9..33d7a7c3e 100644 --- a/apps/dokploy/pages/dashboard/settings/billing.tsx +++ b/apps/dokploy/pages/dashboard/settings/billing.tsx @@ -31,7 +31,7 @@ export async function getServerSideProps( } const { req, res } = ctx; const { user, session } = await validateRequest(req); - if (!user || user.role === "member") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/certificates.tsx b/apps/dokploy/pages/dashboard/settings/certificates.tsx index 0c82ed4fb..5abedc728 100644 --- a/apps/dokploy/pages/dashboard/settings/certificates.tsx +++ b/apps/dokploy/pages/dashboard/settings/certificates.tsx @@ -25,7 +25,7 @@ export async function getServerSideProps( ) { const { req, res } = ctx; const { user, session } = await validateRequest(req); - if (!user || user.role === "member") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/cluster.tsx b/apps/dokploy/pages/dashboard/settings/cluster.tsx index a1a46bb65..fbae9db58 100644 --- a/apps/dokploy/pages/dashboard/settings/cluster.tsx +++ b/apps/dokploy/pages/dashboard/settings/cluster.tsx @@ -34,7 +34,7 @@ export async function getServerSideProps( }; } const { user, session } = await validateRequest(ctx.req); - if (!user || user.role === "member") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/destinations.tsx b/apps/dokploy/pages/dashboard/settings/destinations.tsx index 3c906b55c..792106484 100644 --- a/apps/dokploy/pages/dashboard/settings/destinations.tsx +++ b/apps/dokploy/pages/dashboard/settings/destinations.tsx @@ -26,7 +26,7 @@ export async function getServerSideProps( ) { const { req, res } = ctx; const { user, session } = await validateRequest(req); - if (!user || user.role === "member") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/git-providers.tsx b/apps/dokploy/pages/dashboard/settings/git-providers.tsx index fec2a08f6..33601876e 100644 --- a/apps/dokploy/pages/dashboard/settings/git-providers.tsx +++ b/apps/dokploy/pages/dashboard/settings/git-providers.tsx @@ -3,6 +3,7 @@ import { DashboardLayout } from "@/components/layouts/dashboard-layout"; import { appRouter } from "@/server/api/root"; import { validateRequest } from "@dokploy/server"; +import { PERMISSIONS } from "@dokploy/server/lib/permissions"; import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; import type { ReactElement } from "react"; @@ -49,12 +50,12 @@ export async function getServerSideProps( try { await helpers.project.all.prefetch(); await helpers.settings.isCloud.prefetch(); - if (user.role === "member") { - const userR = await helpers.user.one.fetch({ - userId: user.id, - }); - - if (!userR?.canAccessToGitProviders) { + if (user.role?.name === "member" || !user?.role?.isSystem) { + if ( + !user?.role?.permissions?.includes( + PERMISSIONS.GIT_PROVIDERS.ACCESS.name, + ) + ) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/notifications.tsx b/apps/dokploy/pages/dashboard/settings/notifications.tsx index fbdc2e205..041b1a0a4 100644 --- a/apps/dokploy/pages/dashboard/settings/notifications.tsx +++ b/apps/dokploy/pages/dashboard/settings/notifications.tsx @@ -26,7 +26,7 @@ export async function getServerSideProps( ) { const { req, res } = ctx; const { user, session } = await validateRequest(req); - if (!user || user.role === "member") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/registry.tsx b/apps/dokploy/pages/dashboard/settings/registry.tsx index 42f0627f4..2d4337ee8 100644 --- a/apps/dokploy/pages/dashboard/settings/registry.tsx +++ b/apps/dokploy/pages/dashboard/settings/registry.tsx @@ -26,7 +26,7 @@ export async function getServerSideProps( ) { const { req, res } = ctx; const { user, session } = await validateRequest(req); - if (!user || user.role === "member") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/server.tsx b/apps/dokploy/pages/dashboard/settings/server.tsx index 84f6b2a18..290348bfc 100644 --- a/apps/dokploy/pages/dashboard/settings/server.tsx +++ b/apps/dokploy/pages/dashboard/settings/server.tsx @@ -59,7 +59,7 @@ export async function getServerSideProps( }, }; } - if (user.role === "member") { + if (user.role?.name === "member" || !user?.role?.isSystem) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/servers.tsx b/apps/dokploy/pages/dashboard/settings/servers.tsx index 5cc30b832..65cfc2124 100644 --- a/apps/dokploy/pages/dashboard/settings/servers.tsx +++ b/apps/dokploy/pages/dashboard/settings/servers.tsx @@ -36,7 +36,7 @@ export async function getServerSideProps( }, }; } - if (user.role === "member") { + if (user.role?.name === "member" || !user?.role?.isSystem) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx b/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx index 6ce2fd571..677570d05 100644 --- a/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx +++ b/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx @@ -3,6 +3,7 @@ import { DashboardLayout } from "@/components/layouts/dashboard-layout"; import { appRouter } from "@/server/api/root"; import { validateRequest } from "@dokploy/server"; +import { PERMISSIONS } from "@dokploy/server/lib/permissions"; import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; import type { ReactElement } from "react"; @@ -50,12 +51,10 @@ export async function getServerSideProps( await helpers.project.all.prefetch(); await helpers.settings.isCloud.prefetch(); - if (user.role === "member") { - const userR = await helpers.user.one.fetch({ - userId: user.id, - }); - - if (!userR?.canAccessToSSHKeys) { + if (user.role?.name === "member" || !user?.role?.isSystem) { + if ( + !user?.role?.permissions?.includes(PERMISSIONS.SSH_KEYS.ACCESS.name) + ) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/settings/users.tsx b/apps/dokploy/pages/dashboard/settings/users.tsx index 16f90abb1..ba1609c55 100644 --- a/apps/dokploy/pages/dashboard/settings/users.tsx +++ b/apps/dokploy/pages/dashboard/settings/users.tsx @@ -29,7 +29,7 @@ export async function getServerSideProps( const { req, res } = ctx; const { user, session } = await validateRequest(req); - if (!user || user.role === "member") { + if (!user || (user.role?.name !== "owner" && user.role?.name !== "admin")) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/swarm.tsx b/apps/dokploy/pages/dashboard/swarm.tsx index 976436143..af70a68cd 100644 --- a/apps/dokploy/pages/dashboard/swarm.tsx +++ b/apps/dokploy/pages/dashboard/swarm.tsx @@ -3,6 +3,7 @@ import { DashboardLayout } from "@/components/layouts/dashboard-layout"; import { appRouter } from "@/server/api/root"; import { IS_CLOUD } from "@dokploy/server/constants"; import { validateRequest } from "@dokploy/server/lib/auth"; +import { PERMISSIONS } from "@dokploy/server/lib/permissions"; import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; import type { ReactElement } from "react"; @@ -53,12 +54,8 @@ export async function getServerSideProps( try { await helpers.project.all.prefetch(); - if (user.role === "member") { - const userR = await helpers.user.one.fetch({ - userId: user.id, - }); - - if (!userR?.canAccessToDocker) { + if (user.role?.name === "member" || !user?.role?.isSystem) { + if (!user?.role?.permissions?.includes(PERMISSIONS.DOCKER.VIEW.name)) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/dashboard/traefik.tsx b/apps/dokploy/pages/dashboard/traefik.tsx index 893b3725d..bd2d42693 100644 --- a/apps/dokploy/pages/dashboard/traefik.tsx +++ b/apps/dokploy/pages/dashboard/traefik.tsx @@ -3,6 +3,7 @@ import { DashboardLayout } from "@/components/layouts/dashboard-layout"; import { appRouter } from "@/server/api/root"; import { IS_CLOUD } from "@dokploy/server/constants"; import { validateRequest } from "@dokploy/server/lib/auth"; +import { PERMISSIONS } from "@dokploy/server/lib/permissions"; import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; import type { ReactElement } from "react"; @@ -53,12 +54,8 @@ export async function getServerSideProps( try { await helpers.project.all.prefetch(); - if (user.role === "member") { - const userR = await helpers.user.one.fetch({ - userId: user.id, - }); - - if (!userR?.canAccessToTraefikFiles) { + if (user.role?.name === "member" || !user?.role?.isSystem) { + if (!user?.role?.permissions?.includes(PERMISSIONS.TRAEFIK.ACCESS.name)) { return { redirect: { permanent: true, diff --git a/apps/dokploy/pages/swagger.tsx b/apps/dokploy/pages/swagger.tsx index 11ea0731d..679329180 100644 --- a/apps/dokploy/pages/swagger.tsx +++ b/apps/dokploy/pages/swagger.tsx @@ -1,12 +1,10 @@ -import { appRouter } from "@/server/api/root"; import { api } from "@/utils/api"; import { validateRequest } from "@dokploy/server"; -import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext, NextPage } from "next"; import dynamic from "next/dynamic"; import "swagger-ui-react/swagger-ui.css"; import { useEffect, useState } from "react"; -import superjson from "superjson"; +import { PERMISSIONS } from "@dokploy/server/lib/permissions"; const SwaggerUI = dynamic(() => import("swagger-ui-react"), { ssr: false }); @@ -71,8 +69,7 @@ const Home: NextPage = () => { export default Home; export async function getServerSideProps(context: GetServerSidePropsContext) { - const { req, res } = context; - const { user, session } = await validateRequest(context.req); + const { user } = await validateRequest(context.req); if (!user) { return { redirect: { @@ -81,23 +78,9 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }, }; } - const helpers = createServerSideHelpers({ - router: appRouter, - ctx: { - req: req as any, - res: res as any, - db: null as any, - session: session as any, - user: user as any, - }, - transformer: superjson, - }); - if (user.role === "member") { - const userR = await helpers.user.one.fetch({ - userId: user.id, - }); - if (!userR?.canAccessToAPI) { + if (user.role?.name === "member" || !user?.role?.isSystem) { + if (!user?.role?.permissions?.includes(PERMISSIONS.API.ACCESS.name)) { return { redirect: { permanent: true, diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts index fa7e740b8..e89a71cf7 100644 --- a/apps/dokploy/server/api/trpc.ts +++ b/apps/dokploy/server/api/trpc.ts @@ -206,6 +206,7 @@ export const cliProcedure = t.procedure.use(({ ctx, next }) => { }); export const adminProcedure = t.procedure.use(({ ctx, next }) => { + console.log("adminProcedure", ctx.session, ctx.user); if ( !ctx.session || !ctx.user || diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index 2dc758e95..b7662a7aa 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -16,6 +16,7 @@ import { findWebServer, updateWebServer, } from "@dokploy/server/services/web-server"; +import type { Role } from "../db/schema/rbac"; const { handler, api } = betterAuth({ database: drizzleAdapter(db, { @@ -409,15 +410,7 @@ export const validateRequest = async (request: IncomingMessage) => { }; } - const mockSession = { - session: { - ...session.session, - }, - user: { - ...session.user, - ownerId: session.user.ownerId, - }, - }; + let role: Role | null = null; if (session?.user) { const member = await db.query.member.findFirst({ where: and( @@ -432,17 +425,22 @@ export const validateRequest = async (request: IncomingMessage) => { organization: true, }, }); - - if (member?.role) { - mockSession.user.role = member.role; - } - + role = member?.role || null; if (member) { - mockSession.user.ownerId = member.organization.ownerId; + session.user.ownerId = member.organization.ownerId; } else { - mockSession.user.ownerId = session.user.id; + session.user.ownerId = session.user.id; } } - + const mockSession = { + session: { + ...session.session, + }, + user: { + ...session.user, + role, + ownerId: session.user.ownerId, + }, + }; return mockSession; };