Masquer la saisie des PIN sysadmin

This commit is contained in:
Daniel Allaire 2026-06-01 12:33:51 -04:00
parent ddb391c673
commit ff1700ec5e
2 changed files with 107 additions and 22 deletions

View file

@ -18,6 +18,8 @@ export default function GestionGroupes() {
const [busy, setBusy] = useState(false);
const [msg, setMsg] = useState('');
const [error, setError] = useState('');
const [pinFormOuvert, setPinFormOuvert] = useState(false);
const [pinForm, setPinForm] = useState({ actuel: '', nouveau: '', confirmation: '' });
const charger = async () => {
setLoading(true);
@ -167,17 +169,17 @@ export default function GestionGroupes() {
}
};
const changerPinSysadmin = async () => {
const pinActuel = window.prompt('PIN actuel');
if (!pinActuel) return;
const nouveauPin = window.prompt('Nouveau PIN sysadmin (4 chiffres)');
if (!nouveauPin) return;
if (!/^[0-9]{4}$/.test(nouveauPin)) {
const changerPinSysadmin = async (e) => {
e.preventDefault();
if (!/^[0-9]{4}$/.test(pinForm.actuel)) {
setError('Le PIN actuel doit contenir 4 chiffres');
return;
}
if (!/^[0-9]{4}$/.test(pinForm.nouveau)) {
setError('Le nouveau PIN doit contenir 4 chiffres');
return;
}
const confirmation = window.prompt('Confirmer le nouveau PIN');
if (confirmation !== nouveauPin) {
if (pinForm.confirmation !== pinForm.nouveau) {
setError('La confirmation ne correspond pas');
return;
}
@ -186,10 +188,12 @@ export default function GestionGroupes() {
setMsg('');
try {
const res = await api.post('/admin/sysadmin-pin', {
pin_actuel: pinActuel,
nouveau_pin: nouveauPin,
pin_actuel: pinForm.actuel,
nouveau_pin: pinForm.nouveau,
});
setMsg(res.message || 'PIN sysadmin mis à jour');
setPinForm({ actuel: '', nouveau: '', confirmation: '' });
setPinFormOuvert(false);
} catch (e) {
setError(e.message);
} finally {
@ -214,7 +218,11 @@ export default function GestionGroupes() {
Créer un groupe
</button>
<button
onClick={changerPinSysadmin}
onClick={() => {
setPinFormOuvert((ouvert) => !ouvert);
setPinForm({ actuel: '', nouveau: '', confirmation: '' });
setError('');
}}
disabled={busy}
className="w-full min-h-[48px] p-3 bg-gray-800 text-white rounded-xl text-sm font-bold disabled:opacity-40"
>
@ -222,6 +230,53 @@ export default function GestionGroupes() {
</button>
</div>
{pinFormOuvert && (
<form onSubmit={changerPinSysadmin} className="bg-white rounded-2xl p-4 shadow-sm space-y-3">
<h2 className="font-bold text-gray-800">Changer le PIN sysadmin</h2>
<input
type="password"
inputMode="numeric"
pattern="[0-9]*"
maxLength={4}
placeholder="PIN actuel"
value={pinForm.actuel}
onChange={(e) => setPinForm({ ...pinForm, actuel: e.target.value.replace(/\D/g, '').slice(0, 4) })}
className="w-full p-3 bg-gray-50 rounded-xl border-0 text-center tracking-[0.8em]"
autoComplete="current-password"
/>
<input
type="password"
inputMode="numeric"
pattern="[0-9]*"
maxLength={4}
placeholder="Nouveau PIN"
value={pinForm.nouveau}
onChange={(e) => setPinForm({ ...pinForm, nouveau: e.target.value.replace(/\D/g, '').slice(0, 4) })}
className="w-full p-3 bg-gray-50 rounded-xl border-0 text-center tracking-[0.8em]"
autoComplete="new-password"
/>
<input
type="password"
inputMode="numeric"
pattern="[0-9]*"
maxLength={4}
placeholder="Confirmer le nouveau PIN"
value={pinForm.confirmation}
onChange={(e) => setPinForm({ ...pinForm, confirmation: e.target.value.replace(/\D/g, '').slice(0, 4) })}
className="w-full p-3 bg-gray-50 rounded-xl border-0 text-center tracking-[0.8em]"
autoComplete="new-password"
/>
<div className="grid grid-cols-2 gap-2">
<button type="submit" disabled={busy} className="p-3 bg-green-600 text-white rounded-xl text-sm font-bold disabled:opacity-40">
Enregistrer
</button>
<button type="button" onClick={() => { setPinFormOuvert(false); setPinForm({ actuel: '', nouveau: '', confirmation: '' }); }} className="p-3 bg-gray-100 text-gray-600 rounded-xl text-sm font-bold">
Annuler
</button>
</div>
</form>
)}
{form && (
<div className="bg-white rounded-2xl p-4 shadow-sm space-y-3">
<h2 className="font-bold text-gray-800">{editionId === 'nouveau' ? 'Nouveau groupe' : 'Modifier le groupe'}</h2>

View file

@ -14,6 +14,8 @@ export default function Login() {
const [groupeId, setGroupeId] = useState(localStorage.getItem('gm_groupe_id') || '');
const [groupeOuvert, setGroupeOuvert] = useState('');
const [seSouvenir, setSeSouvenir] = useState(!!localStorage.getItem('gm_courriel'));
const [sysadminOuvert, setSysadminOuvert] = useState(false);
const [sysadminPin, setSysadminPin] = useState('');
useEffect(() => {
if (groupes.length === 0) loadGroupes();
@ -47,11 +49,11 @@ export default function Login() {
} catch {}
};
const ouvrirSysadmin = async () => {
const pin = window.prompt('PIN Sysadmin');
if (!pin) return;
const ouvrirSysadmin = async (e) => {
e.preventDefault();
if (sysadminPin.length !== 4) return;
try {
const data = await api.post('/admin/sysadmin-login', { pin });
const data = await api.post('/admin/sysadmin-login', { pin: sysadminPin });
api.setToken(data.access_token, false);
localStorage.setItem('gm_sysadmin_instance', '1');
localStorage.removeItem('gm_groupe_id');
@ -72,6 +74,8 @@ export default function Login() {
setPage('G14');
} catch (e) {
useStore.setState({ error: e.message });
} finally {
setSysadminPin('');
}
};
@ -186,13 +190,39 @@ export default function Login() {
Découvrir l'application
</a>
{sysadminOuvert && (
<form onSubmit={ouvrirSysadmin} className="mb-3 rounded-2xl bg-white/95 p-3 shadow-lg">
<input
type="password"
inputMode="numeric"
pattern="[0-9]*"
maxLength={4}
placeholder="PIN sysadmin"
value={sysadminPin}
onChange={(e) => setSysadminPin(e.target.value.replace(/\D/g, '').slice(0, 4))}
className="mb-2 w-full rounded-xl border-0 bg-gray-50 p-3 text-center text-lg tracking-[0.8em] focus:ring-2 focus:ring-aa-blue"
autoFocus
/>
<div className="grid grid-cols-2 gap-2">
<button type="submit" disabled={sysadminPin.length !== 4} className="rounded-xl bg-aa-blue p-3 text-sm font-bold text-white disabled:opacity-40">
Ouvrir
</button>
<button type="button" onClick={() => { setSysadminOuvert(false); setSysadminPin(''); }} className="rounded-xl bg-gray-100 p-3 text-sm font-bold text-gray-600">
Annuler
</button>
</div>
</form>
)}
{!sysadminOuvert && (
<button
type="button"
onClick={ouvrirSysadmin}
onClick={() => setSysadminOuvert(true)}
className="mx-auto mb-3 block px-3 py-2 text-xs font-semibold text-blue-200/80"
>
Sysadmin
</button>
)}
<p className="text-center text-xs text-blue-300">100% logiciel libre · Hébergé au Québec</p>
</div>
</div>