Skip to content

Commit 151edf8

Browse files
authored
Merge pull request #5648 from imrja8/feat/enterprise-custom-forgot-password
feat(enterprise): custom forgot password flow in whitelabeling settings
2 parents bce96fd + 10aa020 commit 151edf8

6 files changed

Lines changed: 68 additions & 11 deletions

File tree

‎apps/dokploy/__test__/traefik/server/update-server-config.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ const baseSettings: WebServerSettings = {
6464
errorPageDescription: null,
6565
ogImageUrl: null,
6666
footerText: null,
67+
passwordResetGuide: null,
68+
supportEmail: null,
6769
logoLightUrl: null,
6870
loginLogoLightUrl: null,
6971
faviconLightUrl: null,

‎apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ const safeUrlField = z
3737
message: "Only http:// and https:// URLs are allowed",
3838
});
3939

40+
const safeEmailField = z
41+
.string()
42+
.refine((val) => val === "" || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), {
43+
message: "Enter a valid email address",
44+
});
45+
4046
const formSchema = z
4147
.object({
4248
appName: z.string(),
@@ -51,6 +57,8 @@ const formSchema = z
5157
errorPageDescription: z.string(),
5258
footerText: z.string(),
5359
ogImageUrl: safeUrlField,
60+
passwordResetGuide: z.string().optional(),
61+
supportEmail: safeEmailField.optional(),
5462
logoLightUrl: z.string(),
5563
loginLogoLightUrl: z.string(),
5664
faviconLightUrl: z.string(),
@@ -218,6 +226,8 @@ export function WhitelabelingSettings() {
218226
errorPageDescription: "",
219227
footerText: "",
220228
ogImageUrl: "",
229+
passwordResetGuide: "",
230+
supportEmail: "",
221231
logoLightUrl: "",
222232
loginLogoLightUrl: "",
223233
faviconLightUrl: "",
@@ -241,6 +251,8 @@ export function WhitelabelingSettings() {
241251
errorPageDescription: data.errorPageDescription ?? "",
242252
footerText: data.footerText ?? "",
243253
ogImageUrl: data.ogImageUrl ?? "",
254+
passwordResetGuide: data.passwordResetGuide ?? "",
255+
supportEmail: data.supportEmail ?? "",
244256
logoLightUrl: data.logoLightUrl ?? "",
245257
loginLogoLightUrl: data.loginLogoLightUrl ?? "",
246258
faviconLightUrl: data.faviconLightUrl ?? "",
@@ -275,6 +287,8 @@ export function WhitelabelingSettings() {
275287
errorPageDescription: values.errorPageDescription || null,
276288
footerText: values.footerText || null,
277289
ogImageUrl: values.ogImageUrl || null,
290+
passwordResetGuide: values.passwordResetGuide || null,
291+
supportEmail: values.supportEmail || null,
278292
logoLightUrl: values.logoLightUrl || null,
279293
loginLogoLightUrl: values.loginLogoLightUrl || null,
280294
faviconLightUrl: values.faviconLightUrl || null,

‎apps/dokploy/pages/index.tsx‎

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ export default function Home({ IS_CLOUD, enforceSSO }: Props) {
7676
const twoFactorCursor = Math.min(twoFactorCaret, twoFactorCode.length);
7777
const [isBackupCodeModalOpen, setIsBackupCodeModalOpen] = useState(false);
7878
const [backupCode, setBackupCode] = useState("");
79+
const [showForgotPassword, setShowForgotPassword] = useState(false);
80+
81+
const hasCustomPasswordReset = !IS_CLOUD && !!whitelabeling?.supportEmail;
7982
const loginForm = useForm<LoginForm>({
8083
resolver: zodResolver(LoginSchema),
8184
defaultValues: {
@@ -294,6 +297,18 @@ export default function Home({ IS_CLOUD, enforceSSO }: Props) {
294297
</>
295298
);
296299

300+
const forgotPasswordContent = (
301+
<div className="flex flex-col gap-4">
302+
<p className="text-sm text-muted-foreground whitespace-pre-line">
303+
{whitelabeling?.passwordResetGuide ||
304+
"To reset your password, please contact your administrator. They will help you regain access to your account."}
305+
</p>
306+
<Button className="w-full" asChild>
307+
<a href={`mailto:${whitelabeling?.supportEmail}`}>Get Support</a>
308+
</Button>
309+
</div>
310+
);
311+
297312
return (
298313
<>
299314
<div className="flex flex-col space-y-2 text-center">
@@ -326,15 +341,19 @@ export default function Home({ IS_CLOUD, enforceSSO }: Props) {
326341
)}
327342
<CardContent className="p-0">
328343
{!isTwoFactor ? (
329-
<>
330-
{enforceSSO ? (
331-
<SignInWithSSO enforce />
332-
) : showSignInWithSSO ? (
333-
<SignInWithSSO>{loginContent}</SignInWithSSO>
334-
) : (
335-
loginContent
336-
)}
337-
</>
344+
showForgotPassword ? (
345+
forgotPasswordContent
346+
) : (
347+
<>
348+
{enforceSSO ? (
349+
<SignInWithSSO enforce />
350+
) : showSignInWithSSO ? (
351+
<SignInWithSSO>{loginContent}</SignInWithSSO>
352+
) : (
353+
loginContent
354+
)}
355+
</>
356+
)
338357
) : (
339358
<>
340359
<form
@@ -495,15 +514,25 @@ export default function Home({ IS_CLOUD, enforceSSO }: Props) {
495514
>
496515
Lost your password?
497516
</Link>
498-
) : (
517+
) : hasCustomPasswordReset && !isTwoFactor ? (
518+
<button
519+
type="button"
520+
className="hover:underline text-muted-foreground"
521+
onClick={() => setShowForgotPassword((prev) => !prev)}
522+
>
523+
{showForgotPassword
524+
? "Go back to login"
525+
: "Lost your password?"}
526+
</button>
527+
) : !hasCustomPasswordReset ? (
499528
<Link
500529
className="hover:underline text-muted-foreground"
501530
href="https://docs.dokploy.com/docs/core/reset-password"
502531
target="_blank"
503532
>
504533
Lost your password?
505534
</Link>
506-
)}
535+
) : null}
507536
</div>
508537
</div>
509538
<div className="p-2" />

‎apps/dokploy/server/api/routers/proprietary/whitelabeling.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ export const whitelabelingRouter = createTRPCRouter({
8787
errorPageDescription: null,
8888
ogImageUrl: null,
8989
footerText: null,
90+
passwordResetGuide: null,
91+
supportEmail: null,
9092
logoLightUrl: null,
9193
loginLogoLightUrl: null,
9294
faviconLightUrl: null,

‎packages/server/src/db/schema/web-server-settings.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ export const webServerSettings = pgTable("webServerSettings", {
9797
errorPageDescription: string | null;
9898
footerText: string | null;
9999
ogImageUrl: string | null;
100+
passwordResetGuide: string | null;
101+
supportEmail: string | null;
100102
logoLightUrl: string | null;
101103
loginLogoLightUrl: string | null;
102104
faviconLightUrl: string | null;
@@ -115,6 +117,8 @@ export const webServerSettings = pgTable("webServerSettings", {
115117
errorPageDescription: null,
116118
footerText: null,
117119
ogImageUrl: null,
120+
passwordResetGuide: null,
121+
supportEmail: null,
118122
logoLightUrl: null,
119123
loginLogoLightUrl: null,
120124
faviconLightUrl: null,
@@ -248,6 +252,8 @@ export const whitelabelingConfigSchema = z.object({
248252
errorPageDescription: z.string().nullable(),
249253
footerText: z.string().nullable(),
250254
ogImageUrl: safeUrl,
255+
passwordResetGuide: z.string().nullable(),
256+
supportEmail: z.string().email().nullable(),
251257
logoLightUrl: safeUrl,
252258
loginLogoLightUrl: safeUrl,
253259
faviconLightUrl: safeUrl,

‎packages/server/src/services/proprietary/whitelabeling.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface PublicWhitelabelingConfig {
1414
errorPageTitle: string | null;
1515
errorPageDescription: string | null;
1616
footerText: string | null;
17+
passwordResetGuide: string | null;
18+
supportEmail: string | null;
1719
logoLightUrl: string | null;
1820
loginLogoLightUrl: string | null;
1921
faviconLightUrl: string | null;
@@ -58,6 +60,8 @@ export const getPublicWhitelabelingConfig =
5860
errorPageTitle: config.errorPageTitle,
5961
errorPageDescription: config.errorPageDescription,
6062
footerText: config.footerText,
63+
passwordResetGuide: config.passwordResetGuide ?? null,
64+
supportEmail: config.supportEmail ?? null,
6165
dualModeBranding: config.dualModeBranding ?? false,
6266
logoLightUrl: config.dualModeBranding
6367
? (config.logoLightUrl ?? null)

0 commit comments

Comments
 (0)