feat(session): handle sessions for admin

This commit is contained in:
Edouard Vanbelle
2026-08-09 03:39:19 +02:00
parent 1b9d812175
commit bee856fbd0
17 changed files with 997 additions and 35 deletions
+34
View File
@@ -6,6 +6,7 @@
import { apiFetch, apiJson } from '$lib/api/client';
import { getCsrfHeaders } from '$lib/api/csrf';
import type {
AdminSessionsPage,
AdminUsersPage,
Drive,
DriveMember,
@@ -241,6 +242,39 @@ export async function deleteDriveAdmin(driveId: string): Promise<void> {
}
}
// ── Sessions (admin panel) ──────────────────────────────────────────────
/** Options for {@link listAdminSessions}. */
export interface ListSessionsOpts {
/** Narrow to one user's sessions; omit for cross-user listing. */
userId?: string;
/** Include revoked / expired rows. Default `false` (active-only UX). */
includeRevoked?: boolean;
/** Page size — server caps at 500. */
limit?: number;
/** Pagination offset. */
offset?: number;
}
/** Global sessions listing — `GET /api/admin/sessions`. */
export function listAdminSessions(opts: ListSessionsOpts = {}): Promise<AdminSessionsPage> {
const params = new URLSearchParams();
if (opts.userId) params.set('user_id', opts.userId);
if (opts.includeRevoked) params.set('include_revoked', 'true');
if (opts.limit !== undefined) params.set('limit', String(opts.limit));
if (opts.offset !== undefined) params.set('offset', String(opts.offset));
const qs = params.toString();
return apiJson<AdminSessionsPage>(`/api/admin/sessions${qs ? '?' + qs : ''}`, {
credentials: 'same-origin'
});
}
/** Revoke a session — `DELETE /api/admin/sessions/{id}`. Sets
* `revoked=true`; the row stays in the DB for audit visibility. */
export function revokeAdminSession(sessionId: string): Promise<void> {
return mutate(`/api/admin/sessions/${encodeURIComponent(sessionId)}`, 'DELETE');
}
// ── Users ───────────────────────────────────────────────────────────────
/** List the compact rows rendered by the management table; full account
+28
View File
@@ -734,3 +734,31 @@ export interface Finding {
detail: Record<string, unknown>;
created_at: string;
}
/**
* Admin sessions-panel row shape. Backend: `SessionSummaryDto` in
* `src/application/dtos/session_dto.rs`. Deliberately narrower than
* the DB row — the refresh token is never serialised, and the full
* DPoP thumbprint is truncated to an 8-char prefix so admins viewing
* other users' sessions can't exfiltrate the full binding fingerprint.
*/
export interface SessionSummary {
id: string;
user_id: string;
created_at: string;
expires_at: string;
ip_address: string | null;
user_agent: string | null;
is_bound: boolean;
dpop_jkt_prefix: string | null;
is_revoked: boolean;
is_active: boolean;
oidc_sid: string | null;
}
/** Wire response of `GET /api/admin/sessions`. */
export interface AdminSessionsPage {
sessions: SessionSummary[];
limit: number;
offset: number;
}