|
| 1 | +import { appendResponseHeader } from 'h3' |
| 2 | +import { parse, parseSetCookie, serialize } from 'cookie-es' |
| 3 | +import type { JwtData } from '@tsndr/cloudflare-worker-jwt' |
| 4 | +import { decode } from '@tsndr/cloudflare-worker-jwt' |
| 5 | + |
| 6 | +export default defineNuxtRouteMiddleware(async () => { |
| 7 | + const nuxtApp = useNuxtApp() |
| 8 | + // Don't run on client hydration when server rendered |
| 9 | + if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) return |
| 10 | + |
| 11 | + const { session, clear: clearSession, fetch: fetchSession } = useUserSession() |
| 12 | + // Ignore if no tokens |
| 13 | + if (!session.value?.jwt) return |
| 14 | + |
| 15 | + const serverEvent = useRequestEvent() |
| 16 | + const runtimeConfig = useRuntimeConfig() |
| 17 | + const { accessToken, refreshToken } = session.value.jwt |
| 18 | + |
| 19 | + const accessPayload = decode(accessToken) |
| 20 | + const refreshPayload = decode(refreshToken) |
| 21 | + |
| 22 | + // Both tokens expired, clearing session |
| 23 | + if (isExpired(accessPayload) && isExpired(refreshPayload)) { |
| 24 | + console.info('both tokens expired, clearing session') |
| 25 | + await clearSession() |
| 26 | + // return navigateTo('/login') |
| 27 | + } |
| 28 | + // Access token expired, refreshing |
| 29 | + else if (isExpired(accessPayload)) { |
| 30 | + console.info('access token expired, refreshing') |
| 31 | + await useRequestFetch()('/api/jtw/refresh', { |
| 32 | + method: 'POST', |
| 33 | + onResponse({ response: { headers } }) { |
| 34 | + // Forward the Set-Cookie header to the main server event |
| 35 | + if (import.meta.server && serverEvent) { |
| 36 | + for (const setCookie of headers.getSetCookie()) { |
| 37 | + appendResponseHeader(serverEvent, 'Set-Cookie', setCookie) |
| 38 | + // Update session cookie for next fetch requests |
| 39 | + const { name, value } = parseSetCookie(setCookie) |
| 40 | + if (name === runtimeConfig.session.name) { |
| 41 | + // console.log('updating headers.cookie to', value) |
| 42 | + const cookies = parse(serverEvent.headers.get('cookie') || '') |
| 43 | + // set or overwrite existing cookie |
| 44 | + cookies[name] = value |
| 45 | + // update cookie event header for future requests |
| 46 | + serverEvent.headers.set('cookie', Object.entries(cookies).map(([name, value]) => serialize(name, value)).join('; ')) |
| 47 | + // Also apply to serverEvent.node.req.headers |
| 48 | + if (serverEvent.node?.req?.headers) { |
| 49 | + serverEvent.node.req.headers['cookie'] = serverEvent.headers.get('cookie') || '' |
| 50 | + } |
| 51 | + } |
| 52 | + } |
| 53 | + } |
| 54 | + }, |
| 55 | + }) |
| 56 | + // refresh the session |
| 57 | + await fetchSession() |
| 58 | + } |
| 59 | +}) |
| 60 | + |
| 61 | +function isExpired(payload: JwtData) { |
| 62 | + return payload.payload?.exp && payload.payload.exp < (Date.now() / 1000) |
| 63 | +} |
0 commit comments