fix(sessions): prune stale sessions on login to prevent accumulation
Some checks failed
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 36s
Release / Test backend (push) Successful in 56s
Release / Check ui (push) Successful in 50s
Release / Docker / caddy (push) Successful in 40s
CI / UI (pull_request) Failing after 37s
CI / Backend (pull_request) Successful in 48s
Release / Docker / runner (push) Failing after 32s
Release / Docker / backend (push) Successful in 2m39s
Release / Docker / ui (push) Successful in 1m45s
Release / Gitea Release (push) Has been skipped
Some checks failed
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 36s
Release / Test backend (push) Successful in 56s
Release / Check ui (push) Successful in 50s
Release / Docker / caddy (push) Successful in 40s
CI / UI (pull_request) Failing after 37s
CI / Backend (pull_request) Successful in 48s
Release / Docker / runner (push) Failing after 32s
Release / Docker / backend (push) Successful in 2m39s
Release / Docker / ui (push) Successful in 1m45s
Release / Gitea Release (push) Has been skipped
Sessions not seen in 30+ days are deleted in the background each time a new session is created. No cron job needed — self-cleaning on login. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1012,6 +1012,8 @@ export async function createUserSession(
|
|||||||
throw new Error(`Failed to create session: ${res.status}`);
|
throw new Error(`Failed to create session: ${res.status}`);
|
||||||
}
|
}
|
||||||
const rec = (await res.json()) as { id: string };
|
const rec = (await res.json()) as { id: string };
|
||||||
|
// Best-effort: prune stale sessions in the background so the list doesn't grow forever
|
||||||
|
pruneStaleUserSessions(userId).catch(() => {});
|
||||||
return rec.id;
|
return rec.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,6 +1050,28 @@ export async function listUserSessions(userId: string): Promise<UserSession[]> {
|
|||||||
return listAll<UserSession>('user_sessions', `user_id="${userId}"`, '-last_seen');
|
return listAll<UserSession>('user_sessions', `user_id="${userId}"`, '-last_seen');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete sessions for a user that haven't been seen in the last `days` days.
|
||||||
|
* Called on login so the list self-cleans without a separate cron job.
|
||||||
|
*/
|
||||||
|
async function pruneStaleUserSessions(userId: string, days = 30): Promise<void> {
|
||||||
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
||||||
|
const stale = await listAll<UserSession>(
|
||||||
|
'user_sessions',
|
||||||
|
`user_id="${userId}" && last_seen<"${cutoff}"`
|
||||||
|
);
|
||||||
|
if (stale.length === 0) return;
|
||||||
|
const token = await getToken();
|
||||||
|
await Promise.all(
|
||||||
|
stale.map((s) =>
|
||||||
|
fetch(`${PB_URL}/api/collections/user_sessions/records/${s.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
}).catch(() => {})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revoke (delete) a specific session by its PocketBase record ID.
|
* Revoke (delete) a specific session by its PocketBase record ID.
|
||||||
* Only allows deletion if the session belongs to the given userId.
|
* Only allows deletion if the session belongs to the given userId.
|
||||||
|
|||||||
Reference in New Issue
Block a user