-
-
Notifications
You must be signed in to change notification settings - Fork 20
Refactor session handling #48
base: main
Are you sure you want to change the base?
Changes from 20 commits
5138f30
a34d5d8
4dc1d99
1a261b7
99ac27f
efc7dc2
3629aa8
8fbed4a
1cb0215
dbd6bf4
46d9d1f
59b3b09
79eaf84
9d39b4d
293ad80
50cd690
0621975
477769f
b98f67e
8238d43
7b55c78
90d68bc
65a5f34
0b5b25a
0418846
dd4b7b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -226,7 +226,11 @@ Here's what the full _default_ module configuration looks like: | |
// Sessions aren't pinned to the user's IP address | ||
ipPinning: false, | ||
// Expiration of the sessions are not reset to the original expiryInSeconds on every request | ||
rolling: false | ||
rolling: false, | ||
// Uninitialized, resp. unmodified sessions are saved to the store, so session cookies are set at the first response | ||
saveUninitialized: true, | ||
// Sessions are saved to the store, even if they were never modified during the request | ||
resave: true | ||
|
||
}, | ||
api: { | ||
// The API is enabled | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
import { deleteCookie, eventHandler, H3Event, parseCookies, setCookie } from 'h3' | ||
import { nanoid } from 'nanoid' | ||
import dayjs from 'dayjs' | ||
import equal from 'fast-deep-equal' | ||
import { SameSiteOptions, Session, SessionOptions } from '../../../../types' | ||
import { dropStorageSession, getStorageSession, setStorageSession } from './storage' | ||
import { processSessionIp, getHashedIpAddress } from './ipPinning' | ||
import { SessionExpired } from './exceptions' | ||
import { resEndProxy } from './resEndProxy' | ||
import { useRuntimeConfig } from '#imports' | ||
|
||
const SESSION_COOKIE_NAME = 'sessionId' | ||
|
@@ -66,25 +68,24 @@ export const deleteSession = async (event: H3Event) => { | |
} | ||
|
||
const newSession = async (event: H3Event) => { | ||
const runtimeConfig = useRuntimeConfig() | ||
const sessionOptions = runtimeConfig.session.session as SessionOptions | ||
const sessionOptions = useRuntimeConfig().session.session as SessionOptions | ||
const now = new Date() | ||
|
||
// (Re-)Set cookie | ||
const sessionId = nanoid(sessionOptions.idLength) | ||
safeSetCookie(event, SESSION_COOKIE_NAME, sessionId, now) | ||
|
||
// Store session data in storage | ||
const session: Session = { | ||
id: sessionId, | ||
id: nanoid(sessionOptions.idLength), | ||
createdAt: now, | ||
ip: sessionOptions.ipPinning ? await getHashedIpAddress(event) : undefined | ||
} | ||
await setStorageSession(sessionId, session) | ||
|
||
return session | ||
} | ||
|
||
const setSession = async (session: Session, event: H3Event) => { | ||
safeSetCookie(event, SESSION_COOKIE_NAME, session.id, session.createdAt) | ||
await setStorageSession(session.id, session) | ||
return session | ||
} | ||
|
||
const getSession = async (event: H3Event): Promise<null | Session> => { | ||
// 1. Does the sessionId cookie exist on the request? | ||
const existingSessionId = getCurrentSessionId(event) | ||
|
@@ -98,8 +99,7 @@ const getSession = async (event: H3Event): Promise<null | Session> => { | |
return null | ||
} | ||
|
||
const runtimeConfig = useRuntimeConfig() | ||
const sessionOptions = runtimeConfig.session.session as SessionOptions | ||
const sessionOptions = useRuntimeConfig().session.session as SessionOptions | ||
const sessionExpiryInSeconds = sessionOptions.expiryInSeconds | ||
|
||
try { | ||
|
@@ -143,21 +143,31 @@ const ensureSession = async (event: H3Event) => { | |
|
||
event.context.sessionId = session.id | ||
event.context.session = session | ||
|
||
return session | ||
} | ||
|
||
export default eventHandler(async (event: H3Event) => { | ||
const sessionOptions = useRuntimeConfig().session.session as SessionOptions | ||
|
||
// 1. Ensure that a session is present by either loading or creating one | ||
await ensureSession(event) | ||
|
||
// 2. Setup a hook that saves any changed made to the session by the subsequent endpoints & middlewares | ||
event.res.on('finish', async () => { | ||
// Session id may not exist if session was deleted | ||
const session = await getSession(event) | ||
if (!session) { | ||
return | ||
const session = await ensureSession(event) | ||
// 2. Save current state of the session | ||
const oldSession = { ...session } | ||
|
||
|
||
// 3. Setup a hook that saves any changed made to the session by the subsequent endpoints & middlewares | ||
resEndProxy(event.node.res, async () => { | ||
const newSession = event.context.session as Session | ||
const storedSession = await getSession(event) | ||
|
||
|
||
if (!storedSession) { | ||
// Save a new session if saveUninitialized is true, or if the session has been modified | ||
if (sessionOptions.saveUninitialized || !equal(newSession, oldSession)) { | ||
await setSession(newSession, event) | ||
} | ||
// Update the session in the storage if resave is true, or if the stored session has been modified | ||
} else if (sessionOptions.resave || !equal(newSession, storedSession)) { | ||
await setStorageSession(storedSession.id, newSession) | ||
} | ||
|
||
await setStorageSession(session.id, event.context.session) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import type { ServerResponse } from 'node:http' | ||
|
||
type MiddleWare = () => Promise<void> | ||
|
||
// Proxy res.end() to get a callback at the end of all event handlers | ||
export const resEndProxy = (res: ServerResponse, middleWare: MiddleWare) => { | ||
const end = res.end | ||
|
||
// @ts-ignore Replacing res.end() will lead to type checking error | ||
res.end = async (chunk: any, encoding: BufferEncoding) => { | ||
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overwriting standard node js methods is obviously not recommended. Is there a better way doing that?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that there is no alternative, as already mentioned in #41. There is no hook at the end of all event handlers, and no event that fires just before sending the response.
|
||
await middleWare() | ||
return end.call(res, chunk, encoding) as ServerResponse | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need to typecast it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.