mirror of
https://github.com/dokploy/dokploy.git
synced 2026-06-13 19:09:49 +00:00
refactor: simplify forward authentication handling in UI and API
- Removed the selection of SSO providers from the UI, streamlining the process to enable/disable SSO for domains. - Updated the API to eliminate the need for a provider ID when enabling forward authentication, relying on the configured settings instead. - Enhanced user feedback by updating toast messages to reflect the current state of SSO authentication. - Improved the UI layout for better clarity on SSO status and actions. This refactor enhances the user experience by simplifying the SSO configuration process and ensuring clearer communication of actions taken.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -12,13 +12,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
interface Props {
|
||||
@@ -28,35 +22,28 @@ interface Props {
|
||||
|
||||
export const HandleForwardAuth = ({ domainId, applicationId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedProviderId, setSelectedProviderId] = useState<string>("");
|
||||
|
||||
const { data: haveValidLicense } =
|
||||
api.licenseKey.haveValidLicenseKey.useQuery();
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { data: status, isLoading: isLoadingStatus } =
|
||||
api.forwardAuth.status.useQuery({ domainId }, { enabled: isOpen });
|
||||
const { data: providers, isLoading: isLoadingProviders } =
|
||||
api.forwardAuth.listProviders.useQuery(undefined, { enabled: isOpen });
|
||||
const { data: status } = api.forwardAuth.status.useQuery(
|
||||
{ domainId },
|
||||
{ enabled: isOpen },
|
||||
);
|
||||
|
||||
const { mutateAsync: enable, isPending: isEnabling } =
|
||||
api.forwardAuth.enable.useMutation();
|
||||
const { mutateAsync: disable, isPending: isDisabling } =
|
||||
api.forwardAuth.disable.useMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (status?.providerId) {
|
||||
setSelectedProviderId(status.providerId);
|
||||
}
|
||||
}, [status?.providerId]);
|
||||
|
||||
if (!haveValidLicense) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isEnabled = !!status?.enabled;
|
||||
const hasProviders = (providers?.length ?? 0) > 0;
|
||||
const isPending = isEnabling || isDisabling;
|
||||
|
||||
const refresh = async () => {
|
||||
await utils.forwardAuth.status.invalidate({ domainId });
|
||||
@@ -64,32 +51,21 @@ export const HandleForwardAuth = ({ domainId, applicationId }: Props) => {
|
||||
await utils.application.readTraefikConfig.invalidate({ applicationId });
|
||||
};
|
||||
|
||||
const handleEnable = async () => {
|
||||
if (!selectedProviderId) {
|
||||
toast.error("Select an SSO provider first");
|
||||
return;
|
||||
}
|
||||
const handleToggle = async (next: boolean) => {
|
||||
try {
|
||||
await enable({ domainId, providerId: selectedProviderId });
|
||||
if (next) {
|
||||
await enable({ domainId });
|
||||
toast.success("SSO authentication enabled for this domain");
|
||||
} else {
|
||||
await disable({ domainId });
|
||||
toast.success("SSO authentication disabled for this domain");
|
||||
}
|
||||
await refresh();
|
||||
toast.success("SSO authentication enabled for this domain");
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Error enabling SSO",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisable = async () => {
|
||||
try {
|
||||
await disable({ domainId });
|
||||
await refresh();
|
||||
toast.success("SSO authentication disabled for this domain");
|
||||
setIsOpen(false);
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Error disabling SSO",
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Error updating SSO authentication",
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -121,72 +97,32 @@ export const HandleForwardAuth = ({ domainId, applicationId }: Props) => {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{!isLoadingProviders && !hasProviders && (
|
||||
<AlertBlock type="warning">
|
||||
No SSO providers configured. Add an OIDC provider in your
|
||||
organization SSO settings first.
|
||||
</AlertBlock>
|
||||
)}
|
||||
|
||||
<AlertBlock type="info">
|
||||
Requires the authentication domain + proxy to be configured in SSO
|
||||
settings, and this app's domain to share its base domain.
|
||||
The authentication proxy must be deployed for this app's server in SSO
|
||||
settings. The domain must share its base domain.
|
||||
</AlertBlock>
|
||||
|
||||
<div className="flex flex-col gap-4 py-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-sm font-medium">Identity provider</span>
|
||||
<Select
|
||||
value={selectedProviderId}
|
||||
onValueChange={setSelectedProviderId}
|
||||
disabled={isLoadingStatus || isLoadingProviders || !hasProviders}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an SSO provider">
|
||||
{selectedProviderId || ""}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{providers?.map((provider) => (
|
||||
<SelectItem
|
||||
key={provider.providerId}
|
||||
value={provider.providerId}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{provider.providerId}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{provider.issuer}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex items-center justify-between rounded-lg border p-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-medium">
|
||||
Protect this domain with SSO
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{isEnabled
|
||||
? "Visitors must log in via your identity provider."
|
||||
: "The domain is publicly accessible."}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isEnabled && (
|
||||
<AlertBlock type="info">
|
||||
SSO is currently enabled for this domain.
|
||||
</AlertBlock>
|
||||
)}
|
||||
<Switch
|
||||
checked={isEnabled}
|
||||
disabled={isPending}
|
||||
onCheckedChange={handleToggle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex-row justify-end gap-2">
|
||||
{isEnabled && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
isLoading={isDisabling}
|
||||
onClick={handleDisable}
|
||||
>
|
||||
Disable
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
isLoading={isEnabling}
|
||||
disabled={!hasProviders || !selectedProviderId}
|
||||
onClick={handleEnable}
|
||||
>
|
||||
{isEnabled ? "Update" : "Enable"}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -124,12 +124,7 @@ export const forwardAuthRouter = createTRPCRouter({
|
||||
.query(({ ctx, input }) => getDomainSsoStatus(ctx, input.domainId)),
|
||||
|
||||
enable: enterpriseProcedure
|
||||
.input(
|
||||
z.object({
|
||||
domainId: z.string().min(1),
|
||||
providerId: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.input(z.object({ domainId: z.string().min(1) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const domain = await assertApplicationDomainAccess(
|
||||
ctx,
|
||||
@@ -138,8 +133,6 @@ export const forwardAuthRouter = createTRPCRouter({
|
||||
);
|
||||
const result = await enableForwardAuthOnDomain({
|
||||
domainId: input.domainId,
|
||||
providerId: input.providerId,
|
||||
organizationId: ctx.session.activeOrganizationId,
|
||||
});
|
||||
await audit(ctx, {
|
||||
action: "update",
|
||||
|
||||
@@ -326,19 +326,16 @@ export const assertApplicationDomainAccess = async (
|
||||
|
||||
export const enableForwardAuthOnDomain = async (input: {
|
||||
domainId: string;
|
||||
providerId: string;
|
||||
organizationId: string;
|
||||
}) => {
|
||||
const { application } = await resolveApplicationDomain(input.domainId);
|
||||
await findProviderForOrg(input.providerId, input.organizationId);
|
||||
const serverId = application.serverId ?? undefined;
|
||||
|
||||
const settings = await getForwardAuthSettings(serverId ?? null);
|
||||
if (!settings) {
|
||||
if (!settings?.providerId) {
|
||||
throw new TRPCError({
|
||||
code: "PRECONDITION_FAILED",
|
||||
message:
|
||||
"Set the authentication domain and deploy the proxy for this server first.",
|
||||
"Deploy the authentication proxy for this server in SSO settings first.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -352,7 +349,7 @@ export const enableForwardAuthOnDomain = async (input: {
|
||||
}
|
||||
|
||||
await updateDomainById(input.domainId, {
|
||||
forwardAuthProviderId: input.providerId,
|
||||
forwardAuthProviderId: settings.providerId,
|
||||
});
|
||||
const domain = await findDomainById(input.domainId);
|
||||
await manageDomain(application, domain);
|
||||
|
||||
Reference in New Issue
Block a user