From a43308b866f4dbd7ad720f5c3a50334253c36543 Mon Sep 17 00:00:00 2001 From: jableader Date: Sat, 1 Nov 2025 19:03:10 +1100 Subject: [PATCH] feat(router,auth,invites): remove legacy routing flag, use typed invitation accept, and restrict cookies to refresh --- src/api/client.ts | 19 +++++++++----- src/api/invitations.ts | 20 +++++---------- src/components/HouseholdSwitcher.vue | 8 +++--- src/router/index.ts | 33 +++++-------------------- tests/router.multitenant.test.ts | 37 +++++++++------------------- 5 files changed, 39 insertions(+), 78 deletions(-) diff --git a/src/api/client.ts b/src/api/client.ts index 97b745f..9c387cb 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -25,14 +25,24 @@ export function setAuthTokenProvider(provider: (() => string | null) | null) { authTokenProvider = provider } +function isRefreshRequest(input: RequestInfo | URL): boolean { + try { + const url = typeof input === 'string' ? input : (input as URL).toString() + return url.includes('/api/v1/auth/refresh') + } catch { + return false + } +} + export const api = createClient({ baseUrl, fetch: (input: RequestInfo | URL, init?: RequestInit) => { const headers = new Headers(init?.headers || {}) const token = authTokenProvider ? authTokenProvider() : null if (token) headers.set('Authorization', `Bearer ${token}`) + const credentials = isRefreshRequest(input) ? 'include' : 'same-origin' return globalThis.fetch(input, { - credentials: 'include', + credentials, ...init, headers, }) @@ -45,9 +55,6 @@ export async function fetchApi(path: string, init?: RequestInit): Promise(resp: Response): Promise { } export async function acceptInvitation(token: string): Promise { - const resp = await fetchApi('/api/v1/invitations/accept', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ token }), - }) - if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText || 'HTTP error'}`) - const data = await safeJson<{ household?: unknown }>(resp) - let h: unknown - if (data && typeof data === 'object' && 'household' in data) { - h = (data as { household?: unknown }).household - } else { - h = undefined - } + const { data, error, response } = await api.POST('/api/v1/invitations/accept', { body: { token } as any }) + if (!response.ok) throw new Error(`${response.status} ${response.statusText || 'HTTP error'}${error ? `: ${String(error)}` : ''}`) + const dataObj = data as any + const dataWrapped: { household?: unknown } | null = (dataObj && typeof dataObj === 'object') ? dataObj : null + let h: unknown = dataWrapped && 'household' in dataWrapped ? dataWrapped.household : undefined if (!isHousehold(h)) { throw new Error('Invalid invitation accept response') } diff --git a/src/components/HouseholdSwitcher.vue b/src/components/HouseholdSwitcher.vue index 62e7ee6..47f5f15 100644 --- a/src/components/HouseholdSwitcher.vue +++ b/src/components/HouseholdSwitcher.vue @@ -1,6 +1,6 @@