Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,29 @@ idGenerator: (request) => {
}
```

##### idStore
A session id store. Used to store and retrieve session ids.
* get(request, key): string | undefined
* set(reply, key, value, cookieOptions)
* clear(reply, key, cookieOptions)

Defaults to a cookie store.

Custom implementation example:
```js
idStore: {
get: (request, key) => request.headers[key.toLowerCase()],
set: (reply, key, value) => {
reply.header(key, value);
},
clear: (reply, key) => {
reply.removeHeader(key);
},
},
}
```


#### request.session

Allows to access or modify the session data.
Expand Down
15 changes: 10 additions & 5 deletions lib/fastifySession.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const fp = require('fastify-plugin')
const idGenerator = require('./idGenerator')()
const Store = require('./store')
const Session = require('./session')
const idStore = require('./idStore')

function fastifySession (fastify, options, next) {
const error = checkOptions(options)
Expand All @@ -14,6 +15,7 @@ function fastifySession (fastify, options, next) {
options = ensureDefaults(options)

const sessionStore = options.store
const sessionIdStore = options.idStore
const cookieSigner = options.signer
const cookieName = options.cookieName
const cookiePrefix = options.cookiePrefix
Expand Down Expand Up @@ -113,14 +115,14 @@ function fastifySession (fastify, options, next) {

const getCookieSessionId = hasCookiePrefix
? function getCookieSessionId (request) {
const cookieValue = request.cookies[cookieName]
const cookieValue = sessionIdStore.get(request, cookieName)
return (
cookieValue?.startsWith(cookiePrefix) &&
cookieValue.slice(cookiePrefixLength)
)
}
: function getCookieSessionId (request) {
return request.cookies[cookieName]
return sessionIdStore.get(request, cookieName)
}

function onRequest (options) {
Expand Down Expand Up @@ -171,11 +173,12 @@ function fastifySession (fastify, options, next) {
if (!saveSession || isInsecureConnection) {
// if a session cookie is set, but has a different ID, clear it
if (cookieSessionId && cookieSessionId !== session.encryptedSessionId) {
reply.clearCookie(cookieName, { domain: cookieOpts.domain })
sessionIdStore.clear?.(reply, cookieName, { domain: cookieOpts.domain })
}

if (session.isSaved()) {
reply.setCookie(
sessionIdStore.set?.(
reply,
cookieName,
sessionIdWithPrefix,
// we need to remove extra properties to align the same with `express-session`
Expand All @@ -192,7 +195,8 @@ function fastifySession (fastify, options, next) {
done(err)
return
}
reply.setCookie(
sessionIdStore.set?.(
reply,
cookieName,
sessionIdWithPrefix,
// we need to remove extra properties to align the same with `express-session`
Expand Down Expand Up @@ -220,6 +224,7 @@ function fastifySession (fastify, options, next) {
function ensureDefaults (options) {
const opts = {}
opts.store = options.store || new Store()
opts.idStore = options.idStore || idStore
opts.idGenerator = options.idGenerator || idGenerator
opts.cookieName = options.cookieName || 'sessionId'
opts.cookie = options.cookie || {}
Expand Down
5 changes: 5 additions & 0 deletions lib/idStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
module.exports = {
'use strict'
module.exports = {

get: (request, key) => request.cookies[key],
set: (reply, key, value, opts) => reply.setCookie(key, value, opts),
clear: (reply, key, opts) => reply.clearCookie(key, opts)
}
73 changes: 73 additions & 0 deletions test/customIdStore.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict'

const test = require('node:test')
const Fastify = require('fastify')
const fastifyCookie = require('@fastify/cookie')
const fastifySession = require('../lib/fastifySession')
const { DEFAULT_OPTIONS, DEFAULT_SESSION_ID, DEFAULT_ENCRYPTED_SESSION_ID } = require('./util')

const idStore = {
get: (request, key) => request.headers[key.toLowerCase()],
set: (reply, key, value) => reply.header(key, value),
clear: (reply, key) => reply.removeHeader(key)
}

test('should set sessionid header with custom id store', async (t) => {
t.plan(3)
const fastify = Fastify()
let sessionId = null

fastify.addHook('onRequest', async (request) => {
request.raw.socket.encrypted = true
})
fastify.register(fastifyCookie)
fastify.register(fastifySession, {
...DEFAULT_OPTIONS,
idStore
})
fastify.get('/', (request, reply) => {
request.session.test = {}
sessionId = request.session.sessionId
reply.send(200)
})
await fastify.listen({ port: 0 })
t.after(() => { fastify.close() })

const response = await fastify.inject({
url: '/'
})

t.assert.strictEqual(response.statusCode, 200)
t.assert.ok(sessionId)
const pattern = `${sessionId}\\..{43,57}`
t.assert.strictEqual(new RegExp(pattern).test(response.headers['sessionid']), true)
})

test('should retrieve sessionid header with custom id store', async (t) => {
t.plan(2)
const fastify = Fastify()

fastify.register(fastifyCookie)
fastify.register(fastifySession, {
...DEFAULT_OPTIONS,
idStore,
store: {
get (id, cb) { cb(null, { id }) },
}
})
fastify.get('/', (request, reply) => {
t.assert.strictEqual(request.session.sessionId, DEFAULT_SESSION_ID)
reply.send(200)
})
await fastify.listen({ port: 0 })
t.after(() => { fastify.close() })

const response = await fastify.inject({
url: '/',
headers: {
sessionid: DEFAULT_ENCRYPTED_SESSION_ID
}
})

t.assert.strictEqual(response.statusCode, 200)
})
12 changes: 12 additions & 0 deletions types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ declare namespace fastifySession {
*/
store?: fastifySession.SessionStore;

/**
* A session ID store.
* Defaults to a cookie based store.
*/
idStore?: SessionIdStore;

/**
* Save sessions to the store, even when they are new and not modified.
* Defaults to true. Setting this to false can be useful to save storage space and to comply with the EU cookie law.
Expand Down Expand Up @@ -182,6 +188,12 @@ declare namespace fastifySession {
maxAge?: number;
}

export interface SessionIdStore {
get: (request: Fastify.FastifyRequest, key: string) => string | undefined;
set?: (reply: Fastify.FastifyReply, key: string, value: string, cookieOptions: CookieOptions) => void;
clear?: (reply: Fastify.FastifyReply, key: string, cookieOptions: CookieOptions) => void;
Comment on lines +193 to +194

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the decision behind making set and clear optionals?

}

export class MemoryStore implements fastifySession.SessionStore {
constructor (map?: Map<string, Fastify.Session>)
set (
Expand Down
Loading