feat(auth): invite links for several people, invite by email, registration with approval or email domains
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m20s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m20s
- Invites: link /?invite=CODE, copy link, send by email, max uses with counter - Registration modes closed | approval | domain | domain+approval | open, waiting list with approve/reject - REGISTRATION_MODE / REGISTRATION_DOMAINS lock the admin setting; ALLOW_REGISTRATION=true is honoured Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
db5c5cde9b
commit
af77b9e809
24 files changed
+1077
-142
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Brain,
|
||||
Shield,
|
||||
@@ -43,13 +43,37 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
||||
const [createSampleData, setCreateSampleData] = useState(false);
|
||||
|
||||
// --- Login / Invite / Register State ---
|
||||
// An invite link (?invite=CODE) opens the invite form with the code filled in
|
||||
const [linkInviteCode] = useState(() => new URLSearchParams(window.location.search).get('invite')?.trim().toUpperCase() || '');
|
||||
const [authTab, setAuthTab] = useState<'login' | 'invite' | 'register' | 'forgot' | 'reset'>(
|
||||
resetToken ? 'reset' : 'login'
|
||||
resetToken ? 'reset' : linkInviteCode ? 'invite' : 'login'
|
||||
);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [displayName, setDisplayName] = useState('');
|
||||
const [inviteCode, setInviteCode] = useState('');
|
||||
const [inviteCode, setInviteCode] = useState(linkInviteCode);
|
||||
// The code is in the form now; it should not stay in the address bar (history, screenshots)
|
||||
useEffect(() => {
|
||||
if (!linkInviteCode) return;
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('invite');
|
||||
window.history.replaceState({}, '', url.pathname + url.search + url.hash);
|
||||
}, [linkInviteCode]);
|
||||
// Hint on the sign-up form: which addresses get in, and whether an admin approves first
|
||||
const registrationHint = (() => {
|
||||
const domains = (authStatus.registrationDomains || []).map((d) => '@' + d.replace(/^\*\./, '')).join(', ');
|
||||
switch (authStatus.registrationMode) {
|
||||
case 'approval':
|
||||
return 'Nach der Registrierung gibt ein Administrator dein Konto frei.';
|
||||
case 'domain':
|
||||
return domains ? `Registrierung mit E-Mail-Adressen von ${domains}.` : '';
|
||||
case 'domain+approval':
|
||||
return domains
|
||||
? `Mit Adressen von ${domains} geht es sofort, andere Konten gibt ein Administrator frei.`
|
||||
: 'Nach der Registrierung gibt ein Administrator dein Konto frei.';
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -167,6 +191,13 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
||||
try {
|
||||
const code = authTab === 'invite' ? inviteCode.trim() : '';
|
||||
const res = await api.register(email.trim(), password, displayName.trim(), code);
|
||||
if (res.pending || !res.user) {
|
||||
// Waiting for approval: back to the login form with the explanation
|
||||
setNotice(res.message || 'Dein Konto wartet auf die Freigabe durch einen Administrator.');
|
||||
setPassword('');
|
||||
setAuthTab('login');
|
||||
return;
|
||||
}
|
||||
onAuthenticated(res.user);
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Registrierung fehlgeschlagen');
|
||||
@@ -687,6 +718,9 @@ 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 === 'invite' && (
|
||||
<div>
|
||||
<label className="block text-muted mb-1">Einladungscode *</label>
|
||||
|
||||
Reference in new issue
Block a user