Some checks failed
Deploy / Deploy Production (push) Has been skipped
Deploy / Deploy Preview (push) Failing after 1s
Deploy / Cleanup Preview (push) Has been skipped
CI / UI / Build (pull_request) Failing after 13s
CI / Scraper / Lint (pull_request) Successful in 19s
CI / Scraper / Test (pull_request) Successful in 20s
CI / Scraper / Build (pull_request) Successful in 12s
- Add user_sessions PocketBase collection (user_id, session_id, user_agent, ip, created/last_seen) - Extend auth token format to include a per-login authSessionId (4th segment) - Hook validates authSessionId against DB on each request; revoked sessions are cleared immediately - Login/register create a session record capturing user-agent and IP - Profile page shows all active sessions with current session highlighted; per-session End/Sign out buttons - GET /api/sessions and DELETE /api/sessions/[id] endpoints for client-side revocation - Backward compatible: legacy 3-segment tokens pass through without DB check
42 lines
1.5 KiB
TypeScript
42 lines
1.5 KiB
TypeScript
import { json, error } from '@sveltejs/kit';
|
|
import type { RequestHandler } from './$types';
|
|
import { revokeUserSession } from '$lib/server/pocketbase';
|
|
import { log } from '$lib/server/logger';
|
|
|
|
/**
|
|
* DELETE /api/sessions/[id]
|
|
* Revokes a specific session by its PocketBase record ID.
|
|
* Only the owner can revoke their own sessions.
|
|
*/
|
|
export const DELETE: RequestHandler = async ({ params, locals, cookies }) => {
|
|
if (!locals.user) {
|
|
error(401, 'Not logged in');
|
|
}
|
|
|
|
const recordId = params.id;
|
|
if (!recordId) {
|
|
error(400, 'Session ID required');
|
|
}
|
|
|
|
try {
|
|
const ok = await revokeUserSession(recordId, locals.user.id);
|
|
if (!ok) {
|
|
error(404, 'Session not found or not yours');
|
|
}
|
|
|
|
// If the user is terminating their own current session, clear their auth cookie
|
|
// so they get logged out immediately (the hook would do this on the next request anyway,
|
|
// but clearing it here gives instant feedback for the "end this session" flow).
|
|
// For other sessions, we leave the cookie intact.
|
|
// We detect "current session" via authSessionId — but since the client sends the
|
|
// record ID (not the session_id), we rely on the UI to redirect after ending its own session.
|
|
|
|
log.info('sessions', 'session revoked', { recordId, userId: locals.user.id });
|
|
return json({ ok: true });
|
|
} catch (e) {
|
|
if (e instanceof Error && 'status' in e) throw e; // re-throw SvelteKit errors
|
|
log.error('sessions', 'DELETE failed', { recordId, err: String(e) });
|
|
error(500, 'Failed to revoke session');
|
|
}
|
|
};
|