diff --git a/apps/dokploy/components/proprietary/sso/sso-settings.tsx b/apps/dokploy/components/proprietary/sso/sso-settings.tsx index bb8330cda..dee41b13c 100644 --- a/apps/dokploy/components/proprietary/sso/sso-settings.tsx +++ b/apps/dokploy/components/proprietary/sso/sso-settings.tsx @@ -84,9 +84,10 @@ export const SSOSettings = () => { const [newOriginInput, setNewOriginInput] = useState(""); const { data: providers, isLoading } = api.sso.listProviders.useQuery(); - const { data: userData } = api.user.get.useQuery(undefined, { - enabled: manageOriginsOpen, - }); + const { data: trustedOrigins = [] } = api.sso.getTrustedOrigins.useQuery( + undefined, + { enabled: manageOriginsOpen }, + ); const { mutateAsync: deleteProvider, isLoading: isDeleting } = api.sso.deleteProvider.useMutation(); const { mutateAsync: addTrustedOrigin, isLoading: isAddingOrigin } = @@ -96,8 +97,6 @@ export const SSOSettings = () => { const { mutateAsync: updateTrustedOrigin, isLoading: isUpdatingOrigin } = api.sso.updateTrustedOrigin.useMutation(); - const trustedOrigins = userData?.user?.trustedOrigins ?? []; - const handleAddOrigin = async () => { const value = newOriginInput.trim(); if (!value) return; @@ -105,7 +104,7 @@ export const SSOSettings = () => { await addTrustedOrigin({ origin: value }); toast.success("Trusted origin added"); setNewOriginInput(""); - await utils.user.get.invalidate(); + await utils.sso.getTrustedOrigins.invalidate(); } catch (err) { toast.error( err instanceof Error ? err.message : "Failed to add trusted origin", @@ -118,7 +117,7 @@ export const SSOSettings = () => { await removeTrustedOrigin({ origin }); toast.success("Trusted origin removed"); if (editingOrigin === origin) setEditingOrigin(null); - await utils.user.get.invalidate(); + await utils.sso.getTrustedOrigins.invalidate(); } catch (err) { toast.error( err instanceof Error ? err.message : "Failed to remove trusted origin", @@ -144,7 +143,7 @@ export const SSOSettings = () => { toast.success("Trusted origin updated"); setEditingOrigin(null); setEditingValue(""); - await utils.user.get.invalidate(); + await utils.sso.getTrustedOrigins.invalidate(); } catch (err) { toast.error( err instanceof Error ? err.message : "Failed to update trusted origin", diff --git a/apps/dokploy/pages/index.tsx b/apps/dokploy/pages/index.tsx index a88a5dc8e..d2ea83297 100644 --- a/apps/dokploy/pages/index.tsx +++ b/apps/dokploy/pages/index.tsx @@ -105,7 +105,6 @@ export default function Home({ IS_CLOUD }: Props) { setIsLoginLoading(false); } }; - const onTwoFactorSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (twoFactorCode.length !== 6) { diff --git a/apps/dokploy/server/api/routers/proprietary/license-key.ts b/apps/dokploy/server/api/routers/proprietary/license-key.ts index ec7ad55c8..d6a770be9 100644 --- a/apps/dokploy/server/api/routers/proprietary/license-key.ts +++ b/apps/dokploy/server/api/routers/proprietary/license-key.ts @@ -1,5 +1,5 @@ import { user } from "@dokploy/server/db/schema"; -import { validateLicenseKey } from "@dokploy/server/index"; +import { hasValidLicense, validateLicenseKey } from "@dokploy/server/index"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -184,18 +184,7 @@ export const licenseKeyRouter = createTRPCRouter({ }; }), haveValidLicenseKey: adminProcedure.query(async ({ ctx }) => { - const currentUserId = ctx.user.id; - const currentUser = await db.query.user.findFirst({ - where: eq(user.id, currentUserId), - columns: { - enableEnterpriseFeatures: true, - isValidEnterpriseLicense: true, - }, - }); - return !!( - currentUser?.enableEnterpriseFeatures && - currentUser?.isValidEnterpriseLicense - ); + return await hasValidLicense(ctx.session.activeOrganizationId); }), updateEnterpriseSettings: adminProcedure .input( diff --git a/apps/dokploy/server/api/routers/proprietary/sso.ts b/apps/dokploy/server/api/routers/proprietary/sso.ts index d59b2c974..c8b42f7fb 100644 --- a/apps/dokploy/server/api/routers/proprietary/sso.ts +++ b/apps/dokploy/server/api/routers/proprietary/sso.ts @@ -2,7 +2,10 @@ import { normalizeTrustedOrigin } from "@dokploy/server"; import { IS_CLOUD } from "@dokploy/server/constants"; import { member, ssoProvider, user } from "@dokploy/server/db/schema"; import { ssoProviderBodySchema } from "@dokploy/server/db/schema/sso"; -import { requestToHeaders } from "@dokploy/server/index"; +import { + getOrganizationOwnerId, + requestToHeaders, +} from "@dokploy/server/index"; import { auth } from "@dokploy/server/lib/auth"; import { TRPCError } from "@trpc/server"; import { and, asc, eq } from "drizzle-orm"; @@ -59,6 +62,17 @@ export const ssoRouter = createTRPCRouter({ }); return providers; }), + getTrustedOrigins: enterpriseProcedure.query(async ({ ctx }) => { + const ownerId = await getOrganizationOwnerId( + ctx.session.activeOrganizationId, + ); + if (!ownerId) return []; + const ownerUser = await db.query.user.findFirst({ + where: eq(user.id, ownerId), + columns: { trustedOrigins: true }, + }); + return ownerUser?.trustedOrigins ?? []; + }), one: enterpriseProcedure .input(z.object({ providerId: z.string().min(1) })) .query(async ({ ctx, input }) => { @@ -135,11 +149,20 @@ export const ssoRouter = createTRPCRouter({ normalizeTrustedOrigin(existing.issuer) !== normalizeTrustedOrigin(input.issuer); if (issuerChanged) { - const currentUser = await db.query.user.findFirst({ - where: eq(user.id, ctx.session.userId), + const ownerId = await getOrganizationOwnerId( + ctx.session.activeOrganizationId, + ); + if (!ownerId) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Organization owner not found", + }); + } + const ownerUser = await db.query.user.findFirst({ + where: eq(user.id, ownerId), columns: { trustedOrigins: true }, }); - const trustedOrigins = currentUser?.trustedOrigins ?? []; + const trustedOrigins = ownerUser?.trustedOrigins ?? []; const newOrigin = normalizeTrustedOrigin(input.issuer); const isInTrustedOrigins = trustedOrigins.some( (o) => o.toLowerCase() === newOrigin.toLowerCase(), @@ -148,7 +171,7 @@ export const ssoRouter = createTRPCRouter({ throw new TRPCError({ code: "BAD_REQUEST", message: - "The new Issuer URL is not in your trusted origins list. Please add it in Manage origins before saving.", + "The new Issuer URL is not in the organization's trusted origins list. Please add it in Manage origins before saving.", }); } } @@ -262,12 +285,21 @@ export const ssoRouter = createTRPCRouter({ addTrustedOrigin: enterpriseProcedure .input(z.object({ origin: z.string().min(1) })) .mutation(async ({ ctx, input }) => { + const ownerId = await getOrganizationOwnerId( + ctx.session.activeOrganizationId, + ); + if (!ownerId) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Organization owner not found", + }); + } const normalized = normalizeTrustedOrigin(input.origin); - const currentUser = await db.query.user.findFirst({ - where: eq(user.id, ctx.session.userId), + const ownerUser = await db.query.user.findFirst({ + where: eq(user.id, ownerId), columns: { trustedOrigins: true }, }); - const existing = currentUser?.trustedOrigins || []; + const existing = ownerUser?.trustedOrigins || []; if (existing.some((o) => o.toLowerCase() === normalized.toLowerCase())) { return { success: true }; } @@ -275,25 +307,34 @@ export const ssoRouter = createTRPCRouter({ await db .update(user) .set({ trustedOrigins: next }) - .where(eq(user.id, ctx.session.userId)); + .where(eq(user.id, ownerId)); return { success: true }; }), removeTrustedOrigin: enterpriseProcedure .input(z.object({ origin: z.string().min(1) })) .mutation(async ({ ctx, input }) => { + const ownerId = await getOrganizationOwnerId( + ctx.session.activeOrganizationId, + ); + if (!ownerId) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Organization owner not found", + }); + } const normalized = normalizeTrustedOrigin(input.origin); - const currentUser = await db.query.user.findFirst({ - where: eq(user.id, ctx.session.userId), + const ownerUser = await db.query.user.findFirst({ + where: eq(user.id, ownerId), columns: { trustedOrigins: true }, }); - const existing = currentUser?.trustedOrigins || []; + const existing = ownerUser?.trustedOrigins || []; const next = existing.filter( (o) => o.toLowerCase() !== normalized.toLowerCase(), ); await db .update(user) .set({ trustedOrigins: next }) - .where(eq(user.id, ctx.session.userId)); + .where(eq(user.id, ownerId)); return { success: true }; }), updateTrustedOrigin: enterpriseProcedure @@ -304,20 +345,29 @@ export const ssoRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { + const ownerId = await getOrganizationOwnerId( + ctx.session.activeOrganizationId, + ); + if (!ownerId) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Organization owner not found", + }); + } const oldNorm = normalizeTrustedOrigin(input.oldOrigin); const newNorm = normalizeTrustedOrigin(input.newOrigin); - const currentUser = await db.query.user.findFirst({ - where: eq(user.id, ctx.session.userId), + const ownerUser = await db.query.user.findFirst({ + where: eq(user.id, ownerId), columns: { trustedOrigins: true }, }); - const existing = currentUser?.trustedOrigins || []; + const existing = ownerUser?.trustedOrigins || []; const next = existing.map((o) => o.toLowerCase() === oldNorm.toLowerCase() ? newNorm : o, ); await db .update(user) .set({ trustedOrigins: next }) - .where(eq(user.id, ctx.session.userId)); + .where(eq(user.id, ownerId)); return { success: true }; }), }); diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts index 51f8cdbee..084d529c7 100644 --- a/apps/dokploy/server/api/trpc.ts +++ b/apps/dokploy/server/api/trpc.ts @@ -7,6 +7,7 @@ * need to use are documented accordingly near the end. */ +import { hasValidLicense } from "@dokploy/server/index"; import { validateRequest } from "@dokploy/server/lib/auth"; import type { OpenApiMeta } from "@dokploy/trpc-openapi"; import { initTRPC, TRPCError } from "@trpc/server"; @@ -239,10 +240,11 @@ export const enterpriseProcedure = t.procedure.use(async ({ ctx, next }) => { throw new TRPCError({ code: "UNAUTHORIZED" }); } - if ( - !ctx.user?.enableEnterpriseFeatures || - !ctx.user.isValidEnterpriseLicense - ) { + const hasValidLicenseResult = await hasValidLicense( + ctx.session.activeOrganizationId, + ); + + if (!hasValidLicenseResult) { throw new TRPCError({ code: "FORBIDDEN", message: "Valid enterprise license required", diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 7bfc5553b..c49a3005a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -31,6 +31,7 @@ export * from "./services/port"; export * from "./services/postgres"; export * from "./services/preview-deployment"; export * from "./services/project"; +export * from "./services/proprietary/license-key"; export * from "./services/proprietary/sso"; export * from "./services/redirect"; export * from "./services/redis"; diff --git a/packages/server/src/services/proprietary/license-key.ts b/packages/server/src/services/proprietary/license-key.ts new file mode 100644 index 000000000..69976ca8c --- /dev/null +++ b/packages/server/src/services/proprietary/license-key.ts @@ -0,0 +1,24 @@ +import { db } from "@dokploy/server/db"; +import { user } from "@dokploy/server/db/schema"; +import { eq } from "drizzle-orm"; +import { getOrganizationOwnerId } from "./sso"; + +export const hasValidLicense = async (organizationId: string) => { + const ownerId = await getOrganizationOwnerId(organizationId); + + if (!ownerId) { + return false; + } + + const currentUser = await db.query.user.findFirst({ + where: eq(user.id, ownerId), + columns: { + enableEnterpriseFeatures: true, + isValidEnterpriseLicense: true, + }, + }); + return !!( + currentUser?.enableEnterpriseFeatures && + currentUser?.isValidEnterpriseLicense + ); +}; diff --git a/packages/server/src/services/proprietary/sso.ts b/packages/server/src/services/proprietary/sso.ts index 85caf600c..352a042e0 100644 --- a/packages/server/src/services/proprietary/sso.ts +++ b/packages/server/src/services/proprietary/sso.ts @@ -1,4 +1,6 @@ import { db } from "@dokploy/server/db"; +import { organization } from "@dokploy/server/db/schema"; +import { eq } from "drizzle-orm"; export const getSSOProviders = async () => { const providers = await db.query.ssoProvider.findMany({ @@ -33,3 +35,12 @@ export const normalizeTrustedOrigin = (value: string): string => { // e.g. "https://example.com/" -> "https://example.com" return value.trim().replace(/\/+$/, ""); }; + +export const getOrganizationOwnerId = async (organizationId: string) => { + const org = await db.query.organization.findFirst({ + where: eq(organization.id, organizationId), + columns: { ownerId: true }, + }); + if (!org) return null; + return org.ownerId; +};