Nachrichten: Senden-Button entfernt, Login-Sicherheit: IPs bei Lockout, Attempts-Bereinigung
This commit is contained in:
@@ -97,7 +97,7 @@ router.put('/users/:id/reset-password', authenticate, requireAdmin, (req, res) =
|
|||||||
|
|
||||||
const getSetting = k => db.prepare('SELECT value FROM admin_settings WHERE key=?').get(k)?.value;
|
const getSetting = k => db.prepare('SELECT value FROM admin_settings WHERE key=?').get(k)?.value;
|
||||||
|
|
||||||
// GET gesperrte Konten
|
// GET gesperrte Konten mit IPs
|
||||||
router.get('/lockouts', authenticate, requireAdmin, (req, res) => {
|
router.get('/lockouts', authenticate, requireAdmin, (req, res) => {
|
||||||
const locked = db.prepare(`
|
const locked = db.prepare(`
|
||||||
SELECT id, username, failed_attempts, locked_until, last_failed_at
|
SELECT id, username, failed_attempts, locked_until, last_failed_at
|
||||||
@@ -105,11 +105,25 @@ router.get('/lockouts', authenticate, requireAdmin, (req, res) => {
|
|||||||
WHERE locked_until IS NOT NULL
|
WHERE locked_until IS NOT NULL
|
||||||
ORDER BY locked_until DESC
|
ORDER BY locked_until DESC
|
||||||
`).all();
|
`).all();
|
||||||
res.json(locked);
|
|
||||||
|
// IPs aus login_attempts hinzufügen
|
||||||
|
const result = locked.map(u => {
|
||||||
|
const ips = db.prepare(`
|
||||||
|
SELECT DISTINCT ip FROM login_attempts
|
||||||
|
WHERE username=? AND success=0
|
||||||
|
ORDER BY created_at DESC LIMIT 10
|
||||||
|
`).all(u.username).map(r => r.ip).filter(Boolean);
|
||||||
|
return { ...u, ips };
|
||||||
|
});
|
||||||
|
res.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
// DELETE Sperre aufheben
|
// DELETE Sperre aufheben + Login-Versuche löschen
|
||||||
router.delete('/lockouts/:userId', authenticate, requireAdmin, (req, res) => {
|
router.delete('/lockouts/:userId', authenticate, requireAdmin, (req, res) => {
|
||||||
|
const user = db.prepare('SELECT username FROM users WHERE id=?').get(req.params.userId);
|
||||||
|
if (user) {
|
||||||
|
db.prepare('DELETE FROM login_attempts WHERE username=?').run(user.username);
|
||||||
|
}
|
||||||
db.prepare('UPDATE users SET failed_attempts=0, locked_until=NULL, last_failed_at=NULL WHERE id=?')
|
db.prepare('UPDATE users SET failed_attempts=0, locked_until=NULL, last_failed_at=NULL WHERE id=?')
|
||||||
.run(req.params.userId);
|
.run(req.params.userId);
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
|
|||||||
@@ -72,8 +72,9 @@ router.post('/login', (req, res) => {
|
|||||||
lockedUntil: until.toISOString(),
|
lockedUntil: until.toISOString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Abgelaufen
|
// Abgelaufen → zurücksetzen + Attempts löschen
|
||||||
db.prepare('UPDATE users SET failed_attempts=0, locked_until=NULL, last_failed_at=NULL WHERE id=?').run(user.id);
|
db.prepare('UPDATE users SET failed_attempts=0, locked_until=NULL, last_failed_at=NULL WHERE id=?').run(user.id);
|
||||||
|
db.prepare('DELETE FROM login_attempts WHERE username=?').run(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Passwort prüfen
|
// Passwort prüfen
|
||||||
@@ -96,8 +97,9 @@ router.post('/login', (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Erfolgreich → Zähler zurücksetzen
|
// Erfolgreich → Zähler zurücksetzen + Attempts löschen
|
||||||
db.prepare('UPDATE users SET failed_attempts=0, locked_until=NULL, last_failed_at=NULL WHERE id=?').run(user.id);
|
db.prepare('UPDATE users SET failed_attempts=0, locked_until=NULL, last_failed_at=NULL WHERE id=?').run(user.id);
|
||||||
|
db.prepare('DELETE FROM login_attempts WHERE username=?').run(username);
|
||||||
|
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{ id: user.id, username: user.username, role: user.role },
|
{ id: user.id, username: user.username, role: user.role },
|
||||||
|
|||||||
@@ -1068,6 +1068,15 @@ function SecuritySettingsAdmin({ toast }) {
|
|||||||
Letzter Versuch: {fmtDate(u.last_failed_at)}
|
Letzter Versuch: {fmtDate(u.last_failed_at)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{u.ips?.length > 0 && (
|
||||||
|
<div style={{ marginTop:4, display:'flex', flexWrap:'wrap', gap:4 }}>
|
||||||
|
{u.ips.map((ip,i) => (
|
||||||
|
<span key={i} style={{ background:'rgba(255,107,157,0.1)', border:'1px solid rgba(255,107,157,0.2)',
|
||||||
|
borderRadius:5, padding:'1px 7px', color:'rgba(255,107,157,0.7)',
|
||||||
|
fontFamily:'monospace', fontSize:9 }}>{ip}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={()=>unlock(u.id)}
|
<button onClick={()=>unlock(u.id)}
|
||||||
style={{ ...S.btn('#4ecdc4', true), flexShrink:0, fontSize:11 }}>
|
style={{ ...S.btn('#4ecdc4', true), flexShrink:0, fontSize:11 }}>
|
||||||
|
|||||||
@@ -479,11 +479,7 @@ export default function Nachrichten({ toast, mobile }) {
|
|||||||
background: showPicker ? 'rgba(78,205,196,0.12)' : 'rgba(255,255,255,0.05)',
|
background: showPicker ? 'rgba(78,205,196,0.12)' : 'rgba(255,255,255,0.05)',
|
||||||
border:`1px solid ${showPicker ? 'rgba(78,205,196,0.3)' : 'rgba(255,255,255,0.1)'}`,
|
border:`1px solid ${showPicker ? 'rgba(78,205,196,0.3)' : 'rgba(255,255,255,0.1)'}`,
|
||||||
borderRadius:8, cursor:'pointer', fontSize:17, lineHeight:1,
|
borderRadius:8, cursor:'pointer', fontSize:17, lineHeight:1,
|
||||||
padding:'5px 8px', flexShrink:0 }}>😊</button>
|
padding:'5px 8px', flexShrink:0, alignSelf:'flex-end' }}>😊</button>
|
||||||
<button onClick={send} disabled={sending || !text.trim()} style={{
|
|
||||||
...S.btn('#4ecdc4'), flex:1, padding:'0 14px',
|
|
||||||
opacity: sending || !text.trim() ? 0.3 : 1, fontSize:17, lineHeight:1,
|
|
||||||
}}>↑</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user