Skip to content
This repository was archived by the owner on Dec 12, 2023. It is now read-only.

Commit ca084fa

Browse files
authored
feat: Add rolling optional setting (#36)
1 parent 76a02b5 commit ca084fa

File tree

4 files changed

+45
-17
lines changed

4 files changed

+45
-17
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ Here's what the full _default_ module configuration looks like:
224224
// The request-domain is strictly used for the cookie, no sub-domains allowed
225225
domain: null,
226226
// Sessions aren't pinned to the user's IP address
227-
ipPinning: false
227+
ipPinning: false,
228+
// Expiration of the sessions are not reset to the original expiryInSeconds on every request
229+
rolling: false
228230
},
229231
api: {
230232
// The API is enabled

src/module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const defaults: FilledModuleOptions = {
2525
options: {}
2626
},
2727
domain: null,
28-
ipPinning: false as boolean|SessionIpPinningOptions
28+
ipPinning: false as boolean|SessionIpPinningOptions,
29+
rolling: false
2930
},
3031
api: {
3132
isEnabled: true,

src/runtime/server/middleware/session/index.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,25 @@ import { SessionExpired } from './exceptions'
88
import { useRuntimeConfig } from '#imports'
99

1010
const SESSION_COOKIE_NAME = 'sessionId'
11-
const safeSetCookie = (event: H3Event, name: string, value: string) => setCookie(event, name, value, {
12-
// Max age of cookie in seconds
13-
maxAge: useRuntimeConfig().session.session.expiryInSeconds,
14-
// Wether to send cookie via HTTPs to mitigate man-in-the-middle attacks
15-
secure: useRuntimeConfig().session.session.cookieSecure,
16-
// Wether to send cookie via HTTP requests and not allowing access of cookie from JS to mitigate XSS attacks
17-
httpOnly: useRuntimeConfig().session.session.cookieHttpOnly,
18-
// Do not send cookies on many cross-site requests to mitigates CSRF and cross-site attacks, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax
19-
sameSite: useRuntimeConfig().session.session.cookieSameSite as SameSiteOptions,
20-
// Set cookie for subdomain
21-
domain: useRuntimeConfig().session.session.domain
22-
})
11+
const safeSetCookie = (event: H3Event, name: string, value: string, createdAt: Date) => {
12+
const sessionOptions = useRuntimeConfig().session.session as SessionOptions
13+
const expirationDate = sessionOptions.expiryInSeconds
14+
? new Date(createdAt.getTime() + sessionOptions.expiryInSeconds * 1000)
15+
: undefined
16+
17+
setCookie(event, name, value, {
18+
// Set cookie expiration date to now + expiryInSeconds
19+
expires: expirationDate,
20+
// Wether to send cookie via HTTPs to mitigate man-in-the-middle attacks
21+
secure: sessionOptions.cookieSecure,
22+
// Wether to send cookie via HTTP requests and not allowing access of cookie from JS to mitigate XSS attacks
23+
httpOnly: sessionOptions.cookieHttpOnly,
24+
// Do not send cookies on many cross-site requests to mitigates CSRF and cross-site attacks, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax
25+
sameSite: sessionOptions.cookieSameSite as SameSiteOptions,
26+
// Set cookie for subdomain
27+
domain: sessionOptions.domain || undefined
28+
})
29+
}
2330

2431
const checkSessionExpirationTime = (session: Session, sessionExpiryInSeconds: number) => {
2532
const now = dayjs()
@@ -60,16 +67,17 @@ export const deleteSession = async (event: H3Event) => {
6067

6168
const newSession = async (event: H3Event) => {
6269
const runtimeConfig = useRuntimeConfig()
63-
const sessionOptions = runtimeConfig.session.session
70+
const sessionOptions = runtimeConfig.session.session as SessionOptions
71+
const now = new Date()
6472

6573
// (Re-)Set cookie
6674
const sessionId = nanoid(sessionOptions.idLength)
67-
safeSetCookie(event, SESSION_COOKIE_NAME, sessionId)
75+
safeSetCookie(event, SESSION_COOKIE_NAME, sessionId, now)
6876

6977
// Store session data in storage
7078
const session: Session = {
7179
id: sessionId,
72-
createdAt: new Date(),
80+
createdAt: now,
7381
ip: sessionOptions.ipPinning ? await getHashedIpAddress(event) : undefined
7482
}
7583
await setStorageSession(sessionId, session)
@@ -113,14 +121,24 @@ const getSession = async (event: H3Event): Promise<null | Session> => {
113121
return session
114122
}
115123

124+
const updateSessionExpirationDate = (session: Session, event: H3Event) => {
125+
const now = new Date()
126+
safeSetCookie(event, SESSION_COOKIE_NAME, session.id, now)
127+
return { ...session, createdAt: now }
128+
}
129+
116130
function isSession (shape: unknown): shape is Session {
117131
return typeof shape === 'object' && !!shape && 'id' in shape && 'createdAt' in shape
118132
}
119133

120134
const ensureSession = async (event: H3Event) => {
135+
const sessionOptions = useRuntimeConfig().session.session as SessionOptions
136+
121137
let session = await getSession(event)
122138
if (!session) {
123139
session = await newSession(event)
140+
} else if (sessionOptions.rolling) {
141+
session = updateSessionExpirationDate(session, event)
124142
}
125143

126144
event.context.sessionId = session.id

src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ export interface SessionOptions {
9898
* @type {SessionIpPinningOptions|boolean}
9999
*/
100100
ipPinning: SessionIpPinningOptions|boolean,
101+
/**
102+
* Force the session identifier cookie to be set on every response. The expiration is reset to the original expiryInSeconds, resetting the expiration countdown.
103+
* @default false
104+
* @example true
105+
* @type boolean
106+
*/
107+
rolling: boolean
101108
}
102109

103110
export interface ApiOptions {

0 commit comments

Comments
 (0)