diff --git a/frontend/composables/useApi.ts b/frontend/composables/useApi.ts index ec296b4..5d9b6dc 100644 --- a/frontend/composables/useApi.ts +++ b/frontend/composables/useApi.ts @@ -80,6 +80,14 @@ export const useApi = (): ApiClient => { baseURL, retry: 0, credentials: 'include', + onRequest({ options }) { + const deviceId = useDeviceId() + if (deviceId) { + const headers = new Headers(options.headers as HeadersInit | undefined) + headers.set('X-Device-Id', deviceId) + options.headers = headers + } + }, onResponse({ options, response }) { const apiOptions = options as ApiFetchOptions<'json'> if (apiOptions?.toast === false) { diff --git a/frontend/composables/useDeviceId.ts b/frontend/composables/useDeviceId.ts new file mode 100644 index 0000000..b8d9879 --- /dev/null +++ b/frontend/composables/useDeviceId.ts @@ -0,0 +1,28 @@ +// Stable per-device identifier used to add forensic context to audit logs. +// Persisted in localStorage so the same browser/device reuses it across sessions. +// NOTE: this identifies a device/browser, not a human — on a shared kiosk every +// user of the same browser shares one id (intended: it distinguishes devices). + +const STORAGE_KEY = 'sirh-device-id' +let cached: string | null = null + +export const useDeviceId = (): string | null => { + if (!import.meta.client) { + return null + } + if (cached) { + return cached + } + try { + let id = localStorage.getItem(STORAGE_KEY) + if (!id) { + id = crypto.randomUUID() + localStorage.setItem(STORAGE_KEY, id) + } + cached = id + return id + } catch { + // localStorage unavailable (private mode, disabled) — degrade gracefully. + return null + } +}