import { db } from "@dokploy/server/db"; import { forwardAuthSettings } from "@dokploy/server/db/schema"; import type { Domain } from "@dokploy/server/services/domain"; import { FORWARD_AUTH_PORT, FORWARD_AUTH_SERVICE_NAME, } from "@dokploy/server/setup/forward-auth-setup"; import { eq, isNull } from "drizzle-orm"; import type { ApplicationNested } from "../builders"; import { removeTraefikConfig, removeTraefikConfigRemote, writeTraefikConfig, writeTraefikConfigRemote, } from "./application"; import type { FileConfig } from "./file-types"; import { loadMiddlewares, loadRemoteMiddlewares, writeMiddleware, } from "./middleware"; export interface AuthDomainConfig { authDomain: string; https: boolean; certificateType: "none" | "letsencrypt" | "custom"; customCertResolver?: string | null; } const TRAEFIK_SERVICE = "forward-auth-proxy"; export const forwardAuthMiddlewareName = ( appName: string, uniqueConfigKey: number, ): string => `forward-auth-${appName}-${uniqueConfigKey}`; const proxyUrl = () => `http://${FORWARD_AUTH_SERVICE_NAME}:${FORWARD_AUTH_PORT}`; const loadOrEmptyMiddlewares = async ( serverId: string | null, ): Promise => { try { return serverId ? await loadRemoteMiddlewares(serverId) : loadMiddlewares(); } catch { return { http: { middlewares: {} } }; } }; const persistMiddlewares = async ( config: FileConfig, serverId: string | null, ) => { if (serverId) { await writeTraefikConfigRemote(config, "middlewares", serverId); } else { writeMiddleware(config); } }; const loadAuthGateDomain = async (serverId: string | null) => { return db.query.forwardAuthSettings.findFirst({ where: serverId ? eq(forwardAuthSettings.serverId, serverId) : isNull(forwardAuthSettings.serverId), columns: { authDomain: true, https: true }, }); }; export const createForwardAuthMiddleware = async ( app: ApplicationNested, domain: Domain, ) => { if (!domain.forwardAuthEnabled) { return; } const authGate = await loadAuthGateDomain(app.serverId ?? null); if (!authGate) { return; } const authDomain = authGate.authDomain; const authDomainHttps = authGate.https; const { appName, serverId } = app; const config = await loadOrEmptyMiddlewares(serverId); config.http = config.http || {}; config.http.middlewares = config.http.middlewares || {}; const name = forwardAuthMiddlewareName(appName, domain.uniqueConfigKey); const scheme = authDomainHttps ? "https" : "http"; config.http.middlewares[name] = { forwardAuth: { address: `${scheme}://${authDomain}/oauth2/auth`, trustForwardHeader: true, authResponseHeaders: [ "X-Auth-Request-User", "X-Auth-Request-Email", "X-Auth-Request-Preferred-Username", "Authorization", ], }, }; config.http.middlewares[`${name}-errors`] = { errors: { status: ["401-403"], service: TRAEFIK_SERVICE, query: "/oauth2/sign_in?rd={url}", statusRewrites: { "401": 302 }, }, }; await persistMiddlewares(config, serverId); }; export const removeForwardAuthMiddleware = async ( app: ApplicationNested, uniqueConfigKey: number, ) => { const { appName, serverId } = app; let config: FileConfig; try { config = serverId ? await loadRemoteMiddlewares(serverId) : loadMiddlewares(); } catch { return; } const name = forwardAuthMiddlewareName(appName, uniqueConfigKey); let changed = false; for (const key of [name, `${name}-errors`]) { if (config.http?.middlewares?.[key]) { delete config.http.middlewares[key]; changed = true; } } if (changed) { await persistMiddlewares(config, serverId); } }; export const buildAuthDomainRouter = (cfg: AuthDomainConfig): FileConfig => { const entry = cfg.https ? "websecure" : "web"; const oauthRouter: NonNullable< NonNullable["routers"] >[string] = { rule: `Host(\`${cfg.authDomain}\`) && PathPrefix(\`/oauth2/\`)`, service: TRAEFIK_SERVICE, entryPoints: [entry], priority: 1000, }; if (cfg.https) { if (cfg.certificateType === "letsencrypt") { oauthRouter.tls = { certResolver: "letsencrypt" }; } else if (cfg.certificateType === "custom" && cfg.customCertResolver) { oauthRouter.tls = { certResolver: cfg.customCertResolver }; } else { oauthRouter.tls = {}; } } return { http: { routers: { "forward-auth-oauth": oauthRouter }, services: { [TRAEFIK_SERVICE]: { loadBalancer: { servers: [{ url: proxyUrl() }], passHostHeader: true, }, }, }, }, }; }; export const authDomainConfigName = "forward-auth-domain"; export const manageForwardAuthDomain = async ( serverId: string | null, cfg: AuthDomainConfig, ) => { const config = buildAuthDomainRouter(cfg); if (serverId) { await writeTraefikConfigRemote(config, authDomainConfigName, serverId); } else { writeTraefikConfig(config, authDomainConfigName); } }; export const removeForwardAuthDomain = async (serverId: string | null) => { if (serverId) { await removeTraefikConfigRemote(authDomainConfigName, serverId); } else { await removeTraefikConfig(authDomainConfigName); } };