mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-12 23:15:18 +00:00
feat: add accessedServers permission handling and server access validation
- Introduced `accessedServers` field in user permissions schema and member table. - Implemented server access validation across various API routers to ensure users can only access permitted servers. - Added a new query to fetch accessible server IDs based on user roles and licenses. - Updated UI components to support server selection in user permissions.
This commit is contained in:
@@ -172,6 +172,7 @@ const addPermissions = z.object({
|
|||||||
accessedEnvironments: z.array(z.string()).optional(),
|
accessedEnvironments: z.array(z.string()).optional(),
|
||||||
accessedServices: z.array(z.string()).optional(),
|
accessedServices: z.array(z.string()).optional(),
|
||||||
accessedGitProviders: z.array(z.string()).optional(),
|
accessedGitProviders: z.array(z.string()).optional(),
|
||||||
|
accessedServers: z.array(z.string()).optional(),
|
||||||
canCreateProjects: z.boolean().optional().default(false),
|
canCreateProjects: z.boolean().optional().default(false),
|
||||||
canCreateServices: z.boolean().optional().default(false),
|
canCreateServices: z.boolean().optional().default(false),
|
||||||
canDeleteProjects: z.boolean().optional().default(false),
|
canDeleteProjects: z.boolean().optional().default(false),
|
||||||
@@ -208,6 +209,13 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: servers } = api.server.allForPermissions.useQuery(
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
enabled: isOpen && !!haveValidLicense,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const { data, refetch } = api.user.one.useQuery(
|
const { data, refetch } = api.user.one.useQuery(
|
||||||
{
|
{
|
||||||
userId,
|
userId,
|
||||||
@@ -226,6 +234,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
|||||||
accessedEnvironments: [],
|
accessedEnvironments: [],
|
||||||
accessedServices: [],
|
accessedServices: [],
|
||||||
accessedGitProviders: [],
|
accessedGitProviders: [],
|
||||||
|
accessedServers: [],
|
||||||
canDeleteEnvironments: false,
|
canDeleteEnvironments: false,
|
||||||
canCreateProjects: false,
|
canCreateProjects: false,
|
||||||
canCreateServices: false,
|
canCreateServices: false,
|
||||||
@@ -248,6 +257,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
|||||||
accessedEnvironments: data.accessedEnvironments || [],
|
accessedEnvironments: data.accessedEnvironments || [],
|
||||||
accessedServices: data.accessedServices || [],
|
accessedServices: data.accessedServices || [],
|
||||||
accessedGitProviders: data.accessedGitProviders || [],
|
accessedGitProviders: data.accessedGitProviders || [],
|
||||||
|
accessedServers: data.accessedServers || [],
|
||||||
canCreateProjects: data.canCreateProjects,
|
canCreateProjects: data.canCreateProjects,
|
||||||
canCreateServices: data.canCreateServices,
|
canCreateServices: data.canCreateServices,
|
||||||
canDeleteProjects: data.canDeleteProjects,
|
canDeleteProjects: data.canDeleteProjects,
|
||||||
@@ -276,6 +286,7 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
|||||||
accessedEnvironments: data.accessedEnvironments || [],
|
accessedEnvironments: data.accessedEnvironments || [],
|
||||||
accessedServices: data.accessedServices || [],
|
accessedServices: data.accessedServices || [],
|
||||||
accessedGitProviders: data.accessedGitProviders || [],
|
accessedGitProviders: data.accessedGitProviders || [],
|
||||||
|
accessedServers: data.accessedServers || [],
|
||||||
canAccessToDocker: data.canAccessToDocker,
|
canAccessToDocker: data.canAccessToDocker,
|
||||||
canAccessToAPI: data.canAccessToAPI,
|
canAccessToAPI: data.canAccessToAPI,
|
||||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||||
@@ -956,6 +967,81 @@ export const AddUserPermissions = ({ userId, role }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{haveValidLicense ? (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="accessedServers"
|
||||||
|
render={() => (
|
||||||
|
<FormItem className="md:col-span-2">
|
||||||
|
<div className="mb-4">
|
||||||
|
<FormLabel className="text-base">Servers</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Select the Servers that the user can access
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
{servers?.length === 0 && (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
No servers found
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="grid md:grid-cols-1 gap-2">
|
||||||
|
{servers?.map((s) => (
|
||||||
|
<FormField
|
||||||
|
key={s.serverId}
|
||||||
|
control={form.control}
|
||||||
|
name="accessedServers"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center space-x-3 space-y-0 rounded-lg border p-3">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value?.includes(
|
||||||
|
s.serverId,
|
||||||
|
)}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (checked) {
|
||||||
|
field.onChange([
|
||||||
|
...(field.value || []),
|
||||||
|
s.serverId,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
field.onChange(
|
||||||
|
field.value?.filter(
|
||||||
|
(v) => v !== s.serverId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FormLabel className="text-sm cursor-pointer">
|
||||||
|
{s.name}
|
||||||
|
</FormLabel>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
({s.ipAddress})
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-muted-foreground capitalize">
|
||||||
|
{s.serverType}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<EnterpriseFeatureLocked
|
||||||
|
compact
|
||||||
|
title="Server Assignment"
|
||||||
|
description="Assign specific Servers to users with an Enterprise license."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2">
|
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2">
|
||||||
<Button
|
<Button
|
||||||
isLoading={isPending}
|
isLoading={isPending}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "member" ADD COLUMN "accessedServers" text[] DEFAULT ARRAY[]::text[] NOT NULL;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1142,6 +1142,13 @@
|
|||||||
"when": 1775348977418,
|
"when": 1775348977418,
|
||||||
"tag": "0162_happy_alex_wilder",
|
"tag": "0162_happy_alex_wilder",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 163,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1775367585821,
|
||||||
|
"tag": "0163_perfect_lethal_legion",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
updateDeploymentStatus,
|
updateDeploymentStatus,
|
||||||
writeConfig,
|
writeConfig,
|
||||||
writeConfigRemote,
|
writeConfigRemote,
|
||||||
|
getAccessibleServerIds,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
@@ -99,6 +100,16 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newApplication = await createApplication(input);
|
const newApplication = await createApplication(input);
|
||||||
|
|
||||||
await addNewService(ctx, newApplication.applicationId);
|
await addNewService(ctx, newApplication.applicationId);
|
||||||
@@ -630,6 +641,17 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
await checkServicePermissionAndAccess(ctx, input.applicationId, {
|
await checkServicePermissionAndAccess(ctx, input.applicationId, {
|
||||||
service: ["create"],
|
service: ["create"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (input.buildServerId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.buildServerId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this build server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { applicationId, ...rest } = input;
|
const { applicationId, ...rest } = input;
|
||||||
const updateApp = await updateApplication(applicationId, {
|
const updateApp = await updateApplication(applicationId, {
|
||||||
...rest,
|
...rest,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
stopCompose,
|
stopCompose,
|
||||||
updateCompose,
|
updateCompose,
|
||||||
updateDeploymentStatus,
|
updateDeploymentStatus,
|
||||||
|
getAccessibleServerIds,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
@@ -100,6 +101,17 @@ export const composeRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newService = await createCompose({
|
const newService = await createCompose({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
@@ -553,6 +565,16 @@ export const composeRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const template = await fetchTemplateFiles(input.id, input.baseUrl);
|
const template = await fetchTemplateFiles(input.id, input.baseUrl);
|
||||||
|
|
||||||
let serverIp = "127.0.0.1";
|
let serverIp = "127.0.0.1";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updateLibsqlById,
|
updateLibsqlById,
|
||||||
|
getAccessibleServerIds,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import {
|
import {
|
||||||
addNewService,
|
addNewService,
|
||||||
@@ -62,6 +63,17 @@ export const libsqlRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newLibsql = await createLibsql({
|
const newLibsql = await createLibsql({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updateMariadbById,
|
updateMariadbById,
|
||||||
|
getAccessibleServerIds,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
@@ -73,6 +74,17 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newMariadb = await createMariadb({
|
const newMariadb = await createMariadb({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
findEnvironmentById,
|
findEnvironmentById,
|
||||||
findMongoById,
|
findMongoById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
|
getAccessibleServerIds,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
@@ -72,6 +73,17 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newMongo = await createMongo({
|
const newMongo = await createMongo({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updateMySqlById,
|
updateMySqlById,
|
||||||
|
getAccessibleServerIds,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
@@ -74,6 +75,16 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newMysql = await createMysql({
|
const newMysql = await createMysql({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updatePostgresById,
|
updatePostgresById,
|
||||||
|
getAccessibleServerIds,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
@@ -74,6 +75,17 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newPostgres = await createPostgres({
|
const newPostgres = await createPostgres({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updateRedisById,
|
updateRedisById,
|
||||||
|
getAccessibleServerIds,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
@@ -70,6 +71,17 @@ export const redisRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newRedis = await createRedis({
|
const newRedis = await createRedis({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ import {
|
|||||||
serverValidate,
|
serverValidate,
|
||||||
setupMonitoring,
|
setupMonitoring,
|
||||||
updateServerById,
|
updateServerById,
|
||||||
|
getAccessibleServerIds,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
|
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { and, desc, eq, getTableColumns, isNotNull, sql } from "drizzle-orm";
|
import { and, desc, eq, getTableColumns, isNotNull, sql } from "drizzle-orm";
|
||||||
@@ -88,6 +90,14 @@ export const serverRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
if (!accessibleIds.has(input.serverId)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this server",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}),
|
}),
|
||||||
getDefaultCommand: withPermission("server", "read")
|
getDefaultCommand: withPermission("server", "read")
|
||||||
@@ -98,6 +108,8 @@ export const serverRouter = createTRPCRouter({
|
|||||||
return defaultCommand(isBuildServer);
|
return defaultCommand(isBuildServer);
|
||||||
}),
|
}),
|
||||||
all: withPermission("server", "read").query(async ({ ctx }) => {
|
all: withPermission("server", "read").query(async ({ ctx }) => {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
|
||||||
const result = await db
|
const result = await db
|
||||||
.select({
|
.select({
|
||||||
...getTableColumns(server),
|
...getTableColumns(server),
|
||||||
@@ -115,8 +127,31 @@ export const serverRouter = createTRPCRouter({
|
|||||||
.orderBy(desc(server.createdAt))
|
.orderBy(desc(server.createdAt))
|
||||||
.groupBy(server.serverId);
|
.groupBy(server.serverId);
|
||||||
|
|
||||||
return result;
|
return result.filter((s) => accessibleIds.has(s.serverId));
|
||||||
}),
|
}),
|
||||||
|
allForPermissions: withPermission("member", "update")
|
||||||
|
.use(async ({ ctx, next }) => {
|
||||||
|
const licensed = await hasValidLicense(ctx.session.activeOrganizationId);
|
||||||
|
if (!licensed) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "Valid enterprise license required",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.query(async ({ ctx }) => {
|
||||||
|
return await db.query.server.findMany({
|
||||||
|
columns: {
|
||||||
|
serverId: true,
|
||||||
|
name: true,
|
||||||
|
ipAddress: true,
|
||||||
|
serverType: true,
|
||||||
|
},
|
||||||
|
orderBy: desc(server.createdAt),
|
||||||
|
where: eq(server.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
});
|
||||||
|
}),
|
||||||
count: protectedProcedure.query(async ({ ctx }) => {
|
count: protectedProcedure.query(async ({ ctx }) => {
|
||||||
const organizations = await db.query.organization.findMany({
|
const organizations = await db.query.organization.findMany({
|
||||||
where: eq(organization.ownerId, ctx.user.id),
|
where: eq(organization.ownerId, ctx.user.id),
|
||||||
@@ -130,6 +165,8 @@ export const serverRouter = createTRPCRouter({
|
|||||||
return servers.length ?? 0;
|
return servers.length ?? 0;
|
||||||
}),
|
}),
|
||||||
withSSHKey: withPermission("server", "read").query(async ({ ctx }) => {
|
withSSHKey: withPermission("server", "read").query(async ({ ctx }) => {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
|
||||||
const result = await db.query.server.findMany({
|
const result = await db.query.server.findMany({
|
||||||
orderBy: desc(server.createdAt),
|
orderBy: desc(server.createdAt),
|
||||||
where: IS_CLOUD
|
where: IS_CLOUD
|
||||||
@@ -145,9 +182,11 @@ export const serverRouter = createTRPCRouter({
|
|||||||
eq(server.serverType, "deploy"),
|
eq(server.serverType, "deploy"),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
return result;
|
return result.filter((s) => accessibleIds.has(s.serverId));
|
||||||
}),
|
}),
|
||||||
buildServers: withPermission("server", "read").query(async ({ ctx }) => {
|
buildServers: withPermission("server", "read").query(async ({ ctx }) => {
|
||||||
|
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||||
|
|
||||||
const result = await db.query.server.findMany({
|
const result = await db.query.server.findMany({
|
||||||
orderBy: desc(server.createdAt),
|
orderBy: desc(server.createdAt),
|
||||||
where: IS_CLOUD
|
where: IS_CLOUD
|
||||||
@@ -163,7 +202,7 @@ export const serverRouter = createTRPCRouter({
|
|||||||
eq(server.serverType, "build"),
|
eq(server.serverType, "build"),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
return result;
|
return result.filter((s) => accessibleIds.has(s.serverId));
|
||||||
}),
|
}),
|
||||||
setup: withPermission("server", "create")
|
setup: withPermission("server", "create")
|
||||||
.input(apiFindOneServer)
|
.input(apiFindOneServer)
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, accessedGitProviders, ...rest } = input;
|
const { id, accessedGitProviders, accessedServers, ...rest } = input;
|
||||||
|
|
||||||
const licensed = await hasValidLicense(
|
const licensed = await hasValidLicense(
|
||||||
ctx.session?.activeOrganizationId || "",
|
ctx.session?.activeOrganizationId || "",
|
||||||
@@ -360,6 +360,9 @@ export const userRouter = createTRPCRouter({
|
|||||||
...(licensed && accessedGitProviders !== undefined
|
...(licensed && accessedGitProviders !== undefined
|
||||||
? { accessedGitProviders }
|
? { accessedGitProviders }
|
||||||
: {}),
|
: {}),
|
||||||
|
...(licensed && accessedServers !== undefined
|
||||||
|
? { accessedServers }
|
||||||
|
: {}),
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
|
|||||||
@@ -167,6 +167,10 @@ export const member = pgTable("member", {
|
|||||||
.array()
|
.array()
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(sql`ARRAY[]::text[]`),
|
.default(sql`ARRAY[]::text[]`),
|
||||||
|
accessedServers: text("accessedServers")
|
||||||
|
.array()
|
||||||
|
.notNull()
|
||||||
|
.default(sql`ARRAY[]::text[]`),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const memberRelations = relations(member, ({ one }) => ({
|
export const memberRelations = relations(member, ({ one }) => ({
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export const apiAssignPermissions = createSchema
|
|||||||
accessedEnvironments: z.array(z.string()).optional(),
|
accessedEnvironments: z.array(z.string()).optional(),
|
||||||
accessedServices: z.array(z.string()).optional(),
|
accessedServices: z.array(z.string()).optional(),
|
||||||
accessedGitProviders: z.array(z.string()).optional(),
|
accessedGitProviders: z.array(z.string()).optional(),
|
||||||
|
accessedServers: z.array(z.string()).optional(),
|
||||||
canCreateProjects: z.boolean().optional(),
|
canCreateProjects: z.boolean().optional(),
|
||||||
canCreateServices: z.boolean().optional(),
|
canCreateServices: z.boolean().optional(),
|
||||||
canDeleteProjects: z.boolean().optional(),
|
canDeleteProjects: z.boolean().optional(),
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
type apiCreateServer,
|
type apiCreateServer,
|
||||||
|
member,
|
||||||
organization,
|
organization,
|
||||||
server,
|
server,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
|
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
|
|
||||||
export type Server = typeof server.$inferSelect;
|
export type Server = typeof server.$inferSelect;
|
||||||
@@ -130,3 +132,37 @@ export const getAllServers = async () => {
|
|||||||
const servers = await db.query.server.findMany();
|
const servers = await db.query.server.findMany();
|
||||||
return servers;
|
return servers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAccessibleServerIds = async (session: {
|
||||||
|
userId: string;
|
||||||
|
activeOrganizationId: string;
|
||||||
|
}): Promise<Set<string>> => {
|
||||||
|
const { userId, activeOrganizationId } = session;
|
||||||
|
|
||||||
|
const allOrgServers = await db.query.server.findMany({
|
||||||
|
where: eq(server.organizationId, activeOrganizationId),
|
||||||
|
columns: {
|
||||||
|
serverId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const memberRecord = await db.query.member.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(member.userId, userId),
|
||||||
|
eq(member.organizationId, activeOrganizationId),
|
||||||
|
),
|
||||||
|
columns: { accessedServers: true, role: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (memberRecord?.role === "owner" || memberRecord?.role === "admin") {
|
||||||
|
return new Set(allOrgServers.map((s) => s.serverId));
|
||||||
|
}
|
||||||
|
|
||||||
|
const licensed = await hasValidLicense(activeOrganizationId);
|
||||||
|
|
||||||
|
if (!licensed) {
|
||||||
|
return new Set(allOrgServers.map((s) => s.serverId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Set(memberRecord?.accessedServers ?? []);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user