-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: forward set-cookie header for
useUserSession().clear()
(#282)
* feat: forward set-cookie header for `useUserSession().clear()` * chore: update
- Loading branch information
Showing
10 changed files
with
547 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { appendResponseHeader } from 'h3' | ||
import { parse, parseSetCookie, serialize } from 'cookie-es' | ||
import type { JwtData } from '@tsndr/cloudflare-worker-jwt' | ||
import { decode } from '@tsndr/cloudflare-worker-jwt' | ||
|
||
export default defineNuxtRouteMiddleware(async () => { | ||
const nuxtApp = useNuxtApp() | ||
// Don't run on client hydration when server rendered | ||
if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) return | ||
|
||
const { session, clear: clearSession, fetch: fetchSession } = useUserSession() | ||
// Ignore if no tokens | ||
if (!session.value?.jwt) return | ||
|
||
const serverEvent = useRequestEvent() | ||
const runtimeConfig = useRuntimeConfig() | ||
const { accessToken, refreshToken } = session.value.jwt | ||
|
||
const accessPayload = decode(accessToken) | ||
const refreshPayload = decode(refreshToken) | ||
|
||
// Both tokens expired, clearing session | ||
if (isExpired(accessPayload) && isExpired(refreshPayload)) { | ||
console.info('both tokens expired, clearing session') | ||
await clearSession() | ||
// return navigateTo('/login') | ||
} | ||
// Access token expired, refreshing | ||
else if (isExpired(accessPayload)) { | ||
console.info('access token expired, refreshing') | ||
await useRequestFetch()('/api/jtw/refresh', { | ||
method: 'POST', | ||
onResponse({ response: { headers } }) { | ||
// Forward the Set-Cookie header to the main server event | ||
if (import.meta.server && serverEvent) { | ||
for (const setCookie of headers.getSetCookie()) { | ||
appendResponseHeader(serverEvent, 'Set-Cookie', setCookie) | ||
// Update session cookie for next fetch requests | ||
const { name, value } = parseSetCookie(setCookie) | ||
if (name === runtimeConfig.session.name) { | ||
// console.log('updating headers.cookie to', value) | ||
const cookies = parse(serverEvent.headers.get('cookie') || '') | ||
// set or overwrite existing cookie | ||
cookies[name] = value | ||
// update cookie event header for future requests | ||
serverEvent.headers.set('cookie', Object.entries(cookies).map(([name, value]) => serialize(name, value)).join('; ')) | ||
// Also apply to serverEvent.node.req.headers | ||
if (serverEvent.node?.req?.headers) { | ||
serverEvent.node.req.headers['cookie'] = serverEvent.headers.get('cookie') || '' | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
}) | ||
// refresh the session | ||
await fetchSession() | ||
} | ||
}) | ||
|
||
function isExpired(payload: JwtData) { | ||
return payload.payload?.exp && payload.payload.exp < (Date.now() / 1000) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<template> | ||
<UPageBody> | ||
<h1>About page</h1> | ||
<UButton | ||
to="/" | ||
variant="link" | ||
:padded="false" | ||
> | ||
Home page | ||
</UButton> | ||
</UPageBody> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import jwt from '@tsndr/cloudflare-worker-jwt' | ||
|
||
export default defineEventHandler(async (event) => { | ||
// Get user from session | ||
const user = await getUserSession(event) | ||
if (!user) { | ||
throw createError({ | ||
statusCode: 401, | ||
message: 'Unauthorized', | ||
}) | ||
} | ||
|
||
if (!process.env.NUXT_SESSION_PASSWORD) { | ||
throw createError({ | ||
statusCode: 500, | ||
message: 'Session secret not configured', | ||
}) | ||
} | ||
|
||
// Generate tokens | ||
const accessToken = await jwt.sign( | ||
{ | ||
hello: 'world', | ||
exp: Math.floor(Date.now() / 1000) + 5, // 30 seconds | ||
}, | ||
process.env.NUXT_SESSION_PASSWORD, | ||
) | ||
|
||
const refreshToken = await jwt.sign( | ||
{ | ||
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, // 7 days | ||
}, | ||
`${process.env.NUXT_SESSION_PASSWORD}-secret`, | ||
) | ||
|
||
await setUserSession(event, { | ||
jwt: { | ||
accessToken, | ||
refreshToken, | ||
}, | ||
loggedInAt: Date.now(), | ||
}) | ||
|
||
// Return tokens | ||
return { | ||
accessToken, | ||
refreshToken, | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import jwt from '@tsndr/cloudflare-worker-jwt' | ||
|
||
export default eventHandler(async (event) => { | ||
const session = await getUserSession(event) | ||
if (!session.jwt?.accessToken) { | ||
throw createError({ | ||
statusCode: 401, | ||
message: 'Unauthorized', | ||
}) | ||
} | ||
|
||
try { | ||
return await jwt.verify(session.jwt.accessToken, process.env.NUXT_SESSION_PASSWORD!, { | ||
throwError: true, | ||
}) | ||
} | ||
catch (err) { | ||
throw createError({ | ||
statusCode: 401, | ||
message: (err as Error).message, | ||
}) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import jwt from '@tsndr/cloudflare-worker-jwt' | ||
|
||
export default eventHandler(async (event) => { | ||
const session = await getUserSession(event) | ||
if (!session.jwt?.accessToken && !session.jwt?.refreshToken) { | ||
throw createError({ | ||
statusCode: 401, | ||
message: 'Unauthorized', | ||
}) | ||
} | ||
|
||
if (!await jwt.verify(session.jwt.refreshToken, `${process.env.NUXT_SESSION_PASSWORD!}-secret`)) { | ||
throw createError({ | ||
statusCode: 401, | ||
message: 'refresh token is invalid', | ||
}) | ||
} | ||
|
||
const accessToken = await jwt.sign( | ||
{ | ||
hello: 'world', | ||
exp: Math.floor(Date.now() / 1000) + 30, // 30 seconds | ||
}, | ||
process.env.NUXT_SESSION_PASSWORD!, | ||
) | ||
|
||
await setUserSession(event, { | ||
jwt: { | ||
accessToken, | ||
refreshToken: session.jwt.refreshToken, | ||
}, | ||
loggedInAt: Date.now(), | ||
}) | ||
|
||
return { | ||
accessToken, | ||
refreshToken: session.jwt.refreshToken, | ||
} | ||
}) |
Oops, something went wrong.