Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aafb364
Enforce SAML SSO on workspace
devkiran Sep 19, 2025
f853779
Merge branch 'main' into enforce-saml-sso
devkiran Sep 23, 2025
f18b9de
Add SAML SSO enforcement for enterprise workspaces
devkiran Sep 23, 2025
86f4fbc
Update saml.tsx
devkiran Sep 23, 2025
e68524f
Update route.ts
devkiran Sep 23, 2025
d12a315
Refactor SAML enforcement to use optimistic updates
devkiran Sep 23, 2025
2ab43b0
Delete workflow.ts
devkiran Sep 23, 2025
9f7dfb5
Update SAML enforcement to skip partner hostnames
devkiran Sep 23, 2025
c79393d
Add SAML enforcement check to account existence action
devkiran Sep 23, 2025
5efa2e1
Update check-account-exists.ts
devkiran Sep 23, 2025
4155d9b
Update check-account-exists.ts
devkiran Sep 23, 2025
87eea4b
Use request headers for SAML enforcement hostname
devkiran Sep 23, 2025
6b882e1
address coderabbit feedback
devkiran Sep 23, 2025
6b53920
some cleanup
devkiran Sep 23, 2025
f42c32a
Merge branch 'main' into enforce-saml-sso
steven-tey Sep 24, 2025
2507a24
Update unban-partner.ts
steven-tey Sep 24, 2025
0996b80
Merge branch 'main' into enforce-saml-sso
devkiran Sep 26, 2025
02b3d56
Enhance SAML enforcement and workspace user logic
devkiran Sep 26, 2025
6d2c849
Merge branch 'main' into enforce-saml-sso
steven-tey Sep 28, 2025
519363d
Merge branch 'main' into enforce-saml-sso
steven-tey Sep 28, 2025
9cd79ba
simplify to just ssoEmailDomain
steven-tey Sep 29, 2025
425eab2
finalize Email domain enforcement UI
steven-tey Sep 29, 2025
89beb98
fix error handling
steven-tey Sep 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions apps/web/app/api/workspaces/[idOrSlug]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { deleteWorkspace } from "@/lib/api/workspaces/delete-workspace";
import { prefixWorkspaceId } from "@/lib/api/workspaces/workspace-id";
import { withWorkspace } from "@/lib/auth";
import { getFeatureFlags } from "@/lib/edge-config";
import { jackson } from "@/lib/jackson";
import { storage } from "@/lib/storage";
import z from "@/lib/zod";
import {
Expand All @@ -32,6 +33,7 @@ const updateWorkspaceSchema = createWorkspaceSchema
z.null(),
])
.optional(),
ssoEmailDomain: z.string().nullish(),
})
.partial();

Expand Down Expand Up @@ -75,14 +77,15 @@ export const GET = withWorkspace(

// PATCH /api/workspaces/[idOrSlug] – update a specific workspace by id or slug
export const PATCH = withWorkspace(
async ({ req, workspace }) => {
async ({ req, workspace, session }) => {
const {
name,
slug,
logo,
conversionEnabled,
allowedHostnames,
publishableKey,
ssoEmailDomain,
} = await updateWorkspaceSchema.parseAsync(await parseRequestBody(req));

if (["free", "pro"].includes(workspace.plan) && conversionEnabled) {
Expand All @@ -103,6 +106,30 @@ export const PATCH = withWorkspace(
)
: null;

if (ssoEmailDomain) {
if (workspace.plan !== "enterprise") {
throw new DubApiError({
code: "forbidden",
message: "SAML SSO is only available on enterprise plans.",
});
}

// Check if SAML is configured before enforcing ssoEmailDomain
const { apiController } = await jackson();

const connections = await apiController.getConnections({
tenant: workspace.id,
product: "Dub",
});

if (connections.length === 0) {
throw new DubApiError({
code: "forbidden",
message: "SAML SSO is not configured for this workspace.",
});
}
}

try {
const response = await prisma.project.update({
where: {
Expand All @@ -117,6 +144,7 @@ export const PATCH = withWorkspace(
allowedHostnames: validHostnames,
}),
...(publishableKey !== undefined && { publishableKey }),
...(ssoEmailDomain !== undefined && { ssoEmailDomain }),
},
include: {
domains: {
Expand Down Expand Up @@ -184,7 +212,7 @@ export const PATCH = withWorkspace(
if (error.code === "P2002") {
throw new DubApiError({
code: "conflict",
message: `The slug "${slug}" is already in use.`,
message: `The ${ssoEmailDomain ? "email domain" : "slug"} "${ssoEmailDomain || slug}" is already in use.`,
});
} else {
throw new DubApiError({
Expand Down
16 changes: 14 additions & 2 deletions apps/web/app/api/workspaces/[idOrSlug]/saml/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { withWorkspace } from "@/lib/auth";
import { jackson, samlAudience } from "@/lib/jackson";
import z from "@/lib/zod";
import { prisma } from "@dub/prisma";
import { APP_DOMAIN_WITH_NGROK } from "@dub/utils";
import { NextResponse } from "next/server";

Expand Down Expand Up @@ -76,7 +77,7 @@ export const POST = withWorkspace(

// DELETE /api/workspaces/[idOrSlug]/saml – delete all SAML connections
export const DELETE = withWorkspace(
async ({ searchParams }) => {
async ({ searchParams, workspace }) => {
const { clientID, clientSecret } =
deleteSAMLConnectionSchema.parse(searchParams);

Expand All @@ -87,7 +88,18 @@ export const DELETE = withWorkspace(
clientSecret,
});

return NextResponse.json({ response: "removed SAML connection" });
await prisma.project.update({
where: {
id: workspace.id,
},
data: {
ssoEmailDomain: null,
},
});

return NextResponse.json({
response: "Successfully removed SAML connection",
});
},
{
requiredPermissions: ["workspaces.write"],
Expand Down
Loading