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) => {