mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-04 21:45:26 +02:00
feat(sso): update SAML registration dialog and settings for improved metadata handling
- Added support for IdP metadata XML in the SAML registration dialog, allowing users to paste full metadata for configuration. - Updated the callback URL and audience handling to dynamically incorporate the base URL. - Refactored the SSO settings to enable SAML provider registration and improved the display of callback URLs based on provider details. - Enhanced trusted origins configuration in the authentication logic to include additional domains for development and production environments.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { type FieldArrayPath, useFieldArray, useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
@@ -52,12 +52,7 @@ const samlProviderSchema = z.object({
|
||||
.url("Invalid URL")
|
||||
.trim(),
|
||||
cert: z.string().min(1, "IdP signing certificate is required"),
|
||||
callbackUrl: z
|
||||
.string()
|
||||
.min(1, "Callback URL is required")
|
||||
.url("Invalid URL")
|
||||
.trim(),
|
||||
audience: z.string().min(1, "Audience (Entity ID) is required").trim(),
|
||||
idpMetadataXml: z.string().optional(),
|
||||
});
|
||||
|
||||
type SamlProviderForm = z.infer<typeof samlProviderSchema>;
|
||||
@@ -72,8 +67,7 @@ const formDefaultValues: SamlProviderForm = {
|
||||
domains: [""],
|
||||
entryPoint: "",
|
||||
cert: "",
|
||||
callbackUrl: "",
|
||||
audience: "",
|
||||
idpMetadataXml: "",
|
||||
};
|
||||
|
||||
export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
@@ -81,6 +75,14 @@ export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { mutateAsync, isLoading } = api.sso.register.useMutation();
|
||||
|
||||
const [baseURL, setBaseURL] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
setBaseURL(window.location.origin);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const form = useForm<SamlProviderForm>({
|
||||
resolver: zodResolver(samlProviderSchema),
|
||||
defaultValues: formDefaultValues,
|
||||
@@ -95,6 +97,17 @@ export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
|
||||
const onSubmit = async (data: SamlProviderForm) => {
|
||||
try {
|
||||
// maybe add the /saml/metadata endpoint to the baseURL
|
||||
const baseURLWithMetadata = `${baseURL}/saml/metadata`;
|
||||
const generateSpMetadata = (providerId: string) => {
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="${baseURL}">
|
||||
<md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="${baseURL}/api/auth/sso/saml2/callback/${providerId}" index="1"/>
|
||||
</md:SPSSODescriptor>
|
||||
</md:EntityDescriptor>`;
|
||||
};
|
||||
|
||||
await mutateAsync({
|
||||
providerId: data.providerId,
|
||||
issuer: data.issuer,
|
||||
@@ -102,13 +115,20 @@ export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
samlConfig: {
|
||||
entryPoint: data.entryPoint,
|
||||
cert: data.cert,
|
||||
callbackUrl: data.callbackUrl,
|
||||
audience: data.audience,
|
||||
wantAssertionsSigned: true,
|
||||
signatureAlgorithm: "sha256",
|
||||
digestAlgorithm: "sha256",
|
||||
callbackUrl: `${baseURL}/api/auth/sso/saml2/callback/${data.providerId}`,
|
||||
audience: baseURL,
|
||||
idpMetadata: data.idpMetadataXml?.trim()
|
||||
? { metadata: data.idpMetadataXml.trim() }
|
||||
: undefined,
|
||||
spMetadata: {
|
||||
entityID: data.audience,
|
||||
metadata: generateSpMetadata(data.providerId),
|
||||
},
|
||||
mapping: {
|
||||
id: "nameID",
|
||||
email: "email",
|
||||
name: "displayName",
|
||||
firstName: "givenName",
|
||||
lastName: "surname",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -264,39 +284,29 @@ export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="callbackUrl"
|
||||
name="idpMetadataXml"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Callback URL (ACS)</FormLabel>
|
||||
<FormLabel>IdP metadata XML (optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="https://yourapp.com/api/auth/sso/saml2/callback/my-provider"
|
||||
<Textarea
|
||||
placeholder="Paste full IdP metadata XML if you have it (EntityDescriptor). Otherwise leave empty and use Issuer, IdP SSO URL and certificate above."
|
||||
rows={5}
|
||||
className="font-mono text-xs"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Use the callback URL shown in your IdP app config for this
|
||||
provider.
|
||||
Some IdPs require full metadata; paste the XML here to
|
||||
override issuer/entry point/cert.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="audience"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Audience (Entity ID)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="https://yourapp.com" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -109,12 +109,12 @@ export const SSOSettings = () => {
|
||||
Add OIDC provider
|
||||
</Button>
|
||||
</RegisterOidcDialog>
|
||||
{/* <RegisterSamlDialog>
|
||||
<RegisterSamlDialog>
|
||||
<Button variant="secondary" size="sm">
|
||||
<LogIn className="mr-2 size-4" />
|
||||
Add SAML provider
|
||||
</Button>
|
||||
</RegisterSamlDialog> */}
|
||||
</RegisterSamlDialog>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -234,12 +234,12 @@ export const SSOSettings = () => {
|
||||
Add OIDC provider
|
||||
</Button>
|
||||
</RegisterOidcDialog>
|
||||
{/* <RegisterSamlDialog>
|
||||
<RegisterSamlDialog>
|
||||
<Button variant="outline">
|
||||
<LogIn className="mr-2 size-4" />
|
||||
Add SAML provider
|
||||
</Button>
|
||||
</RegisterSamlDialog> */}
|
||||
</RegisterSamlDialog>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -340,7 +340,10 @@ export const SSOSettings = () => {
|
||||
Callback URL (configure in your IdP)
|
||||
</span>
|
||||
<p className="break-all rounded-md bg-muted px-2 py-1.5 font-mono text-xs">
|
||||
{baseURL || "{baseURL}"}/api/auth/sso/callback/
|
||||
{baseURL || "{baseURL}"}
|
||||
{detailsProvider.samlConfig
|
||||
? "/api/auth/sso/saml2/callback/"
|
||||
: "/api/auth/sso/callback/"}
|
||||
{detailsProvider.providerId}
|
||||
</p>
|
||||
{!baseURL && (
|
||||
|
||||
Reference in New Issue
Block a user