fix(security): resolve FLAW-08 with token_version invalidation and jwt blacklist
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m0s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m0s
This commit is contained in:
1 parent
f7e70b649f
commit
50b017f387
10 files changed
+372
-63
No files matched your search
@@ -567,7 +567,11 @@ export const App: React.FC = () => {
|
||||
const handleLogout = async () => {
|
||||
triggerHaptic(20);
|
||||
disconnectWebSocket();
|
||||
clearToken();
|
||||
try {
|
||||
await api.logout();
|
||||
} catch {
|
||||
clearToken();
|
||||
}
|
||||
setUser(null);
|
||||
setShowProfileMenu(false);
|
||||
setActiveTab('triage');
|
||||
|
||||
@@ -49,6 +49,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [profileMsg, setProfileMsg] = useState<{ type: 'ok' | 'err'; text: string } | null>(null);
|
||||
const [savingProfile, setSavingProfile] = useState(false);
|
||||
const [revokingSessions, setRevokingSessions] = useState(false);
|
||||
const [isProcessingAvatar, setIsProcessingAvatar] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -155,6 +156,21 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleRevokeAllSessions = async () => {
|
||||
if (!window.confirm('Möchtest du wirklich alle aktiven Sitzungen auf allen Geräten beenden? Du wirst dabei auch auf diesem Gerät abgemeldet.')) {
|
||||
return;
|
||||
}
|
||||
setRevokingSessions(true);
|
||||
try {
|
||||
await api.revokeAllSessions();
|
||||
await api.logout();
|
||||
window.location.reload();
|
||||
} catch (err: any) {
|
||||
setProfileMsg({ type: 'err', text: err.message || 'Fehler beim Beenden der Sitzungen' });
|
||||
setRevokingSessions(false);
|
||||
}
|
||||
};
|
||||
|
||||
// --- Handlers: Alarms ---
|
||||
const handleSaveAlarms = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -530,6 +546,32 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* Sessions & Security */}
|
||||
<div className="pt-5 border-t border-border space-y-3">
|
||||
<div>
|
||||
<h3 className="text-xs font-bold text-main">Aktive Sitzungen & Sicherheit</h3>
|
||||
<p className="text-[11px] text-muted">
|
||||
Sitzungen werden durch kryptografische JWTs geschützt. Bei einer Passwortänderung werden alle alten Sitzungen automatisch ungültig.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 p-3.5 rounded-card border border-border bg-canvas">
|
||||
<div className="text-xs">
|
||||
<p className="font-semibold text-main">Alle Sitzungen sofort beenden</p>
|
||||
<p className="text-[11px] text-faint">
|
||||
Invalidiert alle ausgestellten Tokens auf allen Geräten und Browsern.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRevokeAllSessions}
|
||||
disabled={revokingSessions}
|
||||
className="btn-danger h-8 px-3 text-xs shrink-0 cursor-pointer"
|
||||
>
|
||||
{revokingSessions ? 'Beende…' : 'Alle Sitzungen beenden'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -117,6 +117,18 @@ export const api = {
|
||||
return request<AuthStatus>('/api/v1/auth/status');
|
||||
},
|
||||
|
||||
async logout(): Promise<void> {
|
||||
try {
|
||||
await request<{ success: boolean }>('/api/v1/auth/logout', { method: 'POST' });
|
||||
} finally {
|
||||
clearToken();
|
||||
}
|
||||
},
|
||||
|
||||
async revokeAllSessions(): Promise<void> {
|
||||
await request<{ success: boolean }>('/api/v1/auth/revoke-all', { method: 'POST' });
|
||||
},
|
||||
|
||||
async completeSetup(payload: {
|
||||
email: string;
|
||||
password: string;
|
||||
|
||||
Reference in new issue
Block a user