Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

next-session

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

next-session - npm Package Compare versions

Comparing version 2.1.1 to 2.2.0

12

CHANGELOG.md
# Changelog
## 2.2.0
### Minor
- Allow manual session commit (#59)
### Patches
- Futhur check for headersSent (dd561a71c12ab6248b02873dd50cb114046be430)
## 2.1.1
# Patches
### Patches

@@ -7,0 +17,0 @@ - Only warn if store is memoryStore (1d53f7d39c48e14288b738437c4577767534c08b)

2

package.json
{
"name": "next-session",
"version": "2.1.1",
"version": "2.2.0",
"description": "Simple promise-based session middleware for Next.js",

@@ -5,0 +5,0 @@ "keywords": [

# next-session
[![npm](https://badgen.net/npm/v/next-session)](https://www.npmjs.com/package/next-session)
[![minified size](https://badgen.net/bundlephobia/min/next-session)](https://bundlephobia.com/result?p=next-session)
[![install size](https://packagephobia.now.sh/badge?p=next-session@2.1.0)](https://packagephobia.now.sh/result?p=next-session@2.1.0)
[![CircleCI](https://circleci.com/gh/hoangvvo/next-session.svg?style=svg)](https://circleci.com/gh/hoangvvo/next-session)

@@ -218,3 +218,3 @@ [![codecov](https://codecov.io/gh/hoangvvo/next-session/branch/master/graph/badge.svg)](https://codecov.io/gh/hoangvvo/next-session)

Page.getInitialProps = async ({ req, res }) => await useSession(req, res);
Page.getInitialProps = ({ req, res }) => useSession(req, res);
```

@@ -231,5 +231,6 @@

| storePromisify | Promisify stores that are callback based. This allows you to use `next-session` with Connect stores (ex. used in [express-session](https://github.com/expressjs/session)) | `false` |
| generateId | The function to generate a new session ID. This needs to return a string. | `crypto.randomBytes(16).toString('hex')` |
| genid | The function to generate a new session ID. This needs to return a string. | `crypto.randomBytes(16).toString('hex')` |
| rolling | Force the cookie to be set on every request despite no modification, extending the life time of the cookie in the browser | `false` |
| touchAfter | On every request, the session store extends the life time of the session even when no changes are made (The same is done to Cookie). However, this may increase the load of the database. Setting this value will ask the store to only do so an amount of time since the Cookie is touched, with exception that the session is modified. Setting the value to `-1` will disable `touch()`. | `0` (Touch every time) |
| touchAfter | On every request, session's life time are usually extended despite no changes. This value defer the process (to lower database load). Disable `touch()` by setting this to `-1`. | `0` (Touch every time) |
| autoCommit | Automatically save session and set cookie header | `true` |
| cookie.secure | Specifies the boolean value for the **Secure** `Set-Cookie` attribute. If set to true, cookie is only sent to the server with an encrypted request over the HTTPS protocol. | `false` |

@@ -255,2 +256,6 @@ | cookie.httpOnly | Specifies the boolean value for the **httpOnly** `Set-Cookie` attribute. If set to true, cookies are inaccessible to client-side scripts. This is to help mitigate [cross-site scripting (XSS) attacks](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting). | `true` |

#### req.session.id
The unique id that associates to the current session.
#### req.session.destroy()

@@ -264,5 +269,5 @@

#### req.session.id
#### req.session.commit()
The unique id that associates to the current session. This should not be modified.
If `options.autoCommit` is `false`, call this to save session to store and set cookie header.

@@ -269,0 +274,0 @@ ### Session Store

@@ -11,37 +11,64 @@ /* eslint-disable no-param-reassign */

const generateSessionId = () => crypto.randomBytes(16).toString('hex');
function proxyEnd(res, fn) {
let ended = false;
const oldEnd = res.end;
res.end = function resEndProxy(...args) {
const self = this;
if (res.headersSent || res.finished || ended) return;
ended = true;
fn(() => {
oldEnd.apply(self, args);
});
};
}
const hash = (sess) => {
const str = JSON.stringify(sess, (key, val) => {
if (key === 'cookie') {
// filtered out session.cookie
return undefined;
}
return val;
});
// hash
return crypto
.createHash('sha1')
.update(str, 'utf8')
.digest('hex');
};
let storeReady = true;
const session = (options = {}) => {
const name = options.name || 'sessionId';
const cookieOptions = options.cookie || {};
const store = options.store || new MemoryStore();
const generateId = options.generateId || generateSessionId;
const touchAfter = options.touchAfter ? parseToMs(options.touchAfter) : 0;
const rollingSession = options.rolling || false;
const storePromisify = options.storePromisify || false;
async function initialize(req, res, options) {
// eslint-disable-next-line no-multi-assign
const originalId = req.sessionId = req.headers && req.headers.cookie
? parseCookie(req.headers.cookie)[options.name]
: null;
// Notify MemoryStore should not be used in production
// eslint-disable-next-line no-console
if (store instanceof MemoryStore) console.warn('MemoryStore should not be used in production environment.');
req.sessionStore = options.store;
// Validate parameters
if (typeof generateId !== 'function') throw new TypeError('generateId option must be a function');
if (req.sessionId) {
const sess = await req.sessionStore.get(req.sessionId);
if (sess) req.sessionStore.createSession(req, res, sess);
}
if (!req.session) req.sessionStore.generate(req, res, options.generateId(), options.cookie);
req._session = {
// FIXME: Possible dataloss
original: JSON.parse(JSON.stringify(req.session)),
originalId,
options,
};
// autocommit
if (options.autoCommit) {
proxyEnd(res, async (done) => {
if (req.session) { await req.session.commit(); }
done();
});
}
return req.session;
}
function session(opts = {}) {
const options = {
name: opts.name || 'sessionId',
store: opts.store || new MemoryStore(),
storePromisify: opts.storePromisify || false,
generateId: opts.genid || opts.generateId || function generateId() { return crypto.randomBytes(16).toString('hex'); },
rolling: opts.rolling || false,
touchAfter: opts.touchAfter ? parseToMs(opts.touchAfter) : 0,
cookie: opts.cookie || {},
autoCommit: typeof opts.autoCommit !== 'undefined' ? opts.autoCommit : true,
};
const { store, storePromisify } = options;
// Promisify callback-based store.

@@ -63,91 +90,11 @@ if (storePromisify) {

return (req, res, next) => {
/**
* Modify req and res to "inject" the middleware
*/
if (req.session) return next();
// check for store readiness before proceeded
if (!storeReady) return next();
return async (req, res, next) => {
if (req.session || !storeReady) { next(); return; }
// TODO: add pathname mismatch check
// Expose store
req.sessionStore = store;
// Try parse cookie if not already
req.cookies = req.cookies
|| (req.headers && typeof req.headers.cookie === 'string' && parseCookie(req.headers.cookie)) || {};
// Get sessionId cookie from Next.js parsed req.cookies
req.sessionId = req.cookies[name];
const getSession = () => {
// Return a session object
if (!req.sessionId) {
// If no sessionId found in Cookie header, generate one
return Promise.resolve(hash(req.sessionStore.generate(req, generateId(), cookieOptions)));
}
return req.sessionStore.get(req.sessionId)
.then((sess) => {
if (sess) {
return hash(req.sessionStore.createSession(req, sess));
}
return hash(req.sessionStore.generate(req, generateId(), cookieOptions));
});
};
return getSession().then((hashedsess) => {
let sessionSaved = false;
const oldEnd = res.end;
let ended = false;
// Proxy res.end
res.end = function resEndProxy(...args) {
// If res.end() is called multiple times, do nothing after the first time
if (ended) {
return false;
}
ended = true;
// save session to store if there are changes (and there is a session)
const saveSession = () => {
if (req.session) {
if (hash(req.session) !== hashedsess) {
sessionSaved = true;
return req.session.save();
}
// Touch: extend session time despite no modification
if (req.session.cookie.maxAge && touchAfter >= 0) {
const minuteSinceTouched = (
req.session.cookie.maxAge
- (req.session.cookie.expires - new Date())
);
if ((minuteSinceTouched < touchAfter)) {
return Promise.resolve();
}
return req.session.touch();
}
}
return Promise.resolve();
};
return saveSession()
.then(() => {
if (
(req.cookies[name] !== req.sessionId || sessionSaved || rollingSession)
&& req.session
) {
res.setHeader('Set-Cookie', req.session.cookie.serialize(name, req.sessionId));
}
oldEnd.apply(this, args);
});
};
next();
});
await initialize(req, res, options);
next();
};
};
}
const useSession = (req, res, opts) => {
function useSession(req, res, opts) {
if (!req || !res) return Promise.resolve();

@@ -161,9 +108,8 @@ return new Promise((resolve) => {

});
};
}
const withSession = (handler, options) => {
function withSession(handler, options) {
const isApiRoutes = !Object.prototype.hasOwnProperty.call(handler, 'getInitialProps');
const oldHandler = (isApiRoutes) ? handler : handler.getInitialProps;
function handlerProxy(...args) {

@@ -188,5 +134,6 @@ let req;

return handler;
};
}
module.exports = session;
module.exports.initialize = initialize;
module.exports.withSession = withSession;

@@ -193,0 +140,0 @@ module.exports.useSession = useSession;

@@ -0,4 +1,7 @@

function stringify(sess) { return JSON.stringify(sess, (key, val) => (key === 'cookie' ? undefined : val)); }
class Session {
constructor(req, sess) {
constructor(req, res, sess) {
Object.defineProperty(this, 'req', { value: req });
Object.defineProperty(this, 'res', { value: res });
Object.defineProperty(this, 'id', { value: req.sessionId });

@@ -32,4 +35,28 @@ if (typeof sess === 'object') {

}
async commit() {
const { name, rolling, touchAfter } = this.req._session.options;
let saved = false;
if (stringify(this) !== stringify(this.req._session.original)) {
await this.save();
saved = true;
}
// Touch: extend session time despite no modification
if (this.cookie.maxAge && touchAfter >= 0) {
const minuteSinceTouched = (
this.cookie.maxAge
- (this.cookie.expires - new Date())
);
if ((minuteSinceTouched >= touchAfter)) await this.touch();
}
if (
(saved || rolling || this.req._session.originalId !== this.req.sessionId)
&& this
) this.res.setHeader('Set-Cookie', this.cookie.serialize(name, this.req.sessionId));
}
}
module.exports = Session;

@@ -12,5 +12,5 @@ const util = require('util');

Store.prototype.generate = function generate(req, genId, cookieOptions) {
Store.prototype.generate = function generate(req, res, genId, cookieOptions) {
req.sessionId = genId;
req.session = new Session(req);
req.session = new Session(req, res);
req.session.cookie = new Cookie(cookieOptions);

@@ -20,3 +20,3 @@ return req.session;

Store.prototype.createSession = function createSession(req, sess) {
Store.prototype.createSession = function createSession(req, res, sess) {
const thisSess = sess;

@@ -26,3 +26,3 @@ const { expires } = thisSess.cookie;

if (typeof expires === 'string') thisSess.cookie.expires = new Date(expires);
req.session = new Session(req, thisSess);
req.session = new Session(req, res, thisSess);
return req.session;

@@ -29,0 +29,0 @@ };

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc