From b3621bcfff5fb3613fc65ee89f5f1514962c9f72 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 9 Jul 2026 01:46:32 -0600 Subject: [PATCH] fix(sso): apply trusted origin changes without server restart --- .../server/api/routers/proprietary/sso.ts | 4 ++ packages/server/src/lib/auth.ts | 68 +++++++++++-------- packages/server/src/services/admin.ts | 4 ++ 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/apps/dokploy/server/api/routers/proprietary/sso.ts b/apps/dokploy/server/api/routers/proprietary/sso.ts index 84a79223d..0581ae4db 100644 --- a/apps/dokploy/server/api/routers/proprietary/sso.ts +++ b/apps/dokploy/server/api/routers/proprietary/sso.ts @@ -8,6 +8,7 @@ import { requestToHeaders, } from "@dokploy/server/index"; import { auth } from "@dokploy/server/lib/auth"; +import { invalidateTrustedOriginsCache } from "@dokploy/server/services/admin"; import { getWebServerSettings } from "@dokploy/server/services/web-server-settings"; import { TRPCError } from "@trpc/server"; import { and, asc, eq } from "drizzle-orm"; @@ -319,6 +320,7 @@ export const ssoRouter = createTRPCRouter({ .update(user) .set({ trustedOrigins: next }) .where(eq(user.id, ownerId)); + invalidateTrustedOriginsCache(); return { success: true }; }), removeTrustedOrigin: enterpriseProcedure @@ -346,6 +348,7 @@ export const ssoRouter = createTRPCRouter({ .update(user) .set({ trustedOrigins: next }) .where(eq(user.id, ownerId)); + invalidateTrustedOriginsCache(); return { success: true }; }), updateTrustedOrigin: enterpriseProcedure @@ -379,6 +382,7 @@ export const ssoRouter = createTRPCRouter({ .update(user) .set({ trustedOrigins: next }) .where(eq(user.id, ownerId)); + invalidateTrustedOriginsCache(); return { success: true }; }), }); diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index c8dbf1807..11cf8ffc7 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -4,7 +4,7 @@ import { sso } from "@better-auth/sso"; import * as bcrypt from "bcrypt"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { APIError } from "better-auth/api"; +import { APIError, createAuthMiddleware } from "better-auth/api"; import { admin, organization, twoFactor } from "better-auth/plugins"; import { and, desc, eq } from "drizzle-orm"; import { IS_CLOUD } from "../constants"; @@ -29,6 +29,37 @@ import { getPublicIpWithFallback } from "../wss/utils"; import { ac, adminRole, memberRole, ownerRole } from "./access-control"; import { betterAuthSecret } from "./auth-secret"; +const resolveTrustedOrigins = async () => { + try { + if (IS_CLOUD) { + return await getTrustedOrigins(); + } + const [trustedOrigins, settings] = await Promise.all([ + getTrustedOrigins(), + getWebServerSettings(), + ]); + + if (!settings) return []; + + const devOrigins = + process.env.NODE_ENV === "development" + ? [ + "http://localhost:3000", + "https://absolutely-handy-falcon.ngrok-free.app", + ] + : []; + return [ + ...(settings?.serverIp ? [`http://${settings?.serverIp}:3000`] : []), + ...(settings?.host ? [`https://${settings?.host}`] : []), + ...devOrigins, + ...trustedOrigins, + ]; + } catch (error) { + console.error("Failed to resolve trusted origins:", error); + return []; + } +}; + const { handler, api } = betterAuth({ database: drizzleAdapter(db, { provider: "pg", @@ -80,33 +111,14 @@ const { handler, api } = betterAuth({ logger: { disabled: process.env.NODE_ENV === "production", }, - async trustedOrigins() { - try { - if (IS_CLOUD) { - return await getTrustedOrigins(); - } - const [trustedOrigins, settings] = await Promise.all([ - getTrustedOrigins(), - getWebServerSettings(), - ]); - if (!settings) return []; - const devOrigins = - process.env.NODE_ENV === "development" - ? [ - "http://localhost:3000", - "https://absolutely-handy-falcon.ngrok-free.app", - ] - : []; - return [ - ...(settings?.serverIp ? [`http://${settings?.serverIp}:3000`] : []), - ...(settings?.host ? [`https://${settings?.host}`] : []), - ...devOrigins, - ...trustedOrigins, - ]; - } catch (error) { - console.error("Failed to resolve trusted origins:", error); - return []; - } + trustedOrigins: resolveTrustedOrigins, + hooks: { + before: createAuthMiddleware(async (ctx) => { + ctx.context.trustedOrigins = [ + ...(ctx.context.baseURL ? [new URL(ctx.context.baseURL).origin] : []), + ...(await resolveTrustedOrigins()), + ].filter(Boolean); + }), }, emailVerification: { sendOnSignUp: true, diff --git a/packages/server/src/services/admin.ts b/packages/server/src/services/admin.ts index f0c8cb0eb..bf1135d86 100644 --- a/packages/server/src/services/admin.ts +++ b/packages/server/src/services/admin.ts @@ -120,6 +120,10 @@ export const getDokployUrl = async () => { const TRUSTED_ORIGINS_CACHE_TTL_MS = 30 * 60_000; let trustedOriginsCache: { data: string[]; expiresAt: number } | null = null; +export const invalidateTrustedOriginsCache = () => { + trustedOriginsCache = null; +}; + export const getTrustedOrigins = async () => { const runQuery = async () => { const rows = await db