feat(auth): server-side sessions and current password for account changes (FLAW-30, FLAW-31)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m52s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m52s
- Every login is a session (device, IP, last use); the token's jti is its ID. Sessions end on logout, per device from the profile, or after 30 idle days. - Tokens older than a day are renewed via the X-Session-Token header. - Changing password or login e-mail needs the current password; a new password logs out all other devices and mails the old address. - Demo reset keeps the demo visitors' sessions. - revoked_tokens is replaced by sessions (migration 4); everyone is logged out once after the upgrade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b2ecda9896
commit
1af3a6e90a
25 files changed
+705
-75
No files matched your search
@@ -27,6 +27,7 @@ import {
|
||||
Team,
|
||||
TaskList,
|
||||
TaskListShare,
|
||||
Session,
|
||||
Thought,
|
||||
SubTask,
|
||||
SearchResults,
|
||||
@@ -145,6 +146,10 @@ async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
window.dispatchEvent(new CustomEvent('mindsift:unauthorized'));
|
||||
}
|
||||
|
||||
// Sliding session: the server hands out a fresh token once a day (or after a password change)
|
||||
const renewed = res.headers.get('X-Session-Token');
|
||||
if (renewed && res.ok) setToken(renewed);
|
||||
|
||||
if (!res.ok) {
|
||||
let errMsg = `HTTP ${res.status}`;
|
||||
try {
|
||||
@@ -209,6 +214,14 @@ export const api = {
|
||||
}
|
||||
},
|
||||
|
||||
async listSessions(): Promise<Session[]> {
|
||||
return ensureArray(await request<Session[]>('/api/v1/auth/sessions'));
|
||||
},
|
||||
|
||||
async endSession(id: string): Promise<void> {
|
||||
await request(`/api/v1/auth/sessions/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||
},
|
||||
|
||||
async revokeAllSessions(): Promise<void> {
|
||||
await request<{ success: boolean }>('/api/v1/auth/revoke-all', { method: 'POST' });
|
||||
},
|
||||
@@ -270,6 +283,7 @@ export const api = {
|
||||
displayName?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
currentPassword?: string;
|
||||
avatarUrl?: string;
|
||||
alarmChannels?: string[];
|
||||
alarmEmail?: string;
|
||||
|
||||
Reference in new issue
Block a user