mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-27 07:22:17 +00:00
- Enhanced the `apiReadTraefikConfig` schema by reintroducing path validation logic to prevent directory traversal attacks and unauthorized access. - The validation now includes checks for null bytes and ensures paths start with a defined main Traefik path, improving security and robustness. These changes strengthen the integrity of the configuration handling by ensuring only valid paths are accepted.
244 lines
6.5 KiB
TypeScript
244 lines
6.5 KiB
TypeScript
import { paths } from "@dokploy/server/constants";
|
|
import { relations, sql } from "drizzle-orm";
|
|
import {
|
|
boolean,
|
|
integer,
|
|
pgTable,
|
|
text,
|
|
timestamp,
|
|
} from "drizzle-orm/pg-core";
|
|
import { createInsertSchema } from "drizzle-zod";
|
|
import { nanoid } from "nanoid";
|
|
import { z } from "zod";
|
|
import { account, apikey, organization } from "./account";
|
|
import { backups } from "./backups";
|
|
import { projects } from "./project";
|
|
import { schedules } from "./schedule";
|
|
import { ssoProvider } from "./sso";
|
|
/**
|
|
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
|
* database instance for multiple projects.
|
|
*
|
|
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
|
|
*/
|
|
|
|
// OLD TABLE
|
|
|
|
// TEMP
|
|
export const user = pgTable("user", {
|
|
id: text("id")
|
|
.notNull()
|
|
.primaryKey()
|
|
.$defaultFn(() => nanoid()),
|
|
firstName: text("firstName").notNull().default(""),
|
|
lastName: text("lastName").notNull().default(""),
|
|
isRegistered: boolean("isRegistered").notNull().default(false),
|
|
expirationDate: text("expirationDate")
|
|
.notNull()
|
|
.$defaultFn(() => new Date().toISOString()),
|
|
createdAt2: text("createdAt")
|
|
.notNull()
|
|
.$defaultFn(() => new Date().toISOString()),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
// Auth
|
|
twoFactorEnabled: boolean("two_factor_enabled"),
|
|
email: text("email").notNull().unique(),
|
|
emailVerified: boolean("email_verified").notNull(),
|
|
image: text("image"),
|
|
banned: boolean("banned"),
|
|
banReason: text("ban_reason"),
|
|
banExpires: timestamp("ban_expires"),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
// Admin
|
|
role: text("role").notNull().default("user"),
|
|
// Metrics
|
|
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
|
|
allowImpersonation: boolean("allowImpersonation").notNull().default(false),
|
|
// Enterprise / proprietary features
|
|
enableEnterpriseFeatures: boolean("enableEnterpriseFeatures")
|
|
.notNull()
|
|
.default(false),
|
|
licenseKey: text("licenseKey"),
|
|
isValidEnterpriseLicense: boolean("isValidEnterpriseLicense")
|
|
.notNull()
|
|
.default(false),
|
|
stripeCustomerId: text("stripeCustomerId"),
|
|
stripeSubscriptionId: text("stripeSubscriptionId"),
|
|
serversQuantity: integer("serversQuantity").notNull().default(0),
|
|
sendInvoiceNotifications: boolean("sendInvoiceNotifications")
|
|
.notNull()
|
|
.default(false),
|
|
isEnterpriseCloud: boolean("isEnterpriseCloud").notNull().default(false),
|
|
trustedOrigins: text("trustedOrigins").array(),
|
|
bookmarkedTemplates: text("bookmarkedTemplates")
|
|
.array()
|
|
.default(sql`ARRAY[]::text[]`),
|
|
});
|
|
|
|
export const usersRelations = relations(user, ({ one, many }) => ({
|
|
account: one(account, {
|
|
fields: [user.id],
|
|
references: [account.userId],
|
|
}),
|
|
organizations: many(organization),
|
|
projects: many(projects),
|
|
apiKeys: many(apikey),
|
|
ssoProviders: many(ssoProvider),
|
|
backups: many(backups),
|
|
schedules: many(schedules),
|
|
}));
|
|
|
|
const createSchema = createInsertSchema(user, {
|
|
id: z.string().min(1),
|
|
isRegistered: z.boolean().optional(),
|
|
}).omit({
|
|
role: true,
|
|
trustedOrigins: true,
|
|
bookmarkedTemplates: true,
|
|
isValidEnterpriseLicense: true,
|
|
isEnterpriseCloud: true,
|
|
});
|
|
|
|
export const apiCreateUserInvitation = createSchema.pick({}).extend({
|
|
email: z.string().email(),
|
|
});
|
|
|
|
export const apiRemoveUser = createSchema
|
|
.pick({
|
|
id: true,
|
|
})
|
|
.required();
|
|
|
|
export const apiFindOneToken = createSchema
|
|
.pick({})
|
|
.required()
|
|
.extend({
|
|
token: z.string().min(1),
|
|
});
|
|
|
|
export const apiAssignPermissions = createSchema
|
|
.pick({
|
|
id: true,
|
|
// canCreateProjects: true,
|
|
// canCreateServices: true,
|
|
// canDeleteProjects: true,
|
|
// canDeleteServices: true,
|
|
// accessedProjects: true,
|
|
// accessedServices: true,
|
|
// canAccessToTraefikFiles: true,
|
|
// canAccessToDocker: true,
|
|
// canAccessToAPI: true,
|
|
// canAccessToSSHKeys: true,
|
|
// canAccessToGitProviders: true,
|
|
})
|
|
.extend({
|
|
accessedProjects: z.array(z.string()).optional(),
|
|
accessedEnvironments: z.array(z.string()).optional(),
|
|
accessedServices: z.array(z.string()).optional(),
|
|
accessedGitProviders: z.array(z.string()).optional(),
|
|
accessedServers: z.array(z.string()).optional(),
|
|
canCreateProjects: z.boolean().optional(),
|
|
canCreateServices: z.boolean().optional(),
|
|
canDeleteProjects: z.boolean().optional(),
|
|
canDeleteServices: z.boolean().optional(),
|
|
canAccessToDocker: z.boolean().optional(),
|
|
canAccessToTraefikFiles: z.boolean().optional(),
|
|
canAccessToAPI: z.boolean().optional(),
|
|
canAccessToSSHKeys: z.boolean().optional(),
|
|
canAccessToGitProviders: z.boolean().optional(),
|
|
canDeleteEnvironments: z.boolean().optional(),
|
|
canCreateEnvironments: z.boolean().optional(),
|
|
})
|
|
.required();
|
|
|
|
export const apiFindOneUser = createSchema
|
|
.pick({
|
|
id: true,
|
|
})
|
|
.required();
|
|
|
|
export const apiFindOneUserByAuth = createSchema
|
|
.pick({
|
|
// authId: true,
|
|
})
|
|
.required();
|
|
|
|
export const apiTraefikConfig = z.object({
|
|
traefikConfig: z.string().min(1),
|
|
});
|
|
|
|
export const apiModifyTraefikConfig = z.object({
|
|
path: z.string().min(1),
|
|
traefikConfig: z.string().min(1),
|
|
serverId: z.string().optional(),
|
|
});
|
|
export const apiReadTraefikConfig = z.object({
|
|
path: z
|
|
.string()
|
|
.min(1)
|
|
.refine(
|
|
(path) => {
|
|
// Prevent directory traversal attacks
|
|
if (path.includes("../") || path.includes("..\\")) {
|
|
return false;
|
|
}
|
|
|
|
const { MAIN_TRAEFIK_PATH } = paths();
|
|
if (path.startsWith("/") && !path.startsWith(MAIN_TRAEFIK_PATH)) {
|
|
return false;
|
|
}
|
|
// Prevent null bytes and other dangerous characters
|
|
if (path.includes("\0") || path.includes("\x00")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
{
|
|
message:
|
|
"Invalid path: path traversal or unauthorized directory access detected",
|
|
},
|
|
),
|
|
serverId: z.string().optional(),
|
|
});
|
|
|
|
export const apiEnableDashboard = z.object({
|
|
enableDashboard: z.boolean().optional(),
|
|
serverId: z.string().optional(),
|
|
});
|
|
|
|
export const apiServerSchema = z
|
|
.object({
|
|
serverId: z.string().optional(),
|
|
})
|
|
.optional();
|
|
|
|
export const apiReadStatsLogs = z.object({
|
|
page: z
|
|
.object({
|
|
pageIndex: z.number(),
|
|
pageSize: z.number(),
|
|
})
|
|
.optional(),
|
|
status: z.string().array().optional(),
|
|
search: z.string().optional(),
|
|
sort: z.object({ id: z.string(), desc: z.boolean() }).optional(),
|
|
dateRange: z
|
|
.object({
|
|
start: z.string().optional(),
|
|
end: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
export const apiUpdateUser = createSchema.partial().extend({
|
|
email: z
|
|
.string()
|
|
.email("Please enter a valid email address")
|
|
.min(1, "Email is required")
|
|
.optional(),
|
|
password: z.string().optional(),
|
|
currentPassword: z.string().optional(),
|
|
firstName: z.string().optional(),
|
|
lastName: z.string().optional(),
|
|
});
|