diff --git a/README.md b/README.md index 5abd33e..cde1c47 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Allows to destroy the session in the store. If you do not pass a callback, a Pro Updates the `expires` property of the session. -#### Session#regenerate(callback) +#### Session#regenerate([ignoreFields, ]callback) Regenerates the session by generating a new `sessionId` and persist it to the store. If you do not pass a callback, a Promise will be returned. ```js @@ -125,6 +125,8 @@ fastify.get('/regenerate', (request, reply, done) => { }); ``` +You can pass an array of fields that should be kept when the session is regenerated + #### Session#reload(callback) Reloads the session data from the store and re-populates the `request.session` object. If you do not pass a callback, a Promise will be returned. diff --git a/lib/session.js b/lib/session.js index 59eca9c..5a7fc2f 100644 --- a/lib/session.js +++ b/lib/session.js @@ -43,10 +43,26 @@ module.exports = class Session { } } - regenerate (callback) { - if (callback) { - const session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) + regenerate (keys, callback) { + if (typeof keys === 'function') { + callback = keys + keys = undefined + } + + const session = new Session( + this[requestKey], + this[generateId], + this[cookieOptsKey], + this[secretKey] + ) + + if (Array.isArray(keys)) { + for (const key of keys) { + session.set(key, this[key]) + } + } + if (callback) { this[requestKey].sessionStore.set(session.sessionId, session, error => { this[requestKey].session = session @@ -54,8 +70,6 @@ module.exports = class Session { }) } else { return new Promise((resolve, reject) => { - const session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey]) - this[requestKey].sessionStore.set(session.sessionId, session, error => { this[requestKey].session = session diff --git a/package.json b/package.json index 56f7e15..bf17922 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fastify/session", - "version": "8.2.0", + "version": "8.3.0", "description": "a session plugin for fastify", "main": "lib/fastifySession.js", "scripts": { diff --git a/test/session.test.js b/test/session.test.js index 895d932..b6c9400 100644 --- a/test/session.test.js +++ b/test/session.test.js @@ -197,6 +197,88 @@ test('should generate new sessionId', async (t) => { t.is(response2.statusCode, 200) }) +test('should generate new sessionId keeping ignoreFields', async (t) => { + t.plan(4) + const fastify = Fastify() + + const options = { + secret: 'cNaoPYAwF60HZJzkcNaoPYAwF60HZJzk', + cookie: { secure: false } + } + let oldSessionId + fastify.register(fastifyCookie) + fastify.register(fastifySession, options) + fastify.get('/', (request, reply) => { + oldSessionId = request.session.sessionId + request.session.set('message', 'hello world') + request.session.regenerate(['message'], error => { + if (error) { + reply.status(500).send('Error ' + error) + } else { + reply.send(200) + } + }) + }) + fastify.get('/check', (request, reply) => { + t.not(request.session.sessionId, oldSessionId) + t.is(request.session.get('message'), 'hello world') + reply.send(200) + }) + await fastify.listen({ port: 0 }) + t.teardown(() => { fastify.close() }) + + const response1 = await fastify.inject({ + url: '/' + }) + + t.is(response1.statusCode, 200) + + const response2 = await fastify.inject({ + url: '/check', + headers: { Cookie: response1.headers['set-cookie'] } + }) + + t.is(response2.statusCode, 200) +}) + +test('should generate new sessionId keeping ignoreFields (async)', async (t) => { + t.plan(4) + const fastify = Fastify() + + const options = { + secret: 'cNaoPYAwF60HZJzkcNaoPYAwF60HZJzk', + cookie: { secure: false } + } + let oldSessionId + fastify.register(fastifyCookie) + fastify.register(fastifySession, options) + fastify.get('/', async (request, reply) => { + oldSessionId = request.session.sessionId + request.session.set('message', 'hello world') + await request.session.regenerate(['message']) + reply.send(200) + }) + fastify.get('/check', (request, reply) => { + t.not(request.session.sessionId, oldSessionId) + t.is(request.session.get('message'), 'hello world') + reply.send(200) + }) + await fastify.listen({ port: 0 }) + t.teardown(() => { fastify.close() }) + + const response1 = await fastify.inject({ + url: '/' + }) + + t.is(response1.statusCode, 200) + + const response2 = await fastify.inject({ + url: '/check', + headers: { Cookie: response1.headers['set-cookie'] } + }) + + t.is(response2.statusCode, 200) +}) test('should decorate the server with decryptSession', async t => { t.plan(2) diff --git a/types/types.d.ts b/types/types.d.ts index c5c2ebf..d65fe5c 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -15,6 +15,8 @@ declare module 'fastify' { interface Session extends SessionData {} } +type Callback = (err?: Error) => void; + interface SessionData extends ExpressSessionData { sessionId: string; @@ -26,8 +28,10 @@ interface SessionData extends ExpressSessionData { /** * Regenerates the session by generating a new `sessionId`. */ + regenerate(ignoreFields: string[], callback: Callback): void; regenerate(callback: (err?: Error) => void): void; regenerate(): Promise; + regenerate(ignoreFields: string[]): Promise; /** Allows to destroy the session in the store. */ destroy(callback: (err?: Error) => void): void; diff --git a/types/types.test-d.ts b/types/types.test-d.ts index 1706faf..b285a42 100644 --- a/types/types.test-d.ts +++ b/types/types.test-d.ts @@ -2,10 +2,10 @@ import fastify, { FastifyInstance, FastifyReply, FastifyRequest, - Session -} from 'fastify'; -import { expectError, expectType } from 'tsd'; -import plugin from '..'; + Session, +} from "fastify"; +import { expectError, expectType } from "tsd"; +import plugin from ".."; class EmptyStore { set(_sessionId: string, _session: any, _callback: Function) {} @@ -15,7 +15,7 @@ class EmptyStore { destroy(_sessionId: string, _callback: Function) {} } -declare module 'fastify' { +declare module "fastify" { interface Session { get(key: string): T; set(key: string, value: unknown): void; @@ -25,51 +25,54 @@ declare module 'fastify' { } } -const secret = 'ABCDEFGHIJKLNMOPQRSTUVWXYZ012345'; +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, { secret: "DizIzSecret" }); +app.register(plugin, { secret: "DizIzSecret", rolling: true }); app.register(plugin, { secret, rolling: false, cookie: { - secure: false - } + secure: false, + }, }); app.register(plugin, { secret, cookie: { - secure: false - } + secure: false, + }, }); app.register(plugin, { secret, - store: new EmptyStore() + store: new EmptyStore(), }); app.register(plugin, { secret, - idGenerator: () => Date.now() + '' + idGenerator: () => Date.now() + "", }); app.register(plugin, { secret, - unsignSignedCookie: true + unsignSignedCookie: true, }); app.register(plugin, { secret, - idGenerator: (request) => `${request == undefined ? 'null' : request.ip}-${Date.now()}` + idGenerator: (request) => + `${request == undefined ? "null" : request.ip}-${Date.now()}`, }); expectError(app.register(plugin, {})); -expectError(app.register(plugin, { - secret, - unsignSignedCookie: 'not-a-boolean' -})); +expectError( + app.register(plugin, { + secret, + unsignSignedCookie: "not-a-boolean", + }) +); app.route({ - method: 'GET', - url: '/', + method: "GET", + url: "/", preHandler(req, _rep, next) { expectType(req.session.destroy(next)); expectType>(req.session.destroy()); @@ -81,14 +84,14 @@ app.route({ expectError((request.sessionStore = null)); expectError(request.session.doesNotExist()); expectType<{ id: number } | undefined>(request.session.user); - request.sessionStore.set('session-set-test', request.session, () => {}) - request.sessionStore.get('', (err, session) => { + request.sessionStore.set("session-set-test", request.session, () => {}); + request.sessionStore.get("", (err, session) => { expectType(err); expectType(session); expectType<{ id: number } | undefined>(session.user); }); - expectType(request.session.set('foo', 'bar')); - expectType(request.session.get('foo')); + expectType(request.session.set("foo", "bar")); + expectType(request.session.get("foo")); expectType(request.session.touch()); expectType(request.session.isModified()); expectType(request.session.reload(() => {})); @@ -99,5 +102,7 @@ app.route({ expectType>(request.session.destroy()); expectType>(request.session.regenerate()); expectType>(request.session.save()); - } + expectType(request.session.regenerate(["foo"], () => {})); + expectType>(request.session.regenerate(["foo"])); + }, });