From f27a29edd4b4db126598cb030c45a44c1d4c0d90 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 14:22:23 +0200 Subject: [PATCH 01/16] make @fastify/cookie mandatory --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0786142..cd4ad66 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "author": "Denis Fäcke", "license": "MIT", "dependencies": { + "@fastify/cookie": "^7.4.0", "cookie-signature": "^1.1.0", "fastify-plugin": "^4.0.0", "safe-stable-stringify": "^2.3.1", @@ -29,7 +30,6 @@ "url": "git+https://github.com/fastify/session.git" }, "devDependencies": { - "@fastify/cookie": "^7.0.0", "@types/node": "^18.0.0", "connect-redis": "^6.1.3", "cronometro": "^1.1.0", From 3e90ddfdf537be463d8749883544af18ea34a998 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 14:23:02 +0200 Subject: [PATCH 02/16] remove secret and unSignedCookie from typings --- types/types.d.ts | 20 -------------------- types/types.test-d.ts | 12 +----------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/types/types.d.ts b/types/types.d.ts index 3e7ee51..2e16deb 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -73,29 +73,9 @@ declare namespace FastifySessionPlugin { } interface Options { - /** - * The secret used to sign the cookie. - * - * Must be an array of strings, or a string with length 32 or greater. If an array, the first secret is used to - * sign new cookies, and is the first one to be checked for incoming cookies. - * Further secrets in the array are used to check incoming cookies, in the order specified. - * - * Note that the array may be manipulated by the rest of the application during its life cycle. - * This can be done by storing the array in a separate variable that is later manipulated with mutating methods - * like unshift(), pop(), splice(), etc. - * This can be used to rotate the signing secret at regular intervals. - * A secret should remain somewhere in the array as long as there are active sessions with cookies signed by it. - * Secrets management is left up to the rest of the application. - */ - secret: string | string[]; - /** The name of the session cookie. Defaults to `sessionId`. */ cookieName?: string; - /** If the cookie plugin is already signing the cookie this must be enabled. - * Otherwise it has no effect on the request whatsoever */ - unsignSignedCookie?: boolean; - /** * The options object used to generate the `Set-Cookie` header of the session cookie. * diff --git a/types/types.test-d.ts b/types/types.test-d.ts index ca80d0c..5b0caff 100644 --- a/types/types.test-d.ts +++ b/types/types.test-d.ts @@ -29,35 +29,25 @@ const secret = 'ABCDEFGHIJKLNMOPQRSTUVWXYZ012345'; const app: FastifyInstance = fastify(); app.register(plugin); -app.register(plugin, { secret: 'DizIzSecret' }); -app.register(plugin, { secret: 'DizIzSecret', rolling: true }); +app.register(plugin, { rolling: true }); app.register(plugin, { - secret, rolling: false, cookie: { secure: false } }); app.register(plugin, { - secret, cookie: { secure: false } }); app.register(plugin, { - secret, store: new EmptyStore() }); app.register(plugin, { - secret, idGenerator: () => Date.now() + '' }); app.register(plugin, { - secret, - unsignSignedCookie: true -}); -app.register(plugin, { - secret, idGenerator: (request) => `${request == undefined ? 'null' : request.ip}-${Date.now()}` }); From e979350e182a2f495190aa386c849393d66b4b8b Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 14:27:05 +0200 Subject: [PATCH 03/16] pass fastify to session --- lib/fastifySession.js | 319 +++++++++++++++++++++--------------------- lib/session.js | 16 ++- 2 files changed, 170 insertions(+), 165 deletions(-) diff --git a/lib/fastifySession.js b/lib/fastifySession.js index cf93989..74d307f 100644 --- a/lib/fastifySession.js +++ b/lib/fastifySession.js @@ -30,194 +30,197 @@ function session (fastify, options, next) { fastify.addHook('onRequest', onRequest(options)) fastify.addHook('onSend', onSend(options)) next() -} -function decryptSession (sessionId, options, request, done) { - const cookieOpts = options.cookie - const idGenerator = options.idGenerator - const secrets = options.secret - const secretsLength = secrets.length - const secret = secrets[0] - - let decryptedSessionId = false - for (let i = 0; i < secretsLength; ++i) { - decryptedSessionId = cookieSignature.unsign(sessionId, secrets[i]) - if (decryptedSessionId !== false) { - break + function decryptSession (sessionId, options, request, done) { + const cookieOpts = options.cookie + const idGenerator = options.idGenerator + const secrets = options.secret + const secretsLength = secrets.length + const secret = secrets[0] + + let decryptedSessionId = false + for (let i = 0; i < secretsLength; ++i) { + decryptedSessionId = cookieSignature.unsign(sessionId, secrets[i]) + if (decryptedSessionId !== false) { + break + } } - } - if (decryptedSessionId === false) { - newSession(secret, request, cookieOpts, idGenerator, done) - } else { - options.store.get(decryptedSessionId, (err, session) => { - if (err) { - if (err.code === 'ENOENT') { + if (decryptedSessionId === false) { + newSession(secret, request, cookieOpts, idGenerator, done) + } else { + options.store.get(decryptedSessionId, (err, session) => { + if (err) { + if (err.code === 'ENOENT') { + newSession(secret, request, cookieOpts, idGenerator, done) + } else { + done(err) + } + return + } + if (!session) { newSession(secret, request, cookieOpts, idGenerator, done) + return + } + if (session.cookie?.expires && session.cookie.expires <= Date.now()) { + const restoredSession = Session.restore( + fastify, + request, + idGenerator, + cookieOpts, + secret, + session + ) + + restoredSession.destroy(err => { + if (err) { + done(err) + return + } + + restoredSession.regenerate(done) + }) + return + } + if (options.rolling) { + request.session = new Session( + fastify, + request, + idGenerator, + cookieOpts, + secret, + session + ) } else { - done(err) + request.session = Session.restore( + fastify, + request, + idGenerator, + cookieOpts, + secret, + session + ) } + done() + }) + } + } + + function onRequest (options) { + const unsignSignedCookie = options.unsignSignedCookie + const cookieOpts = options.cookie + const idGenerator = options.idGenerator + return function handleSession (request, reply, done) { + request.session = {} + + const url = request.raw.url + if (url.indexOf(cookieOpts.path || '/') !== 0) { + done() return } - if (!session) { + const sessionId = request.cookies[options.cookieName] + const secret = options.secret[0] + if (!sessionId) { newSession(secret, request, cookieOpts, idGenerator, done) - return - } - if (session.cookie?.expires && session.cookie.expires <= Date.now()) { - const restoredSession = Session.restore( - request, - idGenerator, - cookieOpts, - secret, - session - ) + } else { + let sessionToDecrypt = sessionId - restoredSession.destroy(err => { - if (err) { - done(err) - return + if (unsignSignedCookie) { + const unsignedCookie = reply.unsignCookie(sessionId) + if (unsignedCookie.valid) { + sessionToDecrypt = unsignedCookie.value } + } - restoredSession.regenerate(done) - }) - return - } - if (options.rolling) { - request.session = new Session( - request, - idGenerator, - cookieOpts, - secret, - session - ) - } else { - request.session = Session.restore( - request, - idGenerator, - cookieOpts, - secret, - session - ) + decryptSession(sessionToDecrypt, options, request, done) } - done() - }) + } } -} -function onRequest (options) { - const unsignSignedCookie = options.unsignSignedCookie - const cookieOpts = options.cookie - const idGenerator = options.idGenerator - return function handleSession (request, reply, done) { - request.session = {} - - const url = request.raw.url - if (url.indexOf(cookieOpts.path || '/') !== 0) { - done() - return - } - const sessionId = request.cookies[options.cookieName] - const secret = options.secret[0] - if (!sessionId) { - newSession(secret, request, cookieOpts, idGenerator, done) - } else { - let sessionToDecrypt = sessionId + function onSend (options) { + return function saveSession (request, reply, payload, done) { + const session = request.session + if (!session || !session.sessionId) { + done() + return + } - if (unsignSignedCookie) { - const unsignedCookie = reply.unsignCookie(sessionId) - if (unsignedCookie.valid) { - sessionToDecrypt = unsignedCookie.value + if (!shouldSaveSession(request, options.cookie, options.saveUninitialized)) { + // if a session cookie is set, but has a different ID, clear it + if (request.cookies[options.cookieName] && request.cookies[options.cookieName] !== session.encryptedSessionId) { + reply.clearCookie(options.cookieName) } + done() + return } - - decryptSession(sessionToDecrypt, options, request, done) + session.save((err) => { + if (err) { + done(err) + return + } + reply.setCookie( + options.cookieName, + session.encryptedSessionId, + session.cookie.options(isConnectionSecure(request)) + ) + done() + }) } } -} -function onSend (options) { - return function saveSession (request, reply, payload, done) { - const session = request.session - if (!session || !session.sessionId) { - done() - return - } + function newSession (secret, request, cookieOpts, idGenerator, done) { + request.session = new Session(fastify, request, idGenerator, cookieOpts, secret) + done() + } - if (!shouldSaveSession(request, options.cookie, options.saveUninitialized)) { - // if a session cookie is set, but has a different ID, clear it - if (request.cookies[options.cookieName] && request.cookies[options.cookieName] !== session.encryptedSessionId) { - reply.clearCookie(options.cookieName) - } - done() - return + function checkOptions (options) { + if (!options.secret) { + return new Error('the secret option is required!') + } + if (typeof options.secret === 'string' && options.secret.length < 32) { + return new Error('the secret must have length 32 or greater') + } + if (Array.isArray(options.secret) && options.secret.length === 0) { + return new Error('at least one secret is required') } - session.save((err) => { - if (err) { - done(err) - return - } - reply.setCookie( - options.cookieName, - session.encryptedSessionId, - session.cookie.options(isConnectionSecure(request)) - ) - done() - }) } -} -function newSession (secret, request, cookieOpts, idGenerator, done) { - request.session = new Session(request, idGenerator, cookieOpts, secret) - done() -} - -function checkOptions (options) { - if (!options.secret) { - return new Error('the secret option is required!') - } - if (typeof options.secret === 'string' && options.secret.length < 32) { - return new Error('the secret must have length 32 or greater') + function idGenerator () { + return uid(24) } - if (Array.isArray(options.secret) && options.secret.length === 0) { - return new Error('at least one secret is required') - } -} - -function idGenerator () { - return uid(24) -} - -function ensureDefaults (options) { - options.store = options.store || new Store() - options.idGenerator = options.idGenerator || idGenerator - options.cookieName = options.cookieName || 'sessionId' - options.unsignSignedCookie = options.unsignSignedCookie || false - options.cookie = options.cookie || {} - options.cookie.secure = option(options.cookie, 'secure', true) - options.rolling = option(options, 'rolling', true) - options.saveUninitialized = option(options, 'saveUninitialized', true) - options.secret = Array.isArray(options.secret) ? options.secret : [options.secret] - return options -} -function isConnectionSecure (request) { - return ( - request.raw.socket?.encrypted === true || - request.headers['x-forwarded-proto'] === 'https' - ) -} + function ensureDefaults (options) { + options.store = options.store || new Store() + options.idGenerator = options.idGenerator || idGenerator + options.cookieName = options.cookieName || 'sessionId' + options.unsignSignedCookie = options.unsignSignedCookie || false + options.cookie = options.cookie || {} + options.cookie.secure = option(options.cookie, 'secure', true) + options.rolling = option(options, 'rolling', true) + options.saveUninitialized = option(options, 'saveUninitialized', true) + options.secret = Array.isArray(options.secret) ? options.secret : [options.secret] + return options + } -function shouldSaveSession (request, cookieOpts, saveUninitialized) { - if (!saveUninitialized && !request.session.isModified()) { - return false + function isConnectionSecure (request) { + return ( + request.raw.socket?.encrypted === true || + request.headers['x-forwarded-proto'] === 'https' + ) } - if (cookieOpts.secure !== true || cookieOpts.secure === 'auto') { - return true + + function shouldSaveSession (request, cookieOpts, saveUninitialized) { + if (!saveUninitialized && !request.session.isModified()) { + return false + } + if (cookieOpts.secure !== true || cookieOpts.secure === 'auto') { + return true + } + return isConnectionSecure(request) } - return isConnectionSecure(request) -} -function option (options, key, def) { - return options[key] === undefined ? def : options[key] + function option (options, key, def) { + return options[key] === undefined ? def : options[key] + } } module.exports = fp(session, { diff --git a/lib/session.js b/lib/session.js index 60953e6..3586e6c 100644 --- a/lib/session.js +++ b/lib/session.js @@ -8,6 +8,7 @@ const { configure: configureStringifier } = require('safe-stable-stringify') const stringify = configureStringifier({ bigint: false }) +const fastifyKey = Symbol('fastify') const maxAge = Symbol('maxAge') const secretKey = Symbol('secretKey') const sign = Symbol('sign') @@ -19,7 +20,8 @@ const originalHash = Symbol('originalHash') const hash = Symbol('hash') module.exports = class Session { - constructor (request, idGenerator, cookieOpts, secret, prevSession = {}) { + constructor (fastify, request, idGenerator, cookieOpts, secret, prevSession = {}) { + this[fastifyKey] = fastify this[generateId] = idGenerator this.cookie = new Cookie(cookieOpts) this[cookieOptsKey] = cookieOpts @@ -43,7 +45,7 @@ module.exports = class Session { regenerate (callback) { if (callback) { - const session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) + const session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) this[requestKey].sessionStore.set(session.sessionId, session, error => { this[requestKey].session = session @@ -52,7 +54,7 @@ module.exports = class Session { }) } else { return new Promise((resolve, reject) => { - const session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) + const session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) this[requestKey].sessionStore.set(session.sessionId, session, error => { this[requestKey].session = session @@ -108,14 +110,14 @@ module.exports = class Session { reload (callback) { if (callback) { this[requestKey].sessionStore.get(this.sessionId, (error, session) => { - this[requestKey].session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session) + this[requestKey].session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session) callback(error) }) } else { return new Promise((resolve, reject) => { this[requestKey].sessionStore.get(this.sessionId, (error, session) => { - this[requestKey].session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session) + this[requestKey].session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session) if (error) { reject(error) @@ -170,8 +172,8 @@ module.exports = class Session { return this[originalHash] !== this[hash]() } - static restore (request, idGenerator, cookieOpts, secret, prevSession) { - const restoredSession = new Session(request, idGenerator, cookieOpts, secret, prevSession) + static restore (fastify, request, idGenerator, cookieOpts, secret, prevSession) { + const restoredSession = new Session(fastify, request, idGenerator, cookieOpts, secret, prevSession) const restoredCookie = new Cookie(cookieOpts) restoredCookie.expires = new Date(prevSession.cookie.expires) restoredSession.cookie = restoredCookie From a9207e1cd439b6a4ca1dbda1acf764ece82b04b7 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 14:27:31 +0200 Subject: [PATCH 04/16] fix typing --- types/types.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/types.test-d.ts b/types/types.test-d.ts index 5b0caff..970f2ec 100644 --- a/types/types.test-d.ts +++ b/types/types.test-d.ts @@ -51,7 +51,7 @@ app.register(plugin, { idGenerator: (request) => `${request == undefined ? 'null' : request.ip}-${Date.now()}` }); -expectError(app.register(plugin, {})); +app.register(plugin, {}) expectError(app.register(plugin, { secret, unsignSignedCookie: 'not-a-boolean' From ea6c352d6a7ae9cbc93f3ea485ef569c9d5b40e7 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 15:19:27 +0200 Subject: [PATCH 05/16] fastify cookie does the signing and unsigning --- lib/fastifySession.js | 157 ++++++++++++++++-------------------------- lib/session.js | 19 +++-- test/secret.test.js | 68 ------------------ test/session.test.js | 97 ++++++++++++-------------- test/util.js | 10 ++- 5 files changed, 118 insertions(+), 233 deletions(-) delete mode 100644 test/secret.test.js diff --git a/lib/fastifySession.js b/lib/fastifySession.js index 74d307f..961df62 100644 --- a/lib/fastifySession.js +++ b/lib/fastifySession.js @@ -7,11 +7,6 @@ const Session = require('./session') const cookieSignature = require('cookie-signature') function session (fastify, options, next) { - const error = checkOptions(options) - if (error) { - return next(error) - } - options = ensureDefaults(options) // Decorator function takes cookieOpts so we can customize on per-session basis. @@ -34,75 +29,64 @@ function session (fastify, options, next) { function decryptSession (sessionId, options, request, done) { const cookieOpts = options.cookie const idGenerator = options.idGenerator - const secrets = options.secret - const secretsLength = secrets.length - const secret = secrets[0] - - let decryptedSessionId = false - for (let i = 0; i < secretsLength; ++i) { - decryptedSessionId = cookieSignature.unsign(sessionId, secrets[i]) - if (decryptedSessionId !== false) { - break - } + + const unsignedCookie = fastify.unsignCookie(sessionId) + if (unsignedCookie.valid === false) { + newSession(request, cookieOpts, idGenerator, done) + return } - if (decryptedSessionId === false) { - newSession(secret, request, cookieOpts, idGenerator, done) - } else { - options.store.get(decryptedSessionId, (err, session) => { - if (err) { - if (err.code === 'ENOENT') { - newSession(secret, request, cookieOpts, idGenerator, done) - } else { - done(err) - } - return - } - if (!session) { - newSession(secret, request, cookieOpts, idGenerator, done) - return - } - if (session.cookie?.expires && session.cookie.expires <= Date.now()) { - const restoredSession = Session.restore( - fastify, - request, - idGenerator, - cookieOpts, - secret, - session - ) - - restoredSession.destroy(err => { - if (err) { - done(err) - return - } - - restoredSession.regenerate(done) - }) - return - } - if (options.rolling) { - request.session = new Session( - fastify, - request, - idGenerator, - cookieOpts, - secret, - session - ) + const decryptedSessionId = unsignedCookie.value + options.store.get(decryptedSessionId, (err, session) => { + if (err) { + if (err.code === 'ENOENT') { + newSession(request, cookieOpts, idGenerator, done) } else { - request.session = Session.restore( - fastify, - request, - idGenerator, - cookieOpts, - secret, - session - ) + done(err) } - done() - }) - } + return + } + if (!session) { + newSession(request, cookieOpts, idGenerator, done) + return + } + if (session.cookie?.expires && session.cookie.expires <= Date.now()) { + const restoredSession = Session.restore( + fastify, + request, + idGenerator, + cookieOpts, + session + ) + + restoredSession.destroy(err => { + if (err) { + done(err) + return + } + + restoredSession.regenerate(done) + }) + return + } + if (options.rolling) { + request.session = new Session( + fastify, + request, + idGenerator, + cookieOpts, + session + ) + } else { + request.session = Session.restore( + fastify, + request, + idGenerator, + cookieOpts, + session + ) + } + done() + }) } function onRequest (options) { @@ -118,20 +102,10 @@ function session (fastify, options, next) { return } const sessionId = request.cookies[options.cookieName] - const secret = options.secret[0] if (!sessionId) { - newSession(secret, request, cookieOpts, idGenerator, done) + newSession(request, cookieOpts, idGenerator, done) } else { - let sessionToDecrypt = sessionId - - if (unsignSignedCookie) { - const unsignedCookie = reply.unsignCookie(sessionId) - if (unsignedCookie.valid) { - sessionToDecrypt = unsignedCookie.value - } - } - - decryptSession(sessionToDecrypt, options, request, done) + decryptSession(sessionId, options, request, done) } } } @@ -167,23 +141,11 @@ function session (fastify, options, next) { } } - function newSession (secret, request, cookieOpts, idGenerator, done) { - request.session = new Session(fastify, request, idGenerator, cookieOpts, secret) + function newSession (request, cookieOpts, idGenerator, done) { + request.session = new Session(fastify, request, idGenerator, cookieOpts) done() } - function checkOptions (options) { - if (!options.secret) { - return new Error('the secret option is required!') - } - if (typeof options.secret === 'string' && options.secret.length < 32) { - return new Error('the secret must have length 32 or greater') - } - if (Array.isArray(options.secret) && options.secret.length === 0) { - return new Error('at least one secret is required') - } - } - function idGenerator () { return uid(24) } @@ -197,7 +159,6 @@ function session (fastify, options, next) { options.cookie.secure = option(options.cookie, 'secure', true) options.rolling = option(options, 'rolling', true) options.saveUninitialized = option(options, 'saveUninitialized', true) - options.secret = Array.isArray(options.secret) ? options.secret : [options.secret] return options } diff --git a/lib/session.js b/lib/session.js index 3586e6c..68411d8 100644 --- a/lib/session.js +++ b/lib/session.js @@ -3,14 +3,12 @@ const crypto = require('crypto') const Cookie = require('./cookie') -const cookieSignature = require('cookie-signature') const { configure: configureStringifier } = require('safe-stable-stringify') const stringify = configureStringifier({ bigint: false }) const fastifyKey = Symbol('fastify') const maxAge = Symbol('maxAge') -const secretKey = Symbol('secretKey') const sign = Symbol('sign') const addDataToSession = Symbol('addDataToSession') const generateId = Symbol('generateId') @@ -20,13 +18,12 @@ const originalHash = Symbol('originalHash') const hash = Symbol('hash') module.exports = class Session { - constructor (fastify, request, idGenerator, cookieOpts, secret, prevSession = {}) { + constructor (fastify, request, idGenerator, cookieOpts, prevSession = {}) { this[fastifyKey] = fastify this[generateId] = idGenerator this.cookie = new Cookie(cookieOpts) this[cookieOptsKey] = cookieOpts this[maxAge] = cookieOpts.maxAge - this[secretKey] = secret this[addDataToSession](prevSession) this[requestKey] = request this.touch() @@ -45,7 +42,7 @@ module.exports = class Session { regenerate (callback) { if (callback) { - const session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) + const session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey]) this[requestKey].sessionStore.set(session.sessionId, session, error => { this[requestKey].session = session @@ -54,7 +51,7 @@ module.exports = class Session { }) } else { return new Promise((resolve, reject) => { - const session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) + const session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey]) this[requestKey].sessionStore.set(session.sessionId, session, error => { this[requestKey].session = session @@ -110,14 +107,14 @@ module.exports = class Session { reload (callback) { if (callback) { this[requestKey].sessionStore.get(this.sessionId, (error, session) => { - this[requestKey].session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session) + this[requestKey].session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], session) callback(error) }) } else { return new Promise((resolve, reject) => { this[requestKey].sessionStore.get(this.sessionId, (error, session) => { - this[requestKey].session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session) + this[requestKey].session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], session) if (error) { reject(error) @@ -148,7 +145,7 @@ module.exports = class Session { } [sign] () { - return cookieSignature.sign(this.sessionId, this[secretKey]) + return this[fastifyKey].signCookie(this.sessionId) } [hash] () { @@ -172,8 +169,8 @@ module.exports = class Session { return this[originalHash] !== this[hash]() } - static restore (fastify, request, idGenerator, cookieOpts, secret, prevSession) { - const restoredSession = new Session(fastify, request, idGenerator, cookieOpts, secret, prevSession) + static restore (fastify, request, idGenerator, cookieOpts, prevSession) { + const restoredSession = new Session(fastify, request, idGenerator, cookieOpts, prevSession) const restoredCookie = new Cookie(cookieOpts) restoredCookie.expires = new Date(prevSession.cookie.expires) restoredSession.cookie = restoredCookie diff --git a/test/secret.test.js b/test/secret.test.js deleted file mode 100644 index 4507fb8..0000000 --- a/test/secret.test.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict' - -const test = require('tap').test -const Fastify = require('fastify') -const fastifyCookie = require('@fastify/cookie') -const fastifySession = require('..') -const { DEFAULT_SECRET } = require('./util') - -test('register should fail if no secret is specified', async t => { - t.plan(1) - const fastify = Fastify() - - const options = {} - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) - - await t.rejects(fastify.ready(), 'the secret option is required!') -}) - -test('register should succeed if valid secret is specified', async t => { - t.plan(1) - const fastify = Fastify() - - const options = { secret: DEFAULT_SECRET } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) - await t.resolves(fastify.ready()) -}) - -test('register should fail if the secret is too short', async t => { - t.plan(1) - const fastify = Fastify() - - const options = { secret: 'geheim' } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) - await t.rejects(fastify.ready(), 'the secret must have length 32 or greater') -}) - -test('register should succeed if secret is short, but in an array', async t => { - t.plan(1) - const fastify = Fastify() - - const options = { secret: ['geheim'] } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) - await t.resolves(fastify.ready()) -}) - -test('register should succeed if multiple secrets are present', async t => { - t.plan(1) - const fastify = Fastify() - - const options = { secret: ['geheim', 'test'] } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) - await t.resolves(fastify.ready()) -}) - -test('register should fail if no secret is present in array', async t => { - t.plan(1) - const fastify = Fastify() - - const options = { secret: [] } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) - await t.rejects(fastify.ready(), 'at least one secret is required') -}) diff --git a/test/session.test.js b/test/session.test.js index e96a81d..b0d75a5 100644 --- a/test/session.test.js +++ b/test/session.test.js @@ -105,7 +105,7 @@ test('should allow get/set methods for fetching/updating session values', async test('should use custom sessionId generator if available (without request)', async (t) => { t.plan(2) const fastify = await buildFastify((request, reply) => { - t.equal(request.session.sessionId.startsWith('custom-'), false) + t.equal(request.session.sessionId.startsWith('custom-'), true) reply.send(200) }, { idGenerator: () => { @@ -224,8 +224,8 @@ test('should decryptSession with custom request object', async (t) => { secret: DEFAULT_SECRET } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + fastify.register(fastifyCookie, options) + fastify.register(fastifySession) fastify.addHook('onRequest', (request, reply, done) => { request.sessionStore.set(DEFAULT_SESSION_ID, { testData: 'this is a test', @@ -294,14 +294,16 @@ test('should bubble up errors with destroy call if session expired', async (t) = destroy (id, cb) { cb(new Error('No can do')) } } - const options = { - secret: DEFAULT_SECRET, + const fastifyCookieOpts = { + secret: DEFAULT_SECRET + } + const fastifySessionOpts = { store, cookie: { secure: false } } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + fastify.register(fastifyCookie, fastifyCookieOpts) + fastify.register(fastifySession, fastifySessionOpts) fastify.get('/', (request, reply) => { reply.send(200) @@ -805,8 +807,6 @@ test('when unsignSignedCookie is true sessions should still be managed correctly const store = new Map() const cookieSignKey = 'some-key' const options = { - ...DEFAULT_OPTIONS, - unsignSignedCookie: true, cookie: { secure: false, signed: false }, store: { set (id, data, cb) { @@ -821,51 +821,42 @@ test('when unsignSignedCookie is true sessions should still be managed correctly } } - const runTestScenario = async (cookieSigned) => { - options.cookie.signed = cookieSigned - - let encryptedSessionId = null - - const fastify = Fastify() - fastify.register(fastifyCookie, { secret: cookieSignKey }) - fastify.register(fastifySession, options) - fastify.get('/', (request, reply) => { - encryptedSessionId = encryptedSessionId || request.session.encryptedSessionId - reply.send(200) - }) + let encryptedSessionId = null - const { - statusCode: statusCode1, - headers: { - 'set-cookie': cookie1 - } - } = await fastify.inject('/') - t.ok(cookie1) - t.equal(statusCode1, 200) - - const { sessionId: sessionId1 } = fastify.parseCookie(cookie1) - t.equal(sessionId1, encryptedSessionId) - - const sessionId = cookieSigned - ? cookieSignature.sign(sessionId1, cookieSignKey) - : sessionId1 - const cookie = `sessionId=${sessionId};` - const { - statusCode: statusCode2, - headers: { - 'set-cookie': cookie2 - } - } = await fastify.inject({ - path: '/', - headers: { cookie } - }) - t.equal(statusCode2, 200) - t.ok(cookie2) + const fastify = Fastify() + fastify.register(fastifyCookie, { secret: cookieSignKey }) + fastify.register(fastifySession, options) + fastify.get('/', (request, reply) => { + encryptedSessionId = encryptedSessionId || request.session.encryptedSessionId + reply.send(200) + }) - const { sessionId: sessionId2 } = fastify.parseCookie(cookie2) - t.equal(sessionId2, encryptedSessionId) - } + const { + statusCode: statusCode1, + headers: { + 'set-cookie': cookie1 + } + } = await fastify.inject('/') + t.ok(cookie1) + t.equal(statusCode1, 200) + + const { sessionId: sessionId1 } = fastify.parseCookie(cookie1) + t.equal(sessionId1, encryptedSessionId) + + const sessionId = sessionId1 + const cookie = `sessionId=${sessionId};` + const { + statusCode: statusCode2, + headers: { + 'set-cookie': cookie2 + } + } = await fastify.inject({ + path: '/', + headers: { cookie } + }) + t.equal(statusCode2, 200) + t.ok(cookie2) - await runTestScenario(false) - await runTestScenario(true) + const { sessionId: sessionId2 } = fastify.parseCookie(cookie2) + t.equal(sessionId2, encryptedSessionId) }) diff --git a/test/util.js b/test/util.js index ae53565..3bc32f1 100644 --- a/test/util.js +++ b/test/util.js @@ -11,13 +11,17 @@ const DEFAULT_ENCRYPTED_SESSION_ID = `${DEFAULT_SESSION_ID}.B7fUDYXU9fXF9pNuL3qm const DEFAULT_COOKIE_VALUE = `sessionId=${DEFAULT_ENCRYPTED_SESSION_ID};` const DEFAULT_COOKIE = `${DEFAULT_COOKIE_VALUE}; Path=/; HttpOnly; Secure` -async function buildFastify (handler, sessionOptions, plugin) { +async function buildFastify (handler, options, plugin) { + const { + secret, + ...fastifySessionOpts + } = options const fastify = Fastify() - await fastify.register(fastifyCookie) + await fastify.register(fastifyCookie, { secret }) if (plugin) { await fastify.register(plugin) } - await fastify.register(fastifySession, sessionOptions) + await fastify.register(fastifySession, fastifySessionOpts) fastify.get('/', handler) await fastify.listen({ port: 0 }) From b3346307064b6db7bae83e303e5694e17f261684 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 15:20:09 +0200 Subject: [PATCH 06/16] remove cookie-signature artifacts --- lib/fastifySession.js | 1 - package.json | 1 - test/session.test.js | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/fastifySession.js b/lib/fastifySession.js index 961df62..3284089 100644 --- a/lib/fastifySession.js +++ b/lib/fastifySession.js @@ -4,7 +4,6 @@ const fp = require('fastify-plugin') const uid = require('uid-safe').sync const Store = require('./store') const Session = require('./session') -const cookieSignature = require('cookie-signature') function session (fastify, options, next) { options = ensureDefaults(options) diff --git a/package.json b/package.json index cd4ad66..5d57752 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "license": "MIT", "dependencies": { "@fastify/cookie": "^7.4.0", - "cookie-signature": "^1.1.0", "fastify-plugin": "^4.0.0", "safe-stable-stringify": "^2.3.1", "uid-safe": "^2.1.5" diff --git a/test/session.test.js b/test/session.test.js index b0d75a5..ca33696 100644 --- a/test/session.test.js +++ b/test/session.test.js @@ -5,7 +5,6 @@ const Fastify = require('fastify') const fastifyCookie = require('@fastify/cookie') const sinon = require('sinon') const fastifySession = require('..') -const cookieSignature = require('cookie-signature') const { buildFastify, DEFAULT_OPTIONS, DEFAULT_COOKIE, DEFAULT_SESSION_ID, DEFAULT_SECRET, DEFAULT_COOKIE_VALUE } = require('./util') test('should add session object to request', async (t) => { From a24c5d4a4300b791a152974f1ebabaf8069439ef Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 15:20:29 +0200 Subject: [PATCH 07/16] fix linting --- lib/fastifySession.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/fastifySession.js b/lib/fastifySession.js index 3284089..5d392c8 100644 --- a/lib/fastifySession.js +++ b/lib/fastifySession.js @@ -89,7 +89,6 @@ function session (fastify, options, next) { } function onRequest (options) { - const unsignSignedCookie = options.unsignSignedCookie const cookieOpts = options.cookie const idGenerator = options.idGenerator return function handleSession (request, reply, done) { From f5b70b9958312f7b9b132488e723de4ed1f8aceb Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 15:22:49 +0200 Subject: [PATCH 08/16] remove sign from session --- lib/session.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/session.js b/lib/session.js index 68411d8..34e7583 100644 --- a/lib/session.js +++ b/lib/session.js @@ -9,7 +9,6 @@ const stringify = configureStringifier({ bigint: false }) const fastifyKey = Symbol('fastify') const maxAge = Symbol('maxAge') -const sign = Symbol('sign') const addDataToSession = Symbol('addDataToSession') const generateId = Symbol('generateId') const requestKey = Symbol('request') @@ -29,7 +28,7 @@ module.exports = class Session { this.touch() if (!this.sessionId) { this.sessionId = this[generateId](this[requestKey]) - this.encryptedSessionId = this[sign]() + this.encryptedSessionId = fastify.signCookie(this.sessionId) } this[originalHash] = this[hash]() } @@ -144,10 +143,6 @@ module.exports = class Session { } } - [sign] () { - return this[fastifyKey].signCookie(this.sessionId) - } - [hash] () { const sess = this const str = stringify(sess, function (key, val) { From 3a98382296a2bf7d912a5cd8ec3b277994c79763 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 15:24:58 +0200 Subject: [PATCH 09/16] remove references to secret from readme.md --- README.md | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c1376c0..614bc36 100644 --- a/README.md +++ b/README.md @@ -20,15 +20,14 @@ npm i @fastify/session ```js const fastify = require('fastify'); const fastifySession = require('@fastify/session'); -const fastifyCookie = require('fastify-cookie'); +const fastifyCookie = require('@fastify/cookie'); const app = fastify(); -app.register(fastifyCookie); -app.register(fastifySession, {secret: 'a secret with minimum length of 32 characters'}); +app.register(fastifyCookie, {secret: 'a secret with minimum length of 32 characters'}); +app.register(fastifySession); ``` Store data in the session by adding it to the `session` decorator at the `request`: ```js -app.register(fastifySession, {secret: 'a secret with minimum length of 32 characters'}); app.addHook('preHandler', (request, reply, next) => { request.session.user = {name: 'max'}; next(); @@ -37,7 +36,6 @@ app.addHook('preHandler', (request, reply, next) => { **NOTE**: For all unencrypted (HTTP) connections, you need to set the `secure` cookie option to `false`. See below for all cookie options and their details. The `session` object has methods that allow you to get, save, reload and delete sessions. ```js -app.register(fastifySession, {secret: 'a secret with minimum length of 32 characters'}); app.addHook('preHandler', (request, reply, next) => { request.session.destroy(next); }) @@ -51,14 +49,6 @@ app.addHook('preHandler', (request, reply, next) => { ### session(fastify, options, next) The session plugin accepts the following options. It decorates the request with the `sessionStore` and a `session` object. The session data is stored server-side using the configured session store. #### options -##### secret (required) -The secret used to sign the cookie. Must be an array of strings or a string with a length of 32 or greater. - -If an array, the first secret is used to sign new cookies and is the first to be checked for incoming cookies. -Further secrets in the array are used to check incoming cookies in the order specified. - -Note that the rest of the application may manipulate the array during its life cycle. This can be done by storing the array in a separate variable that is later used with mutating methods like unshift(), pop(), splice(), etc. -This can be used to rotate the signing secret at regular intervals. A secret should remain somewhere in the array as long as there are active sessions with cookies signed by it. Secrets management is left up to the rest of the application. ##### cookieName (optional) The name of the session cookie. Defaults to `sessionId`. ##### cookie From 55e6dbc937649389f842317319191535e3281e44 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 15:48:54 +0200 Subject: [PATCH 10/16] remove newSession --- lib/fastifySession.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/fastifySession.js b/lib/fastifySession.js index 5d392c8..0b4bdfd 100644 --- a/lib/fastifySession.js +++ b/lib/fastifySession.js @@ -31,21 +31,24 @@ function session (fastify, options, next) { const unsignedCookie = fastify.unsignCookie(sessionId) if (unsignedCookie.valid === false) { - newSession(request, cookieOpts, idGenerator, done) + request.session = new Session(fastify, request, idGenerator, cookieOpts) + done() return } const decryptedSessionId = unsignedCookie.value options.store.get(decryptedSessionId, (err, session) => { if (err) { if (err.code === 'ENOENT') { - newSession(request, cookieOpts, idGenerator, done) + request.session = new Session(fastify, request, idGenerator, cookieOpts) + done() } else { done(err) } return } if (!session) { - newSession(request, cookieOpts, idGenerator, done) + request.session = new Session(fastify, request, idGenerator, cookieOpts) + done() return } if (session.cookie?.expires && session.cookie.expires <= Date.now()) { @@ -101,7 +104,8 @@ function session (fastify, options, next) { } const sessionId = request.cookies[options.cookieName] if (!sessionId) { - newSession(request, cookieOpts, idGenerator, done) + request.session = new Session(fastify, request, idGenerator, cookieOpts) + done() } else { decryptSession(sessionId, options, request, done) } @@ -139,11 +143,6 @@ function session (fastify, options, next) { } } - function newSession (request, cookieOpts, idGenerator, done) { - request.session = new Session(fastify, request, idGenerator, cookieOpts) - done() - } - function idGenerator () { return uid(24) } From 63ab6174f4218e7fb6cda44c8c3ddda21574aaf3 Mon Sep 17 00:00:00 2001 From: Rodrigo Menezes Date: Mon, 15 Aug 2022 15:00:04 -0400 Subject: [PATCH 11/16] Add cookiePrefix as an option to allow for compatibility with express-session (#113) * Add cookiePrefix as an option to allow for compatibility with express-session * Add docs * Fix lint * improve solution * Apply suggestions from code review Co-authored-by: uzlopak --- README.md | 2 ++ lib/fastifySession.js | 36 ++++++++++++++++++++++++++++-------- test/base.test.js | 34 ++++++++++++++++++++++++++++++++++ types/types.d.ts | 6 ++++++ 4 files changed, 70 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 614bc36..3673148 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ The session plugin accepts the following options. It decorates the request with #### options ##### cookieName (optional) The name of the session cookie. Defaults to `sessionId`. +##### cookiePrefix (optional) +Prefix for the value of the cookie. This is useful for compatibility with `express-session`, which prefixes all cookies with `"s:"`. Defaults to `""`. ##### cookie The options object is used to generate the `Set-Cookie` header of the session cookie. May have the following properties: * `path` - The `Path` attribute. Defaults to `/` (the root path). diff --git a/lib/fastifySession.js b/lib/fastifySession.js index 0b4bdfd..cb6f851 100644 --- a/lib/fastifySession.js +++ b/lib/fastifySession.js @@ -93,7 +93,12 @@ function session (fastify, options, next) { function onRequest (options) { const cookieOpts = options.cookie + const cookieName = options.cookieName const idGenerator = options.idGenerator + const cookiePrefix = options.cookiePrefix + const hasCookiePrefix = typeof cookiePrefix === 'string' && cookiePrefix.length !== 0 + const cookiePrefixLength = hasCookiePrefix && cookiePrefix.length + return function handleSession (request, reply, done) { request.session = {} @@ -102,7 +107,10 @@ function session (fastify, options, next) { done() return } - const sessionId = request.cookies[options.cookieName] + let sessionId = request.cookies[cookieName] + if (sessionId && hasCookiePrefix && sessionId.startsWith(cookiePrefix)) { + sessionId = sessionId.slice(cookiePrefixLength) + } if (!sessionId) { request.session = new Session(fastify, request, idGenerator, cookieOpts) done() @@ -113,6 +121,12 @@ function session (fastify, options, next) { } function onSend (options) { + const cookieOpts = options.cookie + const cookieName = options.cookieName + const cookiePrefix = options.cookiePrefix + const saveUninitialized = options.saveUninitialized + const hasCookiePrefix = typeof cookiePrefix === 'string' && cookiePrefix.length !== 0 + return function saveSession (request, reply, payload, done) { const session = request.session if (!session || !session.sessionId) { @@ -120,10 +134,15 @@ function session (fastify, options, next) { return } - if (!shouldSaveSession(request, options.cookie, options.saveUninitialized)) { - // if a session cookie is set, but has a different ID, clear it - if (request.cookies[options.cookieName] && request.cookies[options.cookieName] !== session.encryptedSessionId) { - reply.clearCookie(options.cookieName) + let encryptedSessionId = session.encryptedSessionId + if (encryptedSessionId && hasCookiePrefix) { + encryptedSessionId = `${cookiePrefix}${encryptedSessionId}` + } + + if (!shouldSaveSession(request, cookieOpts, saveUninitialized)) { + // if a session cookie is set, but has a different ID, clear it + if (request.cookies[cookieName] && request.cookies[cookieName] !== encryptedSessionId) { + reply.clearCookie(cookieName) } done() return @@ -134,8 +153,8 @@ function session (fastify, options, next) { return } reply.setCookie( - options.cookieName, - session.encryptedSessionId, + cookieName, + encryptedSessionId, session.cookie.options(isConnectionSecure(request)) ) done() @@ -156,13 +175,14 @@ function session (fastify, options, next) { options.cookie.secure = option(options.cookie, 'secure', true) options.rolling = option(options, 'rolling', true) options.saveUninitialized = option(options, 'saveUninitialized', true) + options.cookiePrefix = option(options, 'cookiePrefix', '') return options } function isConnectionSecure (request) { return ( request.raw.socket?.encrypted === true || - request.headers['x-forwarded-proto'] === 'https' + request.headers['x-forwarded-proto'] === 'https' ) } diff --git a/test/base.test.js b/test/base.test.js index aba1323..d0803b0 100644 --- a/test/base.test.js +++ b/test/base.test.js @@ -127,6 +127,40 @@ test('should set session cookie using the default cookie name', async (t) => { t.match(response.headers['set-cookie'], /sessionId=undefined; Path=\/; HttpOnly; Secure/) }) +test('should set express sessions using the specified cookiePrefix', async (t) => { + t.plan(2) + const options = { + secret: 'cNaoPYAwF60HZJzkcNaoPYAwF60HZJzk', + cookieName: 'connect.sid', + cookiePrefix: 's:' + } + + const plugin = fastifyPlugin(async (fastify, opts) => { + fastify.addHook('onRequest', (request, reply, done) => { + request.sessionStore.set('Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN', { + expires: Date.now() + 1000 + }, done) + }) + }) + function handler (request, reply) { + request.session.test = {} + reply.send(200) + } + const fastify = await buildFastify(handler, options, plugin) + t.teardown(() => fastify.close()) + + const response = await fastify.inject({ + url: '/', + headers: { + cookie: 'connect.sid=s%3AQk_XT2K7-clT-x1tVvoY6tIQ83iP72KN.B7fUDYXU9fXF9pNuL3qm4NVmSduLJ6kzCOPh5JhHGoE; Path=/; HttpOnly; Secure', + 'x-forwarded-proto': 'https' + } + }) + + t.equal(response.statusCode, 200) + t.match(response.headers['set-cookie'], /connect.sid=s%3A[\w-]{32}.[\w-%]{43,57}; Path=\/; HttpOnly; Secure/) +}) + test('should create new session on expired session', async (t) => { t.plan(2) const plugin = fastifyPlugin(async (fastify, opts) => { diff --git a/types/types.d.ts b/types/types.d.ts index 2e16deb..9a738b3 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -105,6 +105,12 @@ declare namespace FastifySessionPlugin { /** Function used to generate new session IDs. Defaults to uid(24). */ idGenerator?(request?: Fastify.FastifyRequest): string; + + /** + * Prefixes all cookie values. Run with "s:" to be be compatible with express-session. + * Defaults to "" + */ + cookiePrefix?: string; } interface CookieOptions { From 225db1e34b96870e23207e7fabfda181ececa1f7 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Mon, 15 Aug 2022 21:26:28 +0200 Subject: [PATCH 12/16] fix merge error --- lib/fastifySession.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/fastifySession.js b/lib/fastifySession.js index 925779e..721b5a9 100644 --- a/lib/fastifySession.js +++ b/lib/fastifySession.js @@ -162,10 +162,6 @@ function session (fastify, options, next) { } } - function idGenerator () { - return uid(24) - } - function ensureDefaults (options) { options.store = options.store || new Store() options.idGenerator = options.idGenerator || idGenerator From 0afb1a43e8c18fbe0a637d6acb4c2734157118d4 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Tue, 16 Aug 2022 09:06:12 +0200 Subject: [PATCH 13/16] fix merge error --- lib/session.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/session.js b/lib/session.js index 5a444dd..b0ad946 100644 --- a/lib/session.js +++ b/lib/session.js @@ -9,7 +9,6 @@ const stringify = configureStringifier({ bigint: false }) const fastifyKey = Symbol('fastify') const maxAge = Symbol('maxAge') -const secretKey = Symbol('secretKey') const generateId = Symbol('generateId') const requestKey = Symbol('request') const cookieOptsKey = Symbol('cookieOpts') @@ -17,7 +16,8 @@ const originalHash = Symbol('originalHash') const hash = Symbol('hash') module.exports = class Session { - constructor (request, idGenerator, cookieOpts, secret, prevSession) { + constructor (fastify, request, idGenerator, cookieOpts, prevSession) { + this[fastifyKey] = fastify this[generateId] = idGenerator this[cookieOptsKey] = cookieOpts this[maxAge] = cookieOpts.maxAge From 267abcbc3b024ccd1196401de967af14b1f13970 Mon Sep 17 00:00:00 2001 From: uzlopak Date: Tue, 16 Aug 2022 13:46:09 +0200 Subject: [PATCH 14/16] use @fastify/cookie v8.0.0 --- lib/fastifySession.js | 16 ++--- lib/session.js | 18 +++--- package.json | 2 +- test/base.test.js | 10 +-- test/cookie.test.js | 37 +++++------ test/session.test.js | 146 ++++++++++++++++++------------------------ test/store.test.js | 4 +- test/util.js | 4 +- 8 files changed, 103 insertions(+), 134 deletions(-) diff --git a/lib/fastifySession.js b/lib/fastifySession.js index 721b5a9..36178c7 100644 --- a/lib/fastifySession.js +++ b/lib/fastifySession.js @@ -29,9 +29,9 @@ function session (fastify, options, next) { const cookieOpts = options.cookie const idGenerator = options.idGenerator - const unsignedCookie = fastify.unsignCookie(sessionId) + const unsignedCookie = request.unsignCookie(sessionId) if (unsignedCookie.valid === false) { - request.session = new Session(fastify, request, idGenerator, cookieOpts) + request.session = new Session(request, idGenerator, cookieOpts) done() return } @@ -39,7 +39,7 @@ function session (fastify, options, next) { options.store.get(decryptedSessionId, (err, session) => { if (err) { if (err.code === 'ENOENT') { - request.session = new Session(fastify, request, idGenerator, cookieOpts) + request.session = new Session(request, idGenerator, cookieOpts) done() } else { done(err) @@ -47,13 +47,12 @@ function session (fastify, options, next) { return } if (!session) { - request.session = new Session(fastify, request, idGenerator, cookieOpts) + request.session = new Session(request, idGenerator, cookieOpts) done() return } if (session.cookie?.expires && session.cookie.expires <= Date.now()) { const restoredSession = Session.restore( - fastify, request, idGenerator, cookieOpts, @@ -72,7 +71,6 @@ function session (fastify, options, next) { } if (options.rolling) { request.session = new Session( - fastify, request, idGenerator, cookieOpts, @@ -80,7 +78,6 @@ function session (fastify, options, next) { ) } else { request.session = Session.restore( - fastify, request, idGenerator, cookieOpts, @@ -112,7 +109,7 @@ function session (fastify, options, next) { sessionId = sessionId.slice(cookiePrefixLength) } if (!sessionId) { - request.session = new Session(fastify, request, idGenerator, cookieOpts) + request.session = new Session(request, idGenerator, cookieOpts) done() } else { decryptSession(sessionId, options, request, done) @@ -199,6 +196,9 @@ function session (fastify, options, next) { module.exports = fp(session, { fastify: '4.x', name: '@fastify/session', + decorators: { + request: ['signCookie', 'unsignCookie'] + }, dependencies: [ '@fastify/cookie' ] diff --git a/lib/session.js b/lib/session.js index b0ad946..c43d83d 100644 --- a/lib/session.js +++ b/lib/session.js @@ -7,7 +7,6 @@ const { configure: configureStringifier } = require('safe-stable-stringify') const stringify = configureStringifier({ bigint: false }) -const fastifyKey = Symbol('fastify') const maxAge = Symbol('maxAge') const generateId = Symbol('generateId') const requestKey = Symbol('request') @@ -16,8 +15,7 @@ const originalHash = Symbol('originalHash') const hash = Symbol('hash') module.exports = class Session { - constructor (fastify, request, idGenerator, cookieOpts, prevSession) { - this[fastifyKey] = fastify + constructor (request, idGenerator, cookieOpts, prevSession) { this[generateId] = idGenerator this[cookieOptsKey] = cookieOpts this[maxAge] = cookieOpts.maxAge @@ -34,7 +32,7 @@ module.exports = class Session { this.touch() if (!this.sessionId) { this.sessionId = this[generateId](this[requestKey]) - this.encryptedSessionId = fastify.signCookie(this.sessionId) + this.encryptedSessionId = this[requestKey].signCookie(this.sessionId) } this[originalHash] = this[hash]() } @@ -47,7 +45,7 @@ module.exports = class Session { regenerate (callback) { if (callback) { - const session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey]) + const session = new Session(this[requestKey], this[generateId], this[cookieOptsKey]) this[requestKey].sessionStore.set(session.sessionId, session, error => { this[requestKey].session = session @@ -56,7 +54,7 @@ module.exports = class Session { }) } else { return new Promise((resolve, reject) => { - const session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey]) + const session = new Session(this[requestKey], this[generateId], this[cookieOptsKey]) this[requestKey].sessionStore.set(session.sessionId, session, error => { this[requestKey].session = session @@ -104,14 +102,14 @@ module.exports = class Session { reload (callback) { if (callback) { this[requestKey].sessionStore.get(this.sessionId, (error, session) => { - this[requestKey].session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], session) + this[requestKey].session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], session) callback(error) }) } else { return new Promise((resolve, reject) => { this[requestKey].sessionStore.get(this.sessionId, (error, session) => { - this[requestKey].session = new Session(this[fastifyKey], this[requestKey], this[generateId], this[cookieOptsKey], session) + this[requestKey].session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], session) if (error) { reject(error) @@ -162,8 +160,8 @@ module.exports = class Session { return this[originalHash] !== this[hash]() } - static restore (fastify, request, idGenerator, cookieOpts, prevSession) { - const restoredSession = new Session(fastify, request, idGenerator, cookieOpts, prevSession) + static restore (request, idGenerator, cookieOpts, prevSession) { + const restoredSession = new Session(request, idGenerator, cookieOpts, prevSession) const restoredCookie = new Cookie(cookieOpts) restoredCookie.expires = new Date(prevSession.cookie.expires) restoredSession.cookie = restoredCookie diff --git a/package.json b/package.json index 10e9555..8c8bc24 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "author": "Denis Fäcke", "license": "MIT", "dependencies": { - "@fastify/cookie": "^7.4.0", + "@fastify/cookie": "^8.0.0", "fastify-plugin": "^4.0.0", "safe-stable-stringify": "^2.3.1" }, diff --git a/test/base.test.js b/test/base.test.js index d0803b0..038d40f 100644 --- a/test/base.test.js +++ b/test/base.test.js @@ -2,11 +2,11 @@ const test = require('tap').test const fastifyPlugin = require('fastify-plugin') -const { DEFAULT_OPTIONS, DEFAULT_COOKIE, DEFAULT_SESSION_ID, DEFAULT_SECRET, DEFAULT_ENCRYPTED_SESSION_ID, buildFastify } = require('./util') +const { DEFAULT_COOKIE_OPTIONS, DEFAULT_COOKIE, DEFAULT_SESSION_ID, DEFAULT_SECRET, DEFAULT_ENCRYPTED_SESSION_ID, buildFastify } = require('./util') test('should not set session cookie on post without params', async (t) => { t.plan(3) - const fastify = await buildFastify((request, reply) => reply.send(200), DEFAULT_OPTIONS) + const fastify = await buildFastify((request, reply) => reply.send(200), DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -24,7 +24,7 @@ test('should set session cookie', async (t) => { const fastify = await buildFastify((request, reply) => { request.session.test = {} reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response1 = await fastify.inject({ @@ -112,7 +112,7 @@ test('should set session cookie using the default cookie name', async (t) => { request.session.test = {} reply.send(200) } - const fastify = await buildFastify(handler, DEFAULT_OPTIONS, plugin) + const fastify = await buildFastify(handler, DEFAULT_COOKIE_OPTIONS, plugin) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -231,7 +231,7 @@ test('should set new session cookie if expired', async (t) => { request.session.test = {} reply.send(200) } - const fastify = await buildFastify(handler, DEFAULT_OPTIONS, plugin) + const fastify = await buildFastify(handler, DEFAULT_COOKIE_OPTIONS, plugin) t.teardown(() => fastify.close()) const response = await fastify.inject({ diff --git a/test/cookie.test.js b/test/cookie.test.js index 701d9c9..d2e5a1c 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -4,7 +4,7 @@ const test = require('tap').test const Fastify = require('fastify') const fastifyCookie = require('@fastify/cookie') const fastifySession = require('../lib/fastifySession') -const { DEFAULT_OPTIONS, DEFAULT_SECRET, buildFastify } = require('./util') +const { DEFAULT_COOKIE_OPTIONS, DEFAULT_SECRET, buildFastify } = require('./util') test('should set session cookie', async (t) => { t.plan(2) @@ -13,8 +13,8 @@ test('should set session cookie', async (t) => { fastify.addHook('onRequest', async (request, reply) => { request.raw.socket.encrypted = true }) - fastify.register(fastifyCookie) - fastify.register(fastifySession, DEFAULT_OPTIONS) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession) fastify.get('/', (request, reply) => { request.session.test = {} reply.send(200) @@ -36,8 +36,8 @@ test('should not set session cookie is request is not secure', async (t) => { fastify.addHook('onRequest', async (request, reply) => { request.raw.socket.encrypted = false }) - fastify.register(fastifyCookie) - fastify.register(fastifySession, DEFAULT_OPTIONS) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession) fastify.get('/', (request, reply) => reply.send(200)) await fastify.listen({ port: 0 }) t.teardown(() => { fastify.close() }) @@ -56,8 +56,8 @@ test('should not set session cookie is request is not secure and x-forwarded-pro fastify.addHook('onRequest', async (request, reply) => { request.raw.socket.encrypted = false }) - fastify.register(fastifyCookie) - fastify.register(fastifySession, DEFAULT_OPTIONS) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession) fastify.get('/', (request, reply) => reply.send(200)) await fastify.listen({ port: 0 }) t.teardown(() => { fastify.close() }) @@ -77,8 +77,8 @@ test('should set session cookie is request is not secure and x-forwarded-proto = fastify.addHook('onRequest', async (request, reply) => { request.raw.socket.encrypted = false }) - fastify.register(fastifyCookie) - fastify.register(fastifySession, DEFAULT_OPTIONS) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession) fastify.get('/', (request, reply) => { request.session.test = {} reply.send(200) @@ -182,12 +182,11 @@ test('should set session another path in cookie', async (t) => { t.plan(2) const fastify = Fastify() - const options = { + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession, { secret: DEFAULT_SECRET, cookie: { path: '/a/test/path' } - } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + }) fastify.get('/a/test/path', (request, reply) => { request.session.test = {} reply.send(200) @@ -294,9 +293,8 @@ test('should set session cookie secureAuto', async (t) => { fastify.addHook('onRequest', async (request, reply) => { request.raw.socket.encrypted = false }) - fastify.register(fastifyCookie) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) fastify.register(fastifySession, { - secret: DEFAULT_SECRET, cookie: { secure: 'auto' } }) fastify.get('/', (request, reply) => { @@ -320,9 +318,8 @@ test('should set session cookie secureAuto change SameSite', async (t) => { fastify.addHook('onRequest', async (request, reply) => { request.raw.socket.encrypted = false }) - fastify.register(fastifyCookie) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) fastify.register(fastifySession, { - secret: DEFAULT_SECRET, cookie: { secure: 'auto', sameSite: 'none' } }) fastify.get('/', (request, reply) => { @@ -346,9 +343,8 @@ test('should set session cookie secureAuto keep SameSite when secured', async (t fastify.addHook('onRequest', async (request, reply) => { request.raw.socket.encrypted = true }) - fastify.register(fastifyCookie) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) fastify.register(fastifySession, { - secret: DEFAULT_SECRET, cookie: { secure: 'auto', sameSite: 'none' } }) fastify.get('/', (request, reply) => { @@ -372,9 +368,8 @@ test('should set session secure cookie secureAuto http encrypted', async (t) => fastify.addHook('onRequest', async (request, reply) => { request.raw.socket.encrypted = true }) - fastify.register(fastifyCookie) + fastify.register(fastifyCookie, { secret: DEFAULT_SECRET }) fastify.register(fastifySession, { - secret: DEFAULT_SECRET, cookie: { secure: 'auto' } }) fastify.get('/', (request, reply) => { diff --git a/test/session.test.js b/test/session.test.js index ca33696..5830515 100644 --- a/test/session.test.js +++ b/test/session.test.js @@ -5,14 +5,14 @@ const Fastify = require('fastify') const fastifyCookie = require('@fastify/cookie') const sinon = require('sinon') const fastifySession = require('..') -const { buildFastify, DEFAULT_OPTIONS, DEFAULT_COOKIE, DEFAULT_SESSION_ID, DEFAULT_SECRET, DEFAULT_COOKIE_VALUE } = require('./util') +const { buildFastify, DEFAULT_COOKIE_OPTIONS, DEFAULT_COOKIE, DEFAULT_SESSION_ID, DEFAULT_COOKIE_VALUE } = require('./util') test('should add session object to request', async (t) => { t.plan(2) const fastify = await buildFastify((request, reply) => { t.ok(request.session) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -30,7 +30,7 @@ test('should destroy the session', async (t) => { t.equal(request.session, null) reply.send(200) }) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -45,7 +45,7 @@ test('should add session.encryptedSessionId object to request', async (t) => { const fastify = await buildFastify((request, reply) => { t.ok(request.session.encryptedSessionId) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -60,7 +60,7 @@ test('should add session.cookie object to request', async (t) => { const fastify = await buildFastify((request, reply) => { t.ok(request.session.cookie) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -75,7 +75,7 @@ test('should add session.sessionId object to request', async (t) => { const fastify = await buildFastify((request, reply) => { t.ok(request.session.sessionId) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -91,7 +91,7 @@ test('should allow get/set methods for fetching/updating session values', async request.session.set('foo', 'bar') t.equal(request.session.get('foo'), 'bar') reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -108,13 +108,11 @@ test('should use custom sessionId generator if available (without request)', asy reply.send(200) }, { idGenerator: () => { - return `custom-${ - new Date().getTime() - }-${ - Math.random().toString().slice(2) - }` + return `custom-${new Date().getTime() + }-${Math.random().toString().slice(2) + }` }, - ...DEFAULT_OPTIONS + ...DEFAULT_COOKIE_OPTIONS }) t.teardown(() => fastify.close()) @@ -129,12 +127,10 @@ test('should keep user data in session throughout the time', async (t) => { t.plan(3) const fastify = Fastify() - const options = { - secret: DEFAULT_SECRET, + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession, { cookie: { secure: false } - } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + }) fastify.get('/', (request, reply) => { request.session.foo = 'bar' reply.send(200) @@ -164,13 +160,11 @@ test('should generate new sessionId', async (t) => { t.plan(3) const fastify = Fastify() - const options = { - secret: DEFAULT_SECRET, - cookie: { secure: false } - } let oldSessionId - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession, { + cookie: { secure: false } + }) fastify.get('/', (request, reply) => { oldSessionId = request.session.sessionId request.session.regenerate(error => { @@ -206,9 +200,8 @@ test('should decorate the server with decryptSession', async t => { t.plan(2) const fastify = Fastify() - const options = { secret: DEFAULT_SECRET } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession) t.teardown(() => fastify.close()) t.ok(await fastify.ready()) @@ -219,11 +212,7 @@ test('should decryptSession with custom request object', async (t) => { t.plan(4) const fastify = Fastify() - const options = { - secret: DEFAULT_SECRET - } - - fastify.register(fastifyCookie, options) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) fastify.register(fastifySession) fastify.addHook('onRequest', (request, reply, done) => { request.sessionStore.set(DEFAULT_SESSION_ID, { @@ -245,7 +234,10 @@ test('should decryptSession with custom request object', async (t) => { t.equal(response.statusCode, 200) const { sessionId } = fastify.parseCookie(DEFAULT_COOKIE) - const requestObj = {} + const requestObj = { + signCookie: fastify.signCookie, + unsignCookie: fastify.unsignCookie + } fastify.decryptSession(sessionId, requestObj, () => { t.equal(requestObj.session.cookie.maxAge, null) t.equal(requestObj.session.sessionId, DEFAULT_SESSION_ID) @@ -257,12 +249,8 @@ test('should decryptSession with custom cookie options', async (t) => { t.plan(2) const fastify = Fastify() - const options = { - secret: DEFAULT_SECRET - } - - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession) fastify.get('/', (request, reply) => { reply.send(200) @@ -276,7 +264,10 @@ test('should decryptSession with custom cookie options', async (t) => { t.equal(response.statusCode, 200) const { sessionId } = fastify.parseCookie(DEFAULT_COOKIE) - const requestObj = {} + const requestObj = { + signCookie: fastify.signCookie, + unsignCookie: fastify.unsignCookie + } fastify.decryptSession(sessionId, requestObj, { maxAge: 86400 }, () => { t.equal(requestObj.session.cookie.maxAge, 86400) }) @@ -293,16 +284,12 @@ test('should bubble up errors with destroy call if session expired', async (t) = destroy (id, cb) { cb(new Error('No can do')) } } - const fastifyCookieOpts = { - secret: DEFAULT_SECRET - } - const fastifySessionOpts = { + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession, { store, cookie: { secure: false } } - - fastify.register(fastifyCookie, fastifyCookieOpts) - fastify.register(fastifySession, fastifySessionOpts) + ) fastify.get('/', (request, reply) => { reply.send(200) @@ -323,13 +310,11 @@ test('should not reset session cookie expiration if rolling is false', async (t) const fastify = Fastify() - const options = { - secret: DEFAULT_SECRET, + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession, { rolling: false, cookie: { secure: false, maxAge: 10000 } - } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + }) fastify.addHook('onRequest', (request, reply, done) => { reply.send(request.session.expires) done() @@ -359,13 +344,11 @@ test('should update the expires property of the session using Session#touch() ev const fastify = Fastify() - const options = { - secret: DEFAULT_SECRET, + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) + fastify.register(fastifySession, { rolling: false, cookie: { secure: false, maxAge: 10000 } - } - fastify.register(fastifyCookie) - fastify.register(fastifySession, options) + }) fastify.addHook('onRequest', (request, reply, done) => { request.session.touch() reply.send(request.session.cookie.expires) @@ -395,9 +378,8 @@ test('should update the expires property of the session using Session#touch() ev test('should use custom sessionId generator if available (with request)', async (t) => { const fastify = Fastify() - fastify.register(fastifyCookie) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) fastify.register(fastifySession, { - secret: DEFAULT_SECRET, cookie: { secure: false, maxAge: 10000 }, idGenerator: (request) => { if (request.session?.returningVisitor) return `returningVisitor-${new Date().getTime()}` @@ -446,24 +428,19 @@ test('should use custom sessionId generator if available (with request)', async test('should use custom sessionId generator if available (with request and rolling false)', async (t) => { const fastify = Fastify() - fastify.register(fastifyCookie) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) fastify.register(fastifySession, { - secret: DEFAULT_SECRET, rolling: false, cookie: { secure: false, maxAge: 10000 }, idGenerator: (request) => { if (request.session?.returningVisitor) { - return `returningVisitor-${ - new Date().getTime() - }-${ - Math.random().toString().slice(2) - }` + return `returningVisitor-${new Date().getTime() + }-${Math.random().toString().slice(2) + }` } - return `custom-${ - new Date().getTime() - }-${ - Math.random().toString().slice(2) - }` + return `custom-${new Date().getTime() + }-${Math.random().toString().slice(2) + }` } }) t.teardown(() => fastify.close()) @@ -519,7 +496,7 @@ test('should reload the session', async (t) => { reply.send(200) }) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -549,7 +526,7 @@ test('should save the session', async (t) => { reply.send(200) }) }) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -565,7 +542,7 @@ test('destroy supports promises', async t => { await t.resolves(request.session.destroy()) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -582,7 +559,7 @@ test('destroy supports rejecting promises', async t => { reply.send(200) }, { - ...DEFAULT_OPTIONS, + ...DEFAULT_COOKIE_OPTIONS, store: { set (id, data, cb) { cb(null) }, get (id, cb) { cb(null) }, @@ -605,7 +582,7 @@ test('regenerate supports promises', async t => { await t.resolves(request.session.regenerate()) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -622,7 +599,7 @@ test('regenerate supports rejecting promises', async t => { reply.send(200) }, { - ...DEFAULT_OPTIONS, + ...DEFAULT_COOKIE_OPTIONS, store: { set (id, data, cb) { cb(new Error('no can do')) }, get (id, cb) { cb(null) }, @@ -645,7 +622,7 @@ test('reload supports promises', async t => { await t.resolves(request.session.reload()) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -662,7 +639,7 @@ test('reload supports rejecting promises', async t => { reply.send(200) }, { - ...DEFAULT_OPTIONS, + ...DEFAULT_COOKIE_OPTIONS, store: { set (id, data, cb) { cb(null) }, get (id, cb) { cb(new Error('no can do')) }, @@ -685,7 +662,7 @@ test('save supports promises', async t => { await t.resolves(request.session.save()) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -702,7 +679,7 @@ test('save supports rejecting promises', async t => { reply.send(200) }, { - ...DEFAULT_OPTIONS, + ...DEFAULT_COOKIE_OPTIONS, store: { set (id, data, cb) { cb(new Error('no can do')) }, get (id, cb) { cb(null) }, @@ -723,7 +700,7 @@ test("clears cookie if not backed by a session, and there's nothing to save", as t.plan(2) const fastify = await buildFastify((request, reply) => { reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -739,7 +716,7 @@ test('does not clear cookie if no session cookie in request', async t => { t.plan(2) const fastify = await buildFastify((request, reply) => { reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ @@ -757,10 +734,9 @@ test('only save session when it changes', async t => { const store = new Map() const fastify = Fastify() - fastify.register(fastifyCookie) + fastify.register(fastifyCookie, DEFAULT_COOKIE_OPTIONS) fastify.register(fastifySession, { - ...DEFAULT_OPTIONS, saveUninitialized: false, cookie: { secure: false }, store: { diff --git a/test/store.test.js b/test/store.test.js index 97f517a..8f6c911 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -2,7 +2,7 @@ const test = require('tap').test const fastifyPlugin = require('fastify-plugin') -const { buildFastify, DEFAULT_OPTIONS, DEFAULT_COOKIE, DEFAULT_SECRET, DEFAULT_SESSION_ID } = require('./util') +const { buildFastify, DEFAULT_COOKIE_OPTIONS, DEFAULT_COOKIE, DEFAULT_SECRET, DEFAULT_SESSION_ID } = require('./util') const { Store } = require('..') test('should decorate request with sessionStore', async (t) => { @@ -11,7 +11,7 @@ test('should decorate request with sessionStore', async (t) => { const fastify = await buildFastify((request, reply) => { t.ok(request.sessionStore) reply.send(200) - }, DEFAULT_OPTIONS) + }, DEFAULT_COOKIE_OPTIONS) t.teardown(() => fastify.close()) const response = await fastify.inject({ diff --git a/test/util.js b/test/util.js index 3bc32f1..c857ef7 100644 --- a/test/util.js +++ b/test/util.js @@ -5,7 +5,7 @@ const fastifyCookie = require('@fastify/cookie') const fastifySession = require('../lib/fastifySession') const DEFAULT_SECRET = 'cNaoPYAwF60HZJzkcNaoPYAwF60HZJzk' -const DEFAULT_OPTIONS = { secret: DEFAULT_SECRET } +const DEFAULT_COOKIE_OPTIONS = { secret: DEFAULT_SECRET } const DEFAULT_SESSION_ID = 'Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN' const DEFAULT_ENCRYPTED_SESSION_ID = `${DEFAULT_SESSION_ID}.B7fUDYXU9fXF9pNuL3qm4NVmSduLJ6kzCOPh5JhHGoE` const DEFAULT_COOKIE_VALUE = `sessionId=${DEFAULT_ENCRYPTED_SESSION_ID};` @@ -31,7 +31,7 @@ async function buildFastify (handler, options, plugin) { module.exports = { buildFastify, DEFAULT_SECRET, - DEFAULT_OPTIONS, + DEFAULT_COOKIE_OPTIONS, DEFAULT_SESSION_ID, DEFAULT_ENCRYPTED_SESSION_ID, DEFAULT_COOKIE_VALUE, From 2125428e848125648c1a02779e52fe13f9a40667 Mon Sep 17 00:00:00 2001 From: Uzlopak Date: Tue, 16 Aug 2022 14:43:13 +0200 Subject: [PATCH 15/16] Update lib/session.js Co-authored-by: Simen Bekkhus --- lib/session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/session.js b/lib/session.js index c43d83d..9de9157 100644 --- a/lib/session.js +++ b/lib/session.js @@ -32,7 +32,7 @@ module.exports = class Session { this.touch() if (!this.sessionId) { this.sessionId = this[generateId](this[requestKey]) - this.encryptedSessionId = this[requestKey].signCookie(this.sessionId) + this.encryptedSessionId = request.signCookie(this.sessionId) } this[originalHash] = this[hash]() } From 90d9a4982c2dd623ad0c6ea6ad1a9dad0351110e Mon Sep 17 00:00:00 2001 From: uzlopak Date: Tue, 16 Aug 2022 14:44:03 +0200 Subject: [PATCH 16/16] move @fastify/cookie to devDependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c8bc24..f9b6bb6 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "author": "Denis Fäcke", "license": "MIT", "dependencies": { - "@fastify/cookie": "^8.0.0", "fastify-plugin": "^4.0.0", "safe-stable-stringify": "^2.3.1" }, @@ -28,6 +27,7 @@ "url": "git+https://github.com/fastify/session.git" }, "devDependencies": { + "@fastify/cookie": "^8.0.0", "@types/node": "^18.0.0", "connect-redis": "^6.1.3", "cronometro": "^1.1.0",