-
-
Notifications
You must be signed in to change notification settings - Fork 54
feat: remove session id from session data #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -10,36 +10,33 @@ const stringify = configureStringifier({ bigint: false }) | |||
|
|
||||
| const maxAge = Symbol('maxAge') | ||||
| const secretKey = Symbol('secretKey') | ||||
| const sign = Symbol('sign') | ||||
| const addDataToSession = Symbol('addDataToSession') | ||||
| const generateId = Symbol('generateId') | ||||
| const requestKey = Symbol('request') | ||||
| const cookieOptsKey = Symbol('cookieOpts') | ||||
| const originalHash = Symbol('originalHash') | ||||
| const hash = Symbol('hash') | ||||
| const sessionIdKey = Symbol('sessionId') | ||||
| const encryptedSessionIdKey = Symbol('encryptedSessionId') | ||||
|
|
||||
| module.exports = class Session { | ||||
| constructor (request, idGenerator, cookieOpts, secret, prevSession = {}) { | ||||
| constructor (request, idGenerator, cookieOpts, secret, prevSession = {}, sessionId = idGenerator(request)) { | ||||
| this[generateId] = idGenerator | ||||
| this.expires = null | ||||
| this.cookie = new Cookie(cookieOpts) | ||||
| this[cookieOptsKey] = cookieOpts | ||||
| this[maxAge] = cookieOpts.maxAge | ||||
| this[secretKey] = secret | ||||
| this[addDataToSession](prevSession) | ||||
| this[requestKey] = request | ||||
| this.touch() | ||||
| if (!this.sessionId) { | ||||
| this.sessionId = this[generateId](this[requestKey]) | ||||
| this.encryptedSessionId = this[sign]() | ||||
| } | ||||
| this[sessionIdKey] = sessionId | ||||
| this[encryptedSessionIdKey] = cookieSignature.sign(sessionId, secret) | ||||
| this[originalHash] = this[hash]() | ||||
| } | ||||
|
|
||||
| touch () { | ||||
| if (this[maxAge]) { | ||||
| this.expires = new Date(Date.now() + this[maxAge]) | ||||
| this.cookie.expires = this.expires | ||||
| this.cookie.expires = new Date(Date.now() + this[maxAge]) | ||||
| } | ||||
| } | ||||
|
|
||||
|
|
@@ -71,7 +68,7 @@ module.exports = class Session { | |||
|
|
||||
| [addDataToSession] (prevSession) { | ||||
| for (const key in prevSession) { | ||||
| if (!['expires', 'cookie'].includes(key)) { | ||||
| if (!['cookie', 'sessionId', 'encryptedSessionId'].includes(key)) { | ||||
| this[key] = prevSession[key] | ||||
| } | ||||
| } | ||||
|
|
@@ -87,14 +84,14 @@ module.exports = class Session { | |||
|
|
||||
| destroy (callback) { | ||||
| if (callback) { | ||||
| this[requestKey].sessionStore.destroy(this.sessionId, error => { | ||||
| this[requestKey].sessionStore.destroy(this[sessionIdKey], error => { | ||||
| this[requestKey].session = null | ||||
|
|
||||
| callback(error) | ||||
| }) | ||||
| } else { | ||||
| return new Promise((resolve, reject) => { | ||||
| this[requestKey].sessionStore.destroy(this.sessionId, error => { | ||||
| this[requestKey].sessionStore.destroy(this[sessionIdKey], error => { | ||||
| this[requestKey].session = null | ||||
|
|
||||
| if (error) { | ||||
|
|
@@ -109,15 +106,15 @@ 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].sessionStore.get(this[sessionIdKey], (error, session) => { | ||||
| this[requestKey].session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session, this[sessionIdKey]) | ||||
|
|
||||
| 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].sessionStore.get(this[sessionIdKey], (error, session) => { | ||||
| this[requestKey].session = new Session(this[requestKey], this[generateId], this[cookieOptsKey], this[secretKey], session, this[sessionIdKey]) | ||||
|
|
||||
| if (error) { | ||||
| reject(error) | ||||
|
|
@@ -131,12 +128,12 @@ module.exports = class Session { | |||
|
|
||||
| save (callback) { | ||||
| if (callback) { | ||||
| this[requestKey].sessionStore.set(this.sessionId, this, error => { | ||||
| this[requestKey].sessionStore.set(this[sessionIdKey], this, error => { | ||||
| callback(error) | ||||
| }) | ||||
| } else { | ||||
| return new Promise((resolve, reject) => { | ||||
| this[requestKey].sessionStore.set(this.sessionId, this, error => { | ||||
| this[requestKey].sessionStore.set(this[sessionIdKey], this, error => { | ||||
| if (error) { | ||||
| reject(error) | ||||
| } else { | ||||
|
|
@@ -147,16 +144,13 @@ module.exports = class Session { | |||
| } | ||||
| } | ||||
|
|
||||
| [sign] () { | ||||
| return cookieSignature.sign(this.sessionId, this[secretKey]) | ||||
| } | ||||
|
|
||||
| [hash] () { | ||||
| const sess = this | ||||
| const str = stringify(sess, function (key, val) { | ||||
| // ignore sess.cookie property | ||||
| if (this === sess && key === 'cookie') { | ||||
| return | ||||
| // we want `touch` to affect the hash of the session | ||||
| return sess.cookie.expires | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be e.g. str becomes something like |
||||
| } | ||||
|
|
||||
| return val | ||||
|
|
@@ -172,12 +166,19 @@ 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) | ||||
| get sessionId () { | ||||
| return this[sessionIdKey] | ||||
| } | ||||
|
|
||||
| get encryptedSessionId () { | ||||
| return this[encryptedSessionIdKey] | ||||
| } | ||||
|
|
||||
| static restore (request, idGenerator, cookieOpts, secret, prevSession, sessionId) { | ||||
| const restoredSession = new Session(request, idGenerator, cookieOpts, secret, prevSession, sessionId) | ||||
| const restoredCookie = new Cookie(cookieOpts) | ||||
| restoredCookie.expires = new Date(prevSession.cookie.expires) | ||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't check Line 66 in 76459c2
expires. So this could be a TypeError, depending on what's in the store
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what should be done about this?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. either works I think, but I think it's weird to look at the expiry of the cookie and not the expiry itself. That said,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Go for that! |
||||
| restoredSession.cookie = restoredCookie | ||||
| restoredSession.expires = restoredCookie.expires | ||||
| restoredSession[originalHash] = restoredSession[hash]() | ||||
| return restoredSession | ||||
| } | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,7 +51,7 @@ test('should support multiple secrets', async (t) => { | |
| const plugin = fastifyPlugin(async (fastify, opts) => { | ||
| fastify.addHook('onRequest', (request, reply, done) => { | ||
| request.sessionStore.set('aYb4uTIhdBXCfk_ylik4QN6-u26K0u0e', { | ||
| expires: Date.now() + 1000 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this test, so this change is probably wrong
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what would you like to do about this?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to dig into the test to understand what it's supposed to test.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the intent of this test was to verify that the old secret still works. The expiration should be + 1000 to make sure the cookie hasn't expired yet.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did some spelunkling. Why did this test pass earlier?
Thanks to your changes, sessionid / encryptedSessionId don't have to be in the test prep. This means the existing session will be used! |
||
| cookie: { expires: Date.now() - 1000 } | ||
| }, done) | ||
| }) | ||
| }) | ||
|
|
@@ -98,9 +98,8 @@ test('should set session cookie using the default cookie name', async (t) => { | |
| const plugin = fastifyPlugin(async (fastify, opts) => { | ||
| fastify.addHook('onRequest', (request, reply, done) => { | ||
| request.sessionStore.set('Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN', { | ||
| expires: Date.now() + 1000, | ||
| sessionId: 'Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN', | ||
|
mcollina marked this conversation as resolved.
|
||
| cookie: { secure: true, httpOnly: true, path: '/' } | ||
| cookie: { expires: Date.now() + 1000, secure: true, httpOnly: true, path: '/' } | ||
| }, done) | ||
| }) | ||
| }) | ||
|
|
@@ -119,17 +118,16 @@ test('should set session cookie using the default cookie name', async (t) => { | |
| }) | ||
|
|
||
| t.is(statusCode, 200) | ||
| t.regex(cookie, /sessionId=undefined; Path=\/; HttpOnly; Secure/) | ||
| t.regex(cookie, /sessionId=.*\..*; Path=\/; HttpOnly; Secure/) | ||
| }) | ||
|
|
||
| test('should create new session on expired session', async (t) => { | ||
| t.plan(2) | ||
| const plugin = fastifyPlugin(async (fastify, opts) => { | ||
| fastify.addHook('onRequest', (request, reply, done) => { | ||
| request.sessionStore.set('Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN', { | ||
| expires: Date.now() - 1000, | ||
| sessionId: 'Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN', | ||
| cookie: { secure: true, httpOnly: true, path: '/' } | ||
| cookie: { expires: Date.now() - 1000, secure: true, httpOnly: true, path: '/' } | ||
| }, done) | ||
| }) | ||
| }) | ||
|
|
@@ -163,12 +161,12 @@ test('should set session.expires if maxAge', async (t) => { | |
| const plugin = fastifyPlugin(async (fastify, opts) => { | ||
| fastify.addHook('onRequest', (request, reply, done) => { | ||
| request.sessionStore.set('Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN', { | ||
| expires: Date.now() + 1000 | ||
| cookie: { expires: Date.now() + 1000 } | ||
| }, done) | ||
| }) | ||
| }) | ||
| function handler (request, reply) { | ||
| t.truthy(request.session.expires) | ||
| t.truthy(request.session.cookie.expires) | ||
| reply.send(200) | ||
| } | ||
| const port = await testServer(handler, options, plugin) | ||
|
|
@@ -187,7 +185,7 @@ test('should set new session cookie if expired', async (t) => { | |
| const plugin = fastifyPlugin(async (fastify, opts) => { | ||
| fastify.addHook('onRequest', (request, reply, done) => { | ||
| request.sessionStore.set('Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN', { | ||
| expires: Date.now() + 1000 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this test is explicitly "if expired", not sure why expiry was set in the future?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, this test only passed because of I'm starting forking off your branch, rebasing and removing... |
||
| cookie: { expires: Date.now() - 1000 } | ||
| }, done) | ||
| }) | ||
| }) | ||
|
|
@@ -258,7 +256,7 @@ test('should create new session if cookie contains invalid session', async (t) = | |
| const plugin = fastifyPlugin(async (fastify, opts) => { | ||
| fastify.addHook('onRequest', (request, reply, done) => { | ||
| request.sessionStore.set('Qk_XT2K7-clT-x1tVvoY6tIQ83iP72KN', { | ||
| expires: Date.now() + 1000 | ||
| cookie: { expires: Date.now() + 1000 } | ||
| }, done) | ||
| }) | ||
| }) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.