mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-27 01:55:24 +02:00
- Replaced `forwardAuthProviderId` with `forwardAuthEnabled` in the domain schema to simplify the configuration of forward authentication. - Updated related tests to reflect this change, ensuring consistency across the application. - Introduced a new SQL migration to create the `forward_auth_settings` table for managing authentication domains and their configurations. This refactor enhances the clarity and maintainability of the forward authentication logic within the application.
205 lines
5.0 KiB
TypeScript
205 lines
5.0 KiB
TypeScript
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<FileConfig> => {
|
|
try {
|
|
return serverId
|
|
? await loadRemoteMiddlewares(serverId)
|
|
: loadMiddlewares<FileConfig>();
|
|
} 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<FileConfig>();
|
|
} 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<FileConfig["http"]>["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);
|
|
}
|
|
};
|