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:
Mauricio Siu
2026-06-02 01:47:50 -06:00
parent 6ff2ca0173
commit 41c09cd86b
28 changed files with 27769 additions and 25 deletions

View File

@@ -30,6 +30,7 @@ import { previewDeploymentRouter } from "./routers/preview-deployment";
import { projectRouter } from "./routers/project";
import { auditLogRouter } from "./routers/proprietary/audit-log";
import { customRoleRouter } from "./routers/proprietary/custom-role";
import { forwardAuthRouter } from "./routers/proprietary/forward-auth";
import { licenseKeyRouter } from "./routers/proprietary/license-key";
import { ssoRouter } from "./routers/proprietary/sso";
import { whitelabelingRouter } from "./routers/proprietary/whitelabeling";
@@ -93,6 +94,7 @@ export const appRouter = createTRPCRouter({
organization: organizationRouter,
licenseKey: licenseKeyRouter,
sso: ssoRouter,
forwardAuth: forwardAuthRouter,
whitelabeling: whitelabelingRouter,
customRole: customRoleRouter,
auditLog: auditLogRouter,

View File

@@ -0,0 +1,172 @@
import {
assertApplicationDomainAccess,
deployForwardAuthOnServer,
disableForwardAuthOnDomain,
enableForwardAuthOnDomain,
findServerById,
forwardAuthCallbackUrl,
getDomainSsoStatus,
getForwardAuthServerStatus,
getForwardAuthSettings,
listSsoProvidersForOrg,
removeForwardAuthProxy,
removeForwardAuthSettings,
setForwardAuthSettings,
} from "@dokploy/server";
import { apiSetForwardAuthSettings } from "@dokploy/server/db/schema";
import { z } from "zod";
import { createTRPCRouter, enterpriseProcedure } from "@/server/api/trpc";
import { audit } from "@/server/api/utils/audit";
export const forwardAuthRouter = createTRPCRouter({
getAuthDomain: enterpriseProcedure
.input(z.object({ serverId: z.string().nullable() }))
.query(async ({ input }) => {
const settings = await getForwardAuthSettings(input.serverId);
if (!settings) return null;
return {
host: settings.authDomain,
https: settings.https,
certificateType: settings.certificateType,
customCertResolver: settings.customCertResolver,
callbackUrl: forwardAuthCallbackUrl(
settings.authDomain,
settings.https,
),
};
}),
setAuthDomain: enterpriseProcedure
.input(apiSetForwardAuthSettings)
.mutation(async ({ ctx, input }) => {
if (input.serverId) await findServerById(input.serverId);
const result = await setForwardAuthSettings({
organizationId: ctx.session.activeOrganizationId,
serverId: input.serverId,
authDomain: input.authDomain,
https: input.https,
certificateType: input.certificateType,
customCertResolver: input.customCertResolver,
});
await audit(ctx, {
action: "update",
resourceType: "server",
resourceId: input.serverId ?? "local",
resourceName: "forward-auth-domain",
});
return result;
}),
removeAuthDomain: enterpriseProcedure
.input(z.object({ serverId: z.string().nullable() }))
.mutation(async ({ ctx, input }) => {
if (input.serverId) await findServerById(input.serverId);
const result = await removeForwardAuthSettings(input.serverId);
await audit(ctx, {
action: "delete",
resourceType: "server",
resourceId: input.serverId ?? "local",
resourceName: "forward-auth-domain",
});
return result;
}),
listProviders: enterpriseProcedure.query(({ ctx }) =>
listSsoProvidersForOrg(
ctx.session.activeOrganizationId,
ctx.session.userId,
),
),
serverStatus: enterpriseProcedure.query(({ ctx }) =>
getForwardAuthServerStatus(ctx.session.activeOrganizationId),
),
deployOnServer: enterpriseProcedure
.input(
z.object({
serverId: z.string().nullable(),
providerId: z.string().min(1),
}),
)
.mutation(async ({ ctx, input }) => {
if (input.serverId) await findServerById(input.serverId);
const result = await deployForwardAuthOnServer({
serverId: input.serverId ?? undefined,
providerId: input.providerId,
organizationId: ctx.session.activeOrganizationId,
});
await audit(ctx, {
action: "create",
resourceType: "server",
resourceId: input.serverId ?? "local",
resourceName: "forward-auth",
});
return result;
}),
removeOnServer: enterpriseProcedure
.input(z.object({ serverId: z.string().nullable() }))
.mutation(async ({ ctx, input }) => {
if (input.serverId) await findServerById(input.serverId);
const result = await removeForwardAuthProxy(input.serverId);
await audit(ctx, {
action: "delete",
resourceType: "server",
resourceId: input.serverId ?? "local",
resourceName: "forward-auth",
});
return result;
}),
status: enterpriseProcedure
.input(z.object({ domainId: z.string().min(1) }))
.query(({ ctx, input }) => getDomainSsoStatus(ctx, input.domainId)),
enable: enterpriseProcedure
.input(
z.object({
domainId: z.string().min(1),
providerId: z.string().min(1),
}),
)
.mutation(async ({ ctx, input }) => {
const domain = await assertApplicationDomainAccess(
ctx,
input.domainId,
"create",
);
const result = await enableForwardAuthOnDomain({
domainId: input.domainId,
providerId: input.providerId,
organizationId: ctx.session.activeOrganizationId,
});
await audit(ctx, {
action: "update",
resourceType: "domain",
resourceId: domain.domainId,
resourceName: domain.host,
});
return result;
}),
disable: enterpriseProcedure
.input(z.object({ domainId: z.string().min(1) }))
.mutation(async ({ ctx, input }) => {
const domain = await assertApplicationDomainAccess(
ctx,
input.domainId,
"create",
);
const result = await disableForwardAuthOnDomain({
domainId: input.domainId,
});
await audit(ctx, {
action: "update",
resourceType: "domain",
resourceId: domain.domainId,
resourceName: domain.host,
});
return result;
}),
});