mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-10 16:35:26 +02:00
feat(sso): implement SSO provider registration and update related components
- Refactored SSO registration logic in `register-oidc-dialog` and `register-saml-dialog` to use a new mutation method. - Removed unused imports and error handling for registration failures. - Added foreign key constraint for `organization_id` in the `sso_provider` table. - Introduced new SSO schema and updated user relations to include SSO providers. - Enhanced authentication flow to support SSO provider registration.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Loader2, Plus, Trash2 } from "lucide-react";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import type { FieldArrayPath } from "react-hook-form";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
const DEFAULT_SCOPES = ["openid", "email", "profile"];
|
||||
@@ -74,6 +73,7 @@ const formDefaultValues = {
|
||||
export function RegisterOidcDialog({ children }: RegisterOidcDialogProps) {
|
||||
const utils = api.useUtils();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { mutateAsync, isLoading } = api.sso.register.useMutation();
|
||||
|
||||
const form = useForm<OidcProviderForm>({
|
||||
resolver: zodResolver(oidcProviderSchema),
|
||||
@@ -105,7 +105,7 @@ export function RegisterOidcDialog({ children }: RegisterOidcDialogProps) {
|
||||
.map((d) => d.trim())
|
||||
.filter(Boolean)
|
||||
.join(",");
|
||||
const { error } = await authClient.sso.register({
|
||||
await mutateAsync({
|
||||
providerId: data.providerId,
|
||||
issuer: data.issuer,
|
||||
domain,
|
||||
@@ -125,11 +125,6 @@ export function RegisterOidcDialog({ children }: RegisterOidcDialogProps) {
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message ?? "Failed to register SSO provider");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("OIDC provider registered successfully");
|
||||
form.reset(formDefaultValues);
|
||||
setOpen(false);
|
||||
@@ -340,10 +335,7 @@ export function RegisterOidcDialog({ children }: RegisterOidcDialogProps) {
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting && (
|
||||
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
<Button type="submit" isLoading={isLoading}>
|
||||
Register provider
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Loader2, Plus, Trash2 } from "lucide-react";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { type FieldArrayPath, useFieldArray, useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
const domainsArraySchema = z
|
||||
@@ -80,6 +79,7 @@ const formDefaultValues: SamlProviderForm = {
|
||||
export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
const utils = api.useUtils();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { mutateAsync, isLoading } = api.sso.register.useMutation();
|
||||
|
||||
const form = useForm<SamlProviderForm>({
|
||||
resolver: zodResolver(samlProviderSchema),
|
||||
@@ -99,7 +99,7 @@ export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
.map((d) => d.trim())
|
||||
.filter(Boolean)
|
||||
.join(",");
|
||||
const { error } = await authClient.sso.register({
|
||||
await mutateAsync({
|
||||
providerId: data.providerId,
|
||||
issuer: data.issuer,
|
||||
domain,
|
||||
@@ -117,11 +117,6 @@ export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message ?? "Failed to register SAML provider");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("SAML provider registered successfully");
|
||||
form.reset(formDefaultValues);
|
||||
setOpen(false);
|
||||
@@ -315,10 +310,7 @@ export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting && (
|
||||
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
<Button type="submit" isLoading={isLoading}>
|
||||
Register provider
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
1
apps/dokploy/drizzle/0140_great_lightspeed.sql
Normal file
1
apps/dokploy/drizzle/0140_great_lightspeed.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "sso_provider" ADD CONSTRAINT "sso_provider_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;
|
||||
7166
apps/dokploy/drizzle/meta/0140_snapshot.json
Normal file
7166
apps/dokploy/drizzle/meta/0140_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -981,6 +981,13 @@
|
||||
"when": 1769746948088,
|
||||
"tag": "0139_smiling_havok",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 140,
|
||||
"version": "7",
|
||||
"when": 1769854977685,
|
||||
"tag": "0140_great_lightspeed",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { IS_CLOUD } from "@dokploy/server/constants";
|
||||
import { member, ssoProvider } from "@dokploy/server/db/schema";
|
||||
import { ssoProviderBodySchema } from "@dokploy/server/db/schema/sso";
|
||||
import { auth } from "@dokploy/server/lib/auth";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, asc, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
@@ -10,6 +12,20 @@ import {
|
||||
} from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
|
||||
function requestToHeaders(req: {
|
||||
headers?: Record<string, string | string[] | undefined>;
|
||||
}): Headers {
|
||||
const headers = new Headers();
|
||||
if (req?.headers) {
|
||||
for (const [key, value] of Object.entries(req.headers)) {
|
||||
if (value !== undefined && key.toLowerCase() !== "host") {
|
||||
headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
export const ssoRouter = createTRPCRouter({
|
||||
showSignInWithSSO: publicProcedure.query(async () => {
|
||||
if (IS_CLOUD) {
|
||||
@@ -38,7 +54,7 @@ export const ssoRouter = createTRPCRouter({
|
||||
}),
|
||||
listProviders: enterpriseProcedure.query(async ({ ctx }) => {
|
||||
const providers = await db.query.ssoProvider.findMany({
|
||||
where: eq(ssoProvider.userId, ctx.user.id),
|
||||
where: eq(ssoProvider.organizationId, ctx.session.activeOrganizationId),
|
||||
columns: {
|
||||
id: true,
|
||||
providerId: true,
|
||||
@@ -59,7 +75,7 @@ export const ssoRouter = createTRPCRouter({
|
||||
.where(
|
||||
and(
|
||||
eq(ssoProvider.providerId, input.providerId),
|
||||
eq(ssoProvider.userId, ctx.user.id),
|
||||
eq(ssoProvider.organizationId, ctx.session.activeOrganizationId),
|
||||
),
|
||||
)
|
||||
.returning({ id: ssoProvider.id });
|
||||
@@ -72,6 +88,22 @@ export const ssoRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
register: enterpriseProcedure
|
||||
.input(ssoProviderBodySchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const organizationId = ctx.session.activeOrganizationId;
|
||||
|
||||
const result = await auth.registerSSOProvider({
|
||||
body: {
|
||||
...input,
|
||||
organizationId,
|
||||
},
|
||||
headers: requestToHeaders(ctx.req),
|
||||
});
|
||||
console.log(result);
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user