Skip to content

Commit 60355c7

Browse files
nahSystemuhjball
andauthored
feat: disable email functions if email not enabled (#195)
* fix: disabled email functionality unless email sending enabled * feat: missing locale only russian complete * chore: add ENABLE_EMAIL to docker compose * chore: add translations * fix: removed new env variable, and used SMTP_HOST instead for checking * Revert "fix: removed new env variable, and used SMTP_HOST instead for checking" This reverts commit 5f75237. * feat: allow inviting members via link * refactor: update env to disable email * chore: update translations --------- Co-authored-by: Henry <henry_ball@hotmail.co.uk>
1 parent b159b48 commit 60355c7

File tree

22 files changed

+654
-506
lines changed

22 files changed

+654
-506
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# https://github.com/kanbn/kan?tab=readme-ov-file#environment-variables-)
22

3-
# Required environment variables
3+
# Required environment variables
44
NEXT_PUBLIC_BASE_URL= # e.g. https://kan.bn
55
BETTER_AUTH_SECRET= # Random 32+ char string (can gen with: openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
66

@@ -18,6 +18,9 @@ SMTP_PASSWORD=
1818
EMAIL_FROM= # e.g. "Kan <hello@mail.kan.bn>"
1919
SMTP_SECURE= # set to "false" to use port 587
2020

21+
# Switch email features off entirely (optional)
22+
NEXT_PUBLIC_DISABLE_EMAIL=
23+
2124
# S3 storage (optional)
2225
S3_REGION=
2326
S3_ENDPOINT=

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ pnpm dev
147147
| `SMTP_USER` | SMTP username/email | No | `resend` |
148148
| `SMTP_PASSWORD` | SMTP password/token | No | `re_xxxx` |
149149
| `SMTP_SECURE` | Use secure SMTP connection (defaults to true if not set) | For Email | `true` |
150+
| `NEXT_PUBLIC_DISABLE_EMAIL` | To disable all email features | For Email | `true` |
150151
| `NEXT_PUBLIC_BASE_URL` | Base URL of your installation | Yes | `http://localhost:3000` |
151152
| `BETTER_AUTH_SECRET` | Auth encryption secret | Yes | Random 32+ char string |
152153
| `BETTER_AUTH_TRUSTED_ORIGINS` | Allowed callback origins | No | `http://localhost:3000,http://localhost:3001` |

apps/web/i18n.lock

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ checksums:
112112
Create%20board/singular: 155b62818bfab0e34f0089e1b34a32f3
113113
Create%20card/singular: 32792935dd5837a9433909b04021c22b
114114
Create%20checklist/singular: 5cbca15a7004558c4e6d381f83a34da2
115+
Create%20invite%20link/singular: bef23fe786978f7abf78dece49a35a26
115116
Create%20label/singular: c64e8180fa956f005edc1fd9b8e65abb
116117
Create%20list/singular: 858c3d514aa95765ec82114393199144
117118
Create%20new%20board/singular: cbb87b1f4cc46b336ad9510c9f19407c
@@ -128,6 +129,7 @@ checksums:
128129
Custom%20workspace%20URL/singular: 7ba841d0946eb04fa3d17d74365be37b
129130
Customer%20Support/singular: 50e3c77e22e41061ca85ea2f02625a2e
130131
Dark/singular: 73e6e208ba628b26e90fcf6dce15e1b2
132+
Deactivate%20invite%20link/singular: 8cf4bf82e153e4dc82d026448cb4dd10
131133
Delete/singular: 8bcf303dd10a645b5baacb02b47d72c9
132134
Delete%20account/singular: a9d11113f1a1d7e20582bdcc9633bcef
133135
Delete%20board/singular: e915cd160651ad4b61a67e8da0737c23
@@ -306,6 +308,8 @@ checksums:
306308
Own%20your%20data/singular: cc2178dac4bdf6b07f030cfc2a7510e6
307309
Part-time/singular: 213d63da450f35dabb3ab0e35e29feed
308310
Password%20Changed/singular: 1fcebe9ddb46f722a57f195efddc695d
311+
Password%20is%20required%20to%20login./singular: a09c76294ca7b27b38658b34618a7119
312+
Password%20is%20required%20to%20sign%20up./singular: 3fa1abbf2e6ac8ac4d18c215191bdbcc
309313
Password%20must%20be%20at%20least%208%20characters/singular: 4c30501d085eaccea47af34212bb26a7
310314
Passwords%20do%20not%20match/singular: 37ca1f4e0afc9a0b8e9617f767103c92
311315
Paused/singular: edb1f7b7219e1c9b7aa67159090d6991
@@ -367,7 +371,6 @@ checksums:
367371
Settings%20%7C%20Billing/singular: e44cba741d5414035a0b499c5766c203
368372
Settings%20%7C%20Integrations/singular: d04992e28016452f6d3d7dcc0b592415
369373
Settings%20%7C%20Workspace/singular: 5d0bacf7ff696da940f232df45edfd39
370-
Share%20invite%20link/singular: ec5081a1f4e49fd9d782e770438f703b
371374
Sign%20in/singular: cb8757c7450e17de1e226e82fb0fa4a2
372375
Sign%20In/singular: ec7b8f314fe9bc6591006707484ede61
373376
Sign%20Up/singular: 0dd2ae69be4618c1f9e615774a4509ca

apps/web/src/components/AuthForm.tsx

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { t } from "@lingui/core/macro";
55
import { Trans } from "@lingui/react/macro";
66
import { useQuery } from "@tanstack/react-query";
77
import { env } from "next-runtime-env";
8-
import { useEffect, useState } from "react";
8+
import { useEffect, useMemo, useRef, useState } from "react";
99
import { useForm } from "react-hook-form";
1010
import {
1111
FaApple,
@@ -156,10 +156,12 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
156156
const [isLoginWithProviderPending, setIsLoginWithProviderPending] =
157157
useState<null | AuthProvider>(null);
158158
const [isCredentialsEnabled, setIsCredentialsEnabled] = useState(false);
159+
const [isEmailSendingEnabled, setIsEmailSendingEnabled] = useState(false);
159160
const [isLoginWithEmailPending, setIsLoginWithEmailPending] = useState(false);
160161
const [loginError, setLoginError] = useState<string | null>(null);
161162
const { showPopup } = usePopup();
162163
const oidcProviderName = "OIDC";
164+
const passwordRef = useRef<HTMLInputElement | null>(null);
163165

164166
const redirect = useSearchParams().get("next");
165167
const callbackURL = redirect ?? "/boards";
@@ -168,6 +170,9 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
168170
useEffect(() => {
169171
const credentialsAllowed =
170172
env("NEXT_PUBLIC_ALLOW_CREDENTIALS")?.toLowerCase() === "true";
173+
const emailSendingEnabled =
174+
env("NEXT_PUBLIC_DISABLE_EMAIL")?.toLowerCase() !== "true";
175+
setIsEmailSendingEnabled(emailSendingEnabled);
171176
setIsCredentialsEnabled(credentialsAllowed);
172177
}, []);
173178

@@ -187,7 +192,7 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
187192

188193
const handleLoginWithEmail = async (
189194
email: string,
190-
password?: string,
195+
password?: string | null,
191196
name?: string,
192197
) => {
193198
setIsLoginWithEmailPending(true);
@@ -230,16 +235,26 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
230235
);
231236
}
232237
} else {
233-
await authClient.signIn.magicLink(
234-
{
235-
email,
236-
callbackURL,
237-
},
238-
{
239-
onSuccess: () => setIsMagicLinkSent(true, email),
240-
onError: ({ error }) => setLoginError(error.message),
241-
},
242-
);
238+
// Only allow magic link if email sending is enabled and not in sign up mode
239+
if (isEmailSendingEnabled && !isSignUp) {
240+
await authClient.signIn.magicLink(
241+
{
242+
email,
243+
callbackURL,
244+
},
245+
{
246+
onSuccess: () => setIsMagicLinkSent(true, email),
247+
onError: ({ error }) => setLoginError(error.message),
248+
},
249+
);
250+
} else {
251+
// Provide a clear error feedback when password omitted but magic link unavailable
252+
setLoginError(
253+
isSignUp
254+
? t`Password is required to sign up.`
255+
: t`Password is required to login.`,
256+
);
257+
}
243258
}
244259

245260
setIsLoginWithEmailPending(false);
@@ -276,11 +291,43 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
276291
};
277292

278293
const onSubmit = async (values: FormValues) => {
279-
await handleLoginWithEmail(values.email, values.password, values.name);
294+
// Treat empty password string as undefined to trigger magic link path
295+
const sanitizedPassword = values.password?.trim()
296+
? values.password
297+
: undefined;
298+
await handleLoginWithEmail(values.email, sanitizedPassword, values.name);
280299
};
281300

282301
const password = watch("password");
283302

303+
// Determine if we should operate in magic link mode for current form state (login only)
304+
const isMagicLinkMode = useMemo(() => {
305+
// Magic link only viable when email sending enabled AND not sign up.
306+
if (!isEmailSendingEnabled || isSignUp) return false;
307+
// If credentials disabled we always default to magic link.
308+
if (!isCredentialsEnabled) return true;
309+
// Credentials enabled: user chooses magic link by leaving password blank.
310+
return !password;
311+
}, [isEmailSendingEnabled, isSignUp, isCredentialsEnabled, password]);
312+
313+
// Auto-focus password field when an error indicates it's required
314+
useEffect(() => {
315+
if (!isCredentialsEnabled) return;
316+
// Focus when: sign up and missing password; login error requiring password; validation error on password.
317+
const pwdEmpty = (password ?? "").length === 0;
318+
let needsPassword = false;
319+
if (isSignUp && pwdEmpty) {
320+
needsPassword = true;
321+
} else if (loginError?.toLowerCase().includes("password")) {
322+
needsPassword = true;
323+
} else if (errors.password) {
324+
needsPassword = true;
325+
}
326+
if (needsPassword && passwordRef.current) {
327+
passwordRef.current.focus();
328+
}
329+
}, [isSignUp, password, loginError, errors.password, isCredentialsEnabled]);
330+
284331
return (
285332
<div className="space-y-6">
286333
{socialProviders?.length !== 0 && (
@@ -351,7 +398,7 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
351398
/>
352399
{errors.password && (
353400
<p className="mt-2 text-xs text-red-400">
354-
{t`Please enter a valid password`}
401+
{errors.password.message ?? t`Please enter a valid password`}
355402
</p>
356403
)}
357404
</div>
@@ -368,9 +415,7 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
368415
variant="secondary"
369416
>
370417
{isSignUp ? t`Sign up with ` : t`Continue with `}
371-
{!isCredentialsEnabled || (password && password.length !== 0)
372-
? t`email`
373-
: t`magic link`}
418+
{isMagicLinkMode ? t`magic link` : t`email`}
374419
</Button>
375420
</div>
376421
</form>

0 commit comments

Comments
 (0)