Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
f680818
Add enterprise features management: implement license key settings an…
Siumauricio Jan 28, 2026
25fa362
Add enterprise features management: implement license key settings an…
Siumauricio Jan 29, 2026
0c299a3
Refactor license key management: update API calls to use licenseKey r…
Siumauricio Jan 29, 2026
709ffdd
Update better-auth dependency to version 1.2.8 and enhance license ke…
Siumauricio Jan 29, 2026
262960a
Refactor license key management: remove legacy license key settings c…
Siumauricio Jan 29, 2026
cbfa690
Improve error handling in license key management: update error loggin…
Siumauricio Jan 29, 2026
c9ffb99
Refactor license key deactivation process: update API to retrieve the…
Siumauricio Jan 29, 2026
346216f
Add License Settings Page: Introduce a new License settings page with…
Siumauricio Jan 29, 2026
2b52332
Enhance License Key Management: Add loading state for license key val…
Siumauricio Jan 29, 2026
2e7f4dc
Refactor License Key Settings UI: Simplify conditional rendering for …
Siumauricio Jan 29, 2026
7f27601
Implement Single Sign-On (SSO) Feature: Add SSO settings page, integr…
Siumauricio Jan 30, 2026
6064b8c
Implement SAML Provider Registration and Enhance OIDC Dialog: Add a n…
Siumauricio Jan 30, 2026
9a8de9a
Add Enterprise Feature Gate Component: Introduce EnterpriseFeatureGat…
Siumauricio Jan 30, 2026
12a87f9
Enhance License Key Management and Enterprise Features: Update licens…
Siumauricio Jan 30, 2026
82c06a4
Remove refresh-license-validity API endpoint and integrate enterprise…
Siumauricio Jan 30, 2026
f72bc28
Refactor enterprise backup cron job initialization: Simplified the cr…
Siumauricio Jan 30, 2026
30c3e44
Refactor SSO Registration Dialogs: Remove onSuccess prop from Registe…
Siumauricio Jan 30, 2026
61f6bbf
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 31, 2026
3c2f675
Enhance SSO Functionality: Add detailed view for SSO providers in SSO…
Siumauricio Jan 31, 2026
1f33b0f
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 31, 2026
c4515a2
Fix admin creation check in authentication logic: Re-enable the check…
Siumauricio Jan 31, 2026
66b4bf2
Comment out user, session, account, verification, and apikey table de…
Siumauricio Jan 31, 2026
f3d9960
Implement SSO Sign-In Options: Add components for signing in with Git…
Siumauricio Jan 31, 2026
cae7a92
Refactor SSO Registration Dialogs: Update RegisterOidcDialog and Regi…
Siumauricio Jan 31, 2026
68587c3
Add SSO Provider Integration: Introduce getSSOProviders function to f…
Siumauricio Jan 31, 2026
acb3c1d
Add Sign-In Options for Cloud Users: Integrate GitHub and Google sign…
Siumauricio Jan 31, 2026
bc5c65b
Merge branch 'canary' into feat/introduce-license-key-pay
Siumauricio Jan 31, 2026
d22d961
feat(auth): add SSO request handling and provider validation in authe…
Siumauricio Jan 31, 2026
d5de5b8
feat(sso): implement SSO provider registration and update related com…
Siumauricio Jan 31, 2026
7665b38
feat(sso): refine provider query to include user ID for enhanced secu…
Siumauricio Jan 31, 2026
6b42c9d
feat(auth): expand disabled paths for SSO registration and organizati…
Siumauricio Jan 31, 2026
54229b0
Merge branch 'canary' into feat/introduce-license-key-pay
Siumauricio Jan 31, 2026
69ba901
feat(sso): update SSO provider registration to handle multiple domains
Siumauricio Jan 31, 2026
fb06cf8
feat(auth): add Okta domain to SSO provider list and adjust SSO reque…
Siumauricio Jan 31, 2026
dc756e2
refactor(auth): rename forgetPassword to requestPasswordReset for cla…
Siumauricio Jan 31, 2026
00ce8ca
feat(license): enhance license key management and authorization checks
Siumauricio Feb 1, 2026
11082f2
feat(sso): enhance OIDC registration mapping for Azure and other prov…
Siumauricio Feb 1, 2026
aa558b3
feat(sso): update SAML registration dialog and settings for improved …
Siumauricio Feb 2, 2026
c56def9
fix(db): update database URL for Docker compatibility
Siumauricio Feb 2, 2026
766fd00
Merge branch 'canary' into feat/introduce-license-key-pay
Siumauricio Feb 2, 2026
354407c
chore(license): comment out cloud environment redirection logic in li…
Siumauricio Feb 2, 2026
71b8789
refactor(auth): streamline trusted origins configuration and improve …
Siumauricio Feb 2, 2026
a70018f
feat(auth): add enterprise feature flags to user context and request …
Siumauricio Feb 2, 2026
8b1cc94
feat(sso): implement SSO provider retrieval functionality
Siumauricio Feb 2, 2026
945406a
Merge branch 'canary' into feat/introduce-license-key-pay
Siumauricio Feb 2, 2026
2b36381
fix: update import path for getPublicIpWithFallback in enterprise uti…
Siumauricio Feb 4, 2026
3307f62
refactor(auth): remove unused SSO provider retrieval logic
Siumauricio Feb 4, 2026
dc8148a
fix(db): update database URL configuration for production and develop…
Siumauricio Feb 4, 2026
8335f40
feat(tests): add mock database setup for Vitest testing environment
Siumauricio Feb 4, 2026
cfb9534
feat(tests): enhance mock database with web server settings for testing
Siumauricio Feb 5, 2026
8001e5d
feat(tests): add values method to mock database for enhanced testing
Siumauricio Feb 5, 2026
00e31f3
fix(db): update deprecation warning for legacy database credentials
Siumauricio Feb 5, 2026
ac833ef
feat(tests): enhance Vitest configuration with additional environment…
Siumauricio Feb 5, 2026
dc74d30
fix(tests): update setup file path in Vitest configuration for clarity
Siumauricio Feb 5, 2026
4f0d707
delete: remove obsolete SQL migration files and snapshots
Siumauricio Feb 5, 2026
9910c0e
feat(db): add sso_provider table and update user schema
Siumauricio Feb 5, 2026
542ccc4
feat(sso): enhance SSO provider management and trusted origins handling
Siumauricio Feb 5, 2026
99646f8
feat(tests): enhance mock database with member methods for testing
Siumauricio Feb 5, 2026
bde192c
feat(admin): handle empty member list in trusted origins retrieval
Siumauricio Feb 5, 2026
2746133
delete(tests): remove mock database setup file and update Vitest conf…
Siumauricio Feb 5, 2026
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
5 changes: 5 additions & 0 deletions apps/dokploy/__test__/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ export default defineConfig({
include: ["__test__/**/*.test.ts"], // Incluir solo los archivos de test en el directorio __test__
exclude: ["**/node_modules/**", "**/dist/**", "**/.docker/**"],
pool: "forks",
// Se ejecuta antes de todos los tests y aplica mocks globales (db, postgres, etc.)
},
define: {
"process.env": {
NODE: "test",
GITHUB_CLIENT_ID: "test",
GITHUB_CLIENT_SECRET: "test",
GOOGLE_CLIENT_ID: "test",
GOOGLE_CLIENT_SECRET: "test",
},
},
plugins: [
Expand Down
20 changes: 20 additions & 0 deletions apps/dokploy/components/layouts/side.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import {
Forward,
GalleryVerticalEnd,
GitBranch,
Key,
KeyRound,
Loader2,
LogIn,
type LucideIcon,
Package,
PieChart,
Expand Down Expand Up @@ -396,6 +398,24 @@ const MENU: Menu = {
// Only enabled for admins in cloud environments
isEnabled: ({ auth, isCloud }) => !!(auth?.role === "owner" && isCloud),
},
{
isSingle: true,
title: "License",
url: "/dashboard/settings/license",
icon: Key,
// Only enabled for admins in non-cloud environments
isEnabled: ({ auth }) =>
!!(auth?.role === "owner" || auth?.role === "admin"),
},
{
isSingle: true,
title: "SSO",
url: "/dashboard/settings/sso",
icon: LogIn,
// Enabled for admins in both cloud and self-hosted (enterprise)
isEnabled: ({ auth }) =>
!!(auth?.role === "owner" || auth?.role === "admin"),
},
],

help: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { useState } from "react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { Button } from "@/components/ui/button";

export function SignInWithGithub() {
const [isLoading, setIsLoading] = useState(false);

const handleClick = async () => {
setIsLoading(true);
try {
const { error } = await authClient.signIn.social({
provider: "github",
});
if (error) {
toast.error(error.message);
return;
}
} catch (err) {
toast.error("An error occurred while signing in with GitHub", {
description: err instanceof Error ? err.message : "Unknown error",
});
} finally {
setIsLoading(false);
}
};

return (
<Button
variant="outline"
type="button"
className="w-full mb-4"
onClick={handleClick}
isLoading={isLoading}
>
<svg viewBox="0 0 438.549 438.549" className="mr-2 size-4">
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
/>
</svg>
Sign in with GitHub
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";

import { useState } from "react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { Button } from "@/components/ui/button";

export function SignInWithGoogle() {
const [isLoading, setIsLoading] = useState(false);

const handleClick = async () => {
setIsLoading(true);
try {
const { error } = await authClient.signIn.social({
provider: "google",
});
if (error) {
toast.error(error.message);
return;
}
} catch (err) {
toast.error("An error occurred while signing in with Google", {
description: err instanceof Error ? err.message : "Unknown error",
});
} finally {
setIsLoading(false);
}
};

return (
<Button
variant="outline"
type="button"
className="w-full mb-4"
onClick={handleClick}
isLoading={isLoading}
>
<svg viewBox="0 0 24 24" className="mr-2 size-4">
<path
fill="currentColor"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="currentColor"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="currentColor"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="currentColor"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
Sign in with Google
</Button>
);
}
114 changes: 114 additions & 0 deletions apps/dokploy/components/proprietary/enterprise-feature-gate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use client";

import { Loader2, Lock } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";

interface EnterpriseFeatureLockedProps {
/** Optional title override */
title?: string;
/** Optional description override */
description?: string;
/** Optional custom CTA label */
ctaLabel?: string;
/** Optional CTA href (default: /dashboard/settings/license) */
ctaHref?: string;
/** Compact variant (less padding, smaller icon) */
compact?: boolean;
}

/**
* Displays a locked state for enterprise features when the user has no valid license.
* Use standalone or via EnterpriseFeatureGate.
*/
export function EnterpriseFeatureLocked({
title = "Enterprise feature",
description = "This feature is part of Dokploy Enterprise. Add a valid license to use it.",
ctaLabel = "Go to License",
ctaHref = "/dashboard/settings/license",
compact = false,
}: EnterpriseFeatureLockedProps) {
return (
<Card className="border-dashed bg-transparent">
<CardHeader className={compact ? "pb-2" : undefined}>
<div className="flex flex-col items-center gap-3 text-center">
<div
className={
compact
? "rounded-full bg-muted p-3"
: "rounded-full bg-muted p-4"
}
>
<Lock
className={
compact
? "size-6 text-muted-foreground"
: "size-8 text-muted-foreground"
}
/>
</div>
<div className="space-y-1">
<CardTitle className="text-lg">{title}</CardTitle>
<CardDescription className="max-w-sm mx-auto">
{description}
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className={compact ? "pt-0" : undefined}>
<div className="flex justify-center">
<Button asChild variant="secondary" size={compact ? "sm" : "default"}>
<Link href={ctaHref}>{ctaLabel}</Link>
</Button>
</div>
</CardContent>
</Card>
);
}

interface EnterpriseFeatureGateProps {
children: React.ReactNode;
/** Props for the locked state when license is invalid */
lockedProps?: Omit<EnterpriseFeatureLockedProps, "compact">;
/** Show loading spinner while checking license */
fallback?: React.ReactNode;
}

/**
* Renders children only when the instance has a valid enterprise license.
* Otherwise shows EnterpriseFeatureLocked.
*/
export function EnterpriseFeatureGate({
children,
lockedProps,
fallback,
}: EnterpriseFeatureGateProps) {
const { data: haveValidLicense, isLoading } =
api.licenseKey.haveValidLicenseKey.useQuery();

if (isLoading) {
if (fallback) return <>{fallback}</>;
return (
<div className="flex items-center gap-2 justify-center min-h-[25vh]">
<Loader2 className="size-6 text-muted-foreground animate-spin" />
<span className="text-sm text-muted-foreground">
Checking license...
</span>
</div>
);
}

if (!haveValidLicense) {
return <EnterpriseFeatureLocked {...lockedProps} />;
}

return <>{children}</>;
}
Loading
Loading