mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-07 23:15:27 +02:00
feat: implement forward authentication settings and UI components
- Added a new `forward_auth_settings` table to manage authentication domains and their configurations. - Introduced UI components for handling forward authentication, including enabling/disabling SSO for domains and selecting SSO providers. - Updated existing tests to include validation for the new `forwardAuthProviderId` field in domain configurations. - Enhanced the dashboard to integrate forward authentication management, allowing users to configure SSO settings directly from the application interface. This update improves the flexibility and security of application authentication by allowing integration with various identity providers.
This commit is contained in:
@@ -16,6 +16,7 @@ import { applications } from "./application";
|
||||
import { compose } from "./compose";
|
||||
import { previewDeployments } from "./preview-deployments";
|
||||
import { certificateType } from "./shared";
|
||||
import { ssoProvider } from "./sso";
|
||||
|
||||
export const domainType = pgEnum("domainType", [
|
||||
"compose",
|
||||
@@ -55,6 +56,10 @@ export const domains = pgTable("domain", {
|
||||
internalPath: text("internalPath").default("/"),
|
||||
stripPath: boolean("stripPath").notNull().default(false),
|
||||
middlewares: text("middlewares").array().default(sql`ARRAY[]::text[]`),
|
||||
forwardAuthProviderId: text("forwardAuthProviderId").references(
|
||||
() => ssoProvider.providerId,
|
||||
{ onDelete: "set null" },
|
||||
),
|
||||
});
|
||||
|
||||
export const domainsRelations = relations(domains, ({ one }) => ({
|
||||
@@ -70,6 +75,10 @@ export const domainsRelations = relations(domains, ({ one }) => ({
|
||||
fields: [domains.previewDeploymentId],
|
||||
references: [previewDeployments.previewDeploymentId],
|
||||
}),
|
||||
forwardAuthProvider: one(ssoProvider, {
|
||||
fields: [domains.forwardAuthProviderId],
|
||||
references: [ssoProvider.providerId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(domains, {
|
||||
@@ -94,6 +103,7 @@ export const apiCreateDomain = createSchema.pick({
|
||||
internalPath: true,
|
||||
stripPath: true,
|
||||
middlewares: true,
|
||||
forwardAuthProviderId: true,
|
||||
});
|
||||
|
||||
export const apiFindDomain = z.object({
|
||||
@@ -126,5 +136,6 @@ export const apiUpdateDomain = createSchema
|
||||
internalPath: true,
|
||||
stripPath: true,
|
||||
middlewares: true,
|
||||
forwardAuthProviderId: true,
|
||||
})
|
||||
.merge(createSchema.pick({ domainId: true }).required());
|
||||
|
||||
62
packages/server/src/db/schema/forward-auth.ts
Normal file
62
packages/server/src/db/schema/forward-auth.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { server } from "./server";
|
||||
import { certificateType } from "./shared";
|
||||
import { ssoProvider } from "./sso";
|
||||
|
||||
export const forwardAuthSettings = pgTable("forward_auth_settings", {
|
||||
forwardAuthSettingsId: text("forwardAuthSettingsId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
authDomain: text("authDomain").notNull(),
|
||||
baseDomain: text("baseDomain").notNull(),
|
||||
https: boolean("https").notNull().default(true),
|
||||
certificateType: certificateType("certificateType")
|
||||
.notNull()
|
||||
.default("letsencrypt"),
|
||||
customCertResolver: text("customCertResolver"),
|
||||
providerId: text("providerId").references(() => ssoProvider.providerId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
serverId: text("serverId")
|
||||
.unique()
|
||||
.references(() => server.serverId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
});
|
||||
|
||||
export const forwardAuthSettingsRelations = relations(
|
||||
forwardAuthSettings,
|
||||
({ one }) => ({
|
||||
server: one(server, {
|
||||
fields: [forwardAuthSettings.serverId],
|
||||
references: [server.serverId],
|
||||
}),
|
||||
provider: one(ssoProvider, {
|
||||
fields: [forwardAuthSettings.providerId],
|
||||
references: [ssoProvider.providerId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const domainRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/;
|
||||
|
||||
export const apiSetForwardAuthSettings = z.object({
|
||||
serverId: z.string().nullable(),
|
||||
authDomain: z
|
||||
.string()
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.refine((v) => domainRegex.test(v), { message: "Invalid auth domain" }),
|
||||
https: z.boolean().default(true),
|
||||
certificateType: z
|
||||
.enum(["none", "letsencrypt", "custom"])
|
||||
.default("letsencrypt"),
|
||||
customCertResolver: z.string().optional(),
|
||||
});
|
||||
@@ -10,6 +10,7 @@ export * from "./deployment";
|
||||
export * from "./destination";
|
||||
export * from "./domain";
|
||||
export * from "./environment";
|
||||
export * from "./forward-auth";
|
||||
export * from "./git-provider";
|
||||
export * from "./gitea";
|
||||
export * from "./github";
|
||||
|
||||
@@ -173,31 +173,29 @@ export const apiModifyTraefikConfig = z.object({
|
||||
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;
|
||||
}
|
||||
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",
|
||||
},
|
||||
),
|
||||
// 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(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user