feat(auth): confirm the e-mail address of new accounts by link
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m21s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m21s
- Setting / REGISTRATION_VERIFY_EMAIL; only with SMTP and BASE_URL; invites skip it - Hashed one-time tokens (48 h), resend with cooldown, unconfirmed accounts purged - Approval modes notify admins only after the address is confirmed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1406650b7c
commit
ab97638361
25 files changed
+621
-56
No files matched your search
@@ -52,6 +52,34 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
||||
const [password, setPassword] = useState('');
|
||||
const [displayName, setDisplayName] = useState('');
|
||||
const [inviteCode, setInviteCode] = useState(linkInviteCode);
|
||||
// Link from the confirmation mail (?verifyToken=…): confirm right away and say what happens next
|
||||
useEffect(() => {
|
||||
const url = new URL(window.location.href);
|
||||
const token = url.searchParams.get('verifyToken');
|
||||
if (!token) return;
|
||||
url.searchParams.delete('verifyToken');
|
||||
window.history.replaceState({}, '', url.pathname + url.search + url.hash);
|
||||
setAuthTab('login');
|
||||
api
|
||||
.verifyEmail(token)
|
||||
.then((res) => setNotice(res.message))
|
||||
.catch((err) => setError(err.message || 'Der Bestätigungslink ist ungültig oder abgelaufen.'));
|
||||
}, []);
|
||||
// Login refused because the address is not confirmed: offer to send the link again
|
||||
const [resendState, setResendState] = useState<'idle' | 'sending' | 'sent'>('idle');
|
||||
const resendVerification = async () => {
|
||||
setResendState('sending');
|
||||
try {
|
||||
await api.resendVerification(email.trim());
|
||||
setResendState('sent');
|
||||
setError(null);
|
||||
setNotice('Falls die Adresse noch auf Bestätigung wartet, haben wir dir den Link erneut geschickt.');
|
||||
} catch (err: any) {
|
||||
setResendState('idle');
|
||||
setError(err.message || 'Senden fehlgeschlagen');
|
||||
}
|
||||
};
|
||||
|
||||
// The code is in the form now; it should not stay in the address bar (history, screenshots)
|
||||
useEffect(() => {
|
||||
if (!linkInviteCode) return;
|
||||
@@ -65,6 +93,8 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
||||
switch (authStatus.registrationMode) {
|
||||
case 'approval':
|
||||
return 'Nach der Registrierung gibt ein Administrator dein Konto frei.';
|
||||
case 'open':
|
||||
return authStatus.emailVerification ? 'Du bekommst eine E-Mail, um deine Adresse zu bestätigen.' : '';
|
||||
case 'domain':
|
||||
return domains ? `Registrierung mit E-Mail-Adressen von ${domains}.` : '';
|
||||
case 'domain+approval':
|
||||
@@ -74,6 +104,10 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
const registrationHintFull =
|
||||
registrationHint && authStatus.emailVerification && authStatus.registrationMode !== 'open'
|
||||
? `${registrationHint} Vorher bestätigst du deine Adresse per E-Mail.`
|
||||
: registrationHint;
|
||||
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -512,7 +546,17 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
||||
{error && (
|
||||
<div className="mb-4 alert-err flex items-center gap-2">
|
||||
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||
<span>{error}</span>
|
||||
<span className="flex-1">{error}</span>
|
||||
{/bestätige zuerst deine E-Mail/i.test(error) && email.trim() && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void resendVerification()}
|
||||
disabled={resendState !== 'idle'}
|
||||
className="btn-action h-8 px-2.5 text-[11px] shrink-0 disabled:opacity-50"
|
||||
>
|
||||
{resendState === 'sending' ? 'Sende…' : 'Link erneut senden'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{notice && !error && (
|
||||
@@ -718,8 +762,8 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
||||
|
||||
{(authTab === 'invite' || authTab === 'register') && (
|
||||
<form onSubmit={handleRegisterOrInvite} className="space-y-3.5">
|
||||
{authTab === 'register' && registrationHint && (
|
||||
<p className="text-[11px] text-muted bg-canvas border border-border rounded-btn px-2.5 py-2">{registrationHint}</p>
|
||||
{authTab === 'register' && registrationHintFull && (
|
||||
<p className="text-[11px] text-muted bg-canvas border border-border rounded-btn px-2.5 py-2">{registrationHintFull}</p>
|
||||
)}
|
||||
{authTab === 'invite' && (
|
||||
<div>
|
||||
|
||||
Reference in new issue
Block a user