diff --git a/apps/dokploy/components/proprietary/sso/register-oidc-dialog.tsx b/apps/dokploy/components/proprietary/sso/register-oidc-dialog.tsx index d0f44429c..2ed3199f8 100644 --- a/apps/dokploy/components/proprietary/sso/register-oidc-dialog.tsx +++ b/apps/dokploy/components/proprietary/sso/register-oidc-dialog.tsx @@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Plus, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; import type { FieldArrayPath } from "react-hook-form"; -import { useFieldArray, useForm } from "react-hook-form"; +import { useFieldArray, useForm, useWatch } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { Button } from "@/components/ui/button"; @@ -28,6 +28,7 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; +import { useUrl } from "@/utils/hooks/use-url"; const DEFAULT_SCOPES = ["openid", "email", "profile"]; @@ -120,6 +121,14 @@ export function RegisterOidcDialog({ defaultValues: formDefaultValues, }); + const watchedProviderId = useWatch({ + control: form.control, + name: "providerId", + defaultValue: "", + }); + + const baseURL = useUrl(); + useEffect(() => { if (!data || !open) return; const domains = data.domain @@ -242,6 +251,17 @@ export function RegisterOidcDialog({ Unique identifier; used in callback URL path. {isEdit && " Cannot be changed when editing."} + {baseURL && ( +
+

+ Callback URL (configure in your IdP) +

+

+ {baseURL}/api/auth/sso/callback/ + {watchedProviderId?.trim() || "..."} +

+
+ )} )} diff --git a/apps/dokploy/components/proprietary/sso/register-saml-dialog.tsx b/apps/dokploy/components/proprietary/sso/register-saml-dialog.tsx index 521344882..bd18e878e 100644 --- a/apps/dokploy/components/proprietary/sso/register-saml-dialog.tsx +++ b/apps/dokploy/components/proprietary/sso/register-saml-dialog.tsx @@ -3,7 +3,12 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Plus, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; -import { type FieldArrayPath, useFieldArray, useForm } from "react-hook-form"; +import { + type FieldArrayPath, + useFieldArray, + useForm, + useWatch, +} from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { Button } from "@/components/ui/button"; @@ -28,6 +33,7 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; +import { useUrl } from "@/utils/hooks/use-url"; const domainsArraySchema = z .array(z.string().trim()) @@ -115,13 +121,7 @@ export function RegisterSamlDialog({ ? updateMutation.isLoading : registerMutation.isLoading; - const [baseURL, setBaseURL] = useState(""); - - useEffect(() => { - if (typeof window !== "undefined") { - setBaseURL(window.location.origin); - } - }, []); + const baseURL = useUrl(); const form = useForm({ resolver: zodResolver(samlProviderSchema), @@ -148,6 +148,12 @@ export function RegisterSamlDialog({ }); }, [data, open, form]); + const watchedProviderId = useWatch({ + control: form.control, + name: "providerId", + defaultValue: "", + }); + const { fields, append, remove } = useFieldArray({ control: form.control, name: "domains" as FieldArrayPath, @@ -243,6 +249,17 @@ export function RegisterSamlDialog({ Cannot be changed when editing. )} + {baseURL && ( +
+

+ Callback URL (configure in your IdP) +

+

+ {baseURL}/api/auth/sso/saml2/callback/ + {watchedProviderId?.trim() || "..."} +

+
+ )} )} diff --git a/apps/dokploy/components/proprietary/sso/sso-settings.tsx b/apps/dokploy/components/proprietary/sso/sso-settings.tsx index d5fbe88a6..bb8330cda 100644 --- a/apps/dokploy/components/proprietary/sso/sso-settings.tsx +++ b/apps/dokploy/components/proprietary/sso/sso-settings.tsx @@ -9,7 +9,7 @@ import { Shield, Trash2, } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { toast } from "sonner"; import { DialogAction } from "@/components/shared/dialog-action"; import { Badge } from "@/components/ui/badge"; @@ -31,6 +31,7 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; +import { useUrl } from "@/utils/hooks/use-url"; import { RegisterOidcDialog } from "./register-oidc-dialog"; import { RegisterSamlDialog } from "./register-saml-dialog"; @@ -76,18 +77,12 @@ export const SSOSettings = () => { const utils = api.useUtils(); const [detailsProvider, setDetailsProvider] = useState(null); - const [baseURL, setBaseURL] = useState(""); + const baseURL = useUrl(); const [manageOriginsOpen, setManageOriginsOpen] = useState(false); const [editingOrigin, setEditingOrigin] = useState(null); const [editingValue, setEditingValue] = useState(""); const [newOriginInput, setNewOriginInput] = useState(""); - useEffect(() => { - if (typeof window !== "undefined") { - setBaseURL(window.location.origin); - } - }, []); - const { data: providers, isLoading } = api.sso.listProviders.useQuery(); const { data: userData } = api.user.get.useQuery(undefined, { enabled: manageOriginsOpen, diff --git a/apps/dokploy/server/api/routers/proprietary/sso.ts b/apps/dokploy/server/api/routers/proprietary/sso.ts index 59691c20d..d59b2c974 100644 --- a/apps/dokploy/server/api/routers/proprietary/sso.ts +++ b/apps/dokploy/server/api/routers/proprietary/sso.ts @@ -154,33 +154,27 @@ export const ssoRouter = createTRPCRouter({ } const domain = input.domains.join(","); - - const updatePayload: { + const updateBody: { issuer: string; domain: string; - oidcConfig?: string; - samlConfig?: string; + oidcConfig?: (typeof input)["oidcConfig"]; + samlConfig?: (typeof input)["samlConfig"]; } = { issuer: input.issuer, domain, }; if (input.oidcConfig != null) { - updatePayload.oidcConfig = JSON.stringify(input.oidcConfig); + updateBody.oidcConfig = input.oidcConfig; } if (input.samlConfig != null) { - updatePayload.samlConfig = JSON.stringify(input.samlConfig); + updateBody.samlConfig = input.samlConfig; } - await db - .update(ssoProvider) - .set(updatePayload) - .where( - and( - eq(ssoProvider.providerId, input.providerId), - eq(ssoProvider.organizationId, ctx.session.activeOrganizationId), - eq(ssoProvider.userId, ctx.session.userId), - ), - ); + await auth.updateSSOProvider({ + params: { providerId: input.providerId }, + body: updateBody, + headers: requestToHeaders(ctx.req), + }); return { success: true }; }), deleteProvider: enterpriseProcedure diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index 7f7f2eba3..eb9906beb 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -349,6 +349,7 @@ export const auth = { handler, createApiKey: api.createApiKey, registerSSOProvider: api.registerSSOProvider, + updateSSOProvider: api.updateSSOProvider, }; export const validateRequest = async (request: IncomingMessage) => {