158 lines · 4.5 KB · TypeScript
RawDownload
import { createHmac } from "node:crypto";import type { CreateServiceOptions } from "dockerode";import { betterAuthSecret } from "../lib/auth-secret";import { getRemoteDocker } from "../utils/servers/remote-docker"; export const FORWARD_AUTH_SERVICE_NAME = "dokploy-forward-auth";const FORWARD_AUTH_IMAGE = "quay.io/oauth2-proxy/oauth2-proxy:v7.6.0"; export const FORWARD_AUTH_PORT = 4180; export interface ForwardAuthOidcConfig {	clientId: string;	clientSecret: string;	issuer: string;	scopes?: string[];	skipDiscovery?: boolean;} export interface SetupForwardAuthOptions {	serverId?: string;	oidc: ForwardAuthOidcConfig;	cookieSecret: string;	authDomain: string;	baseDomain: string;	authDomainHttps?: boolean;	emailDomains?: string[];} export const deriveBaseDomain = (authDomain: string): string => {	const labels = authDomain.trim().toLowerCase().split(".").filter(Boolean);	const base = labels.length > 2 ? labels.slice(1) : labels;	return `.${base.join(".")}`;}; export const forwardAuthCallbackUrl = (	authDomain: string,	https: boolean,): string => `${https ? "https" : "http"}://${authDomain}/oauth2/callback`; export const deriveCookieSecret = (salt: string): string => {	// oauth2-proxy requires cookie_secret to be exactly 16, 24, or 32 bytes.	// Take the first 32 hex chars (= 16 bytes) to satisfy that constraint.	return createHmac("sha256", betterAuthSecret)		.update(`forward-auth:${salt}`)		.digest("hex")		.slice(0, 32);}; export const buildForwardAuthEnv = (	options: SetupForwardAuthOptions,): string[] => {	const { oidc, cookieSecret, authDomain, baseDomain, authDomainHttps } =		options;	const scheme = authDomainHttps ? "https" : "http";	const emailDomains =		options.emailDomains && options.emailDomains.length > 0			? options.emailDomains			: ["*"]; 	const env: string[] = [		"OAUTH2_PROXY_PROVIDER=oidc",		`OAUTH2_PROXY_OIDC_ISSUER_URL=${oidc.issuer}`,		`OAUTH2_PROXY_CLIENT_ID=${oidc.clientId}`,		`OAUTH2_PROXY_CLIENT_SECRET=${oidc.clientSecret}`,		`OAUTH2_PROXY_COOKIE_SECRET=${cookieSecret}`,		`OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:${FORWARD_AUTH_PORT}`,		"OAUTH2_PROXY_REVERSE_PROXY=true",		"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true",		"OAUTH2_PROXY_SET_XAUTHREQUEST=true",		"OAUTH2_PROXY_UPSTREAMS=static://202",		`OAUTH2_PROXY_REDIRECT_URL=${scheme}://${authDomain}/oauth2/callback`,		`OAUTH2_PROXY_COOKIE_DOMAINS=${baseDomain}`,		`OAUTH2_PROXY_WHITELIST_DOMAINS=${baseDomain}`,		`OAUTH2_PROXY_COOKIE_SECURE=${authDomainHttps ? "true" : "false"}`,		"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL=true",		`OAUTH2_PROXY_EMAIL_DOMAINS=${emailDomains.join(",")}`,	]; 	const scopes = oidc.scopes?.length		? oidc.scopes		: ["openid", "email", "profile"];	env.push(`OAUTH2_PROXY_SCOPE=${scopes.join(" ")}`); 	if (oidc.skipDiscovery) {		env.push("OAUTH2_PROXY_SKIP_OIDC_DISCOVERY=true");	} 	return env;}; export const setupForwardAuth = async (options: SetupForwardAuthOptions) => {	const { serverId } = options;	const docker = await getRemoteDocker(serverId); 	const settings: CreateServiceOptions = {		Name: FORWARD_AUTH_SERVICE_NAME,		TaskTemplate: {			ContainerSpec: {				Image: FORWARD_AUTH_IMAGE,				Env: buildForwardAuthEnv(options),			},			Networks: [{ Target: "dokploy-network" }],			Placement: {				Constraints: ["node.role==manager"],			},		},		Mode: {			Replicated: {				Replicas: 1,			},		},	}; 	try {		const service = docker.getService(FORWARD_AUTH_SERVICE_NAME);		const inspect = await service.inspect();		await service.update({			version: Number.parseInt(inspect.Version.Index),			...settings,			TaskTemplate: {				...settings.TaskTemplate,				ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,			},		});		console.log("Forward Auth Updated ✅");	} catch (_) {		try {			await docker.createService(settings);			console.log("Forward Auth Started ✅");		} catch (error: any) {			if (error?.statusCode !== 409) {				throw error;			}			console.log("Forward Auth service already exists, continuing...");		}	}}; export const removeForwardAuth = async (serverId?: string) => {	const docker = await getRemoteDocker(serverId);	try {		const service = docker.getService(FORWARD_AUTH_SERVICE_NAME);		await service.remove();		console.log("Forward Auth Removed ✅");	} catch {}}; export const isForwardAuthRunning = async (	serverId?: string,): Promise<boolean> => {	const docker = await getRemoteDocker(serverId);	try {		await docker.getService(FORWARD_AUTH_SERVICE_NAME).inspect();		return true;	} catch {		return false;	}};