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 3.3.2 to 3.4.0

5

CHANGELOG.md
# Changelog
## 3.4.0
- Refactor, remove rolling option and fix unreliable tests (#283)
- Avoid override set-cookie (#320)
## 3.3.2

@@ -4,0 +9,0 @@

4

dist/compat.d.ts
import { Store as ExpressStore } from 'express-session';
export declare function Store(): void;
declare function CompatibleStore(): void;
declare function expressSession(options?: any): any;
declare namespace expressSession {
var Store: typeof import("./compat").Store;
var Store: typeof CompatibleStore;
var MemoryStore: typeof CallbackMemoryStore;

@@ -7,0 +7,0 @@ }

@@ -6,18 +6,17 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.promisifyStore = exports.expressSession = exports.Store = void 0;
exports.promisifyStore = exports.expressSession = void 0;
const util_1 = require("util");
const events_1 = require("events");
const memory_1 = __importDefault(require("./store/memory"));
function Store() {
function CompatibleStore() {
// @ts-ignore
events_1.EventEmitter.call(this);
}
exports.Store = Store;
util_1.inherits(Store, events_1.EventEmitter);
util_1.inherits(CompatibleStore, events_1.EventEmitter);
// no-op for compat
function expressSession(options) { }
exports.expressSession = expressSession;
expressSession.Store = Store;
expressSession.Store = CompatibleStore;
function CallbackMemoryStore() { }
util_1.inherits(CallbackMemoryStore, Store);
util_1.inherits(CallbackMemoryStore, CompatibleStore);
CallbackMemoryStore.prototype.get = util_1.callbackify(memory_1.default.prototype.get);

@@ -29,5 +28,5 @@ CallbackMemoryStore.prototype.set = util_1.callbackify(memory_1.default.prototype.set);

function promisifyStore(store) {
console.warn('promisifyStore has been deprecated! You can simply remove it.');
console.warn('promisifyStore has been deprecated: express-session store still works without using this.');
return store;
}
exports.promisifyStore = promisifyStore;

@@ -1,5 +0,3 @@

import { Options, SessionData } from './types';
import { Options } from './types';
import { IncomingMessage, ServerResponse } from 'http';
export default function session(opts?: Options): (req: IncomingMessage & {
session: SessionData;
}, res: ServerResponse, next: (err?: any) => void) => void;
export default function session(opts?: Options): (req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => void;
/// <reference types="node" />
import { IncomingMessage, ServerResponse } from 'http';
import { Options, SessionData } from './types';
import { Session, Options } from './types';
export declare function applySession<T = {}>(req: IncomingMessage & {
session: SessionData;
}, res: ServerResponse, opts?: Options): Promise<void>;
session?: Session | null | undefined;
}, res: ServerResponse, options?: Options): Promise<void>;

@@ -10,41 +10,34 @@ "use strict";

const memory_1 = __importDefault(require("./store/memory"));
const shouldTouch = (cookie, touchAfter) => {
if (touchAfter === -1 || !cookie.maxAge)
return false;
return (cookie.maxAge * 1000 - (cookie.expires.getTime() - Date.now()) >=
touchAfter);
};
const stringify = (sess) => JSON.stringify(sess, (key, val) => key === 'cookie' || key === 'isNew' || key === 'id' ? undefined : val);
const SESS_PREV = Symbol('session#prev');
const SESS_TOUCHED = Symbol('session#touched');
const commitHead = (req, res, options) => {
if (res.headersSent || !req.session)
const stringify = (sess) => JSON.stringify(sess, (key, val) => (key === 'cookie' ? undefined : val));
const commitHead = (res, name, session, touched, encodeFn) => {
if (res.headersSent || !session)
return;
if (req.session.isNew || (options.rolling && req[SESS_TOUCHED])) {
res.setHeader('Set-Cookie', cookie_1.serialize(options.name, options.encode ? options.encode(req.session.id) : req.session.id, {
path: req.session.cookie.path,
httpOnly: req.session.cookie.httpOnly,
expires: req.session.cookie.expires,
domain: req.session.cookie.domain,
sameSite: req.session.cookie.sameSite,
secure: req.session.cookie.secure,
}));
if (session.isNew || touched) {
const cookieArr = [
cookie_1.serialize(name, encodeFn ? encodeFn(session.id) : session.id, {
path: session.cookie.path,
httpOnly: session.cookie.httpOnly,
expires: session.cookie.expires,
domain: session.cookie.domain,
sameSite: session.cookie.sameSite,
secure: session.cookie.secure,
}),
];
const prevCookies = res.getHeader('set-cookie');
if (prevCookies) {
if (Array.isArray(prevCookies))
cookieArr.push(...prevCookies);
else
cookieArr.push(prevCookies);
}
res.setHeader('set-cookie', cookieArr);
}
};
const save = async (req, options) => {
var _a, _b;
if (!req.session)
return;
const prepareSession = (session) => {
const obj = {};
for (const key in req.session) {
if (!(key === ('isNew' || key === 'id')))
obj[key] = req.session[key];
}
if (stringify(req.session) !== req[SESS_PREV]) {
await options.store.__set(req.session.id, obj);
}
else if (req[SESS_TOUCHED]) {
await ((_b = (_a = options.store).__touch) === null || _b === void 0 ? void 0 : _b.call(_a, req.session.id, obj));
}
for (const key in session)
!(key === ('isNew' || key === 'id')) && (obj[key] = session[key]);
return obj;
};
const save = async (store, session) => session && store.__set(session.id, prepareSession(session));
function setupStore(store) {

@@ -65,2 +58,3 @@ if ('__normalized' in store)

const done = (err, val) => err ? reject(err) : resolve(val);
// @ts-ignore: Certain differences between express-session type and ours
const result = this.get(sid, done);

@@ -74,2 +68,3 @@ if (result && typeof result.then === 'function')

const done = (err) => (err ? reject(err) : resolve());
// @ts-ignore: Certain differences between express-session type and ours
const result = this.set(sid, sess, done);

@@ -84,2 +79,3 @@ if (result && typeof result.then === 'function')

const done = (err) => (err ? reject(err) : resolve());
// @ts-ignore: Certain differences between express-session type and ours
const result = this.touch(sid, sess, done);

@@ -95,57 +91,48 @@ if (result && typeof result.then === 'function')

let memoryStore;
async function applySession(req, res, opts) {
var _a, _b, _c, _d, _e, _f, _g;
async function applySession(req, res, options = {}) {
var _a, _b, _c, _d, _e, _f;
if (req.session)
return;
const options = {
name: (opts === null || opts === void 0 ? void 0 : opts.name) || 'sid',
store: setupStore((opts === null || opts === void 0 ? void 0 : opts.store) || (memoryStore = memoryStore || new memory_1.default())),
genid: (opts === null || opts === void 0 ? void 0 : opts.genid) || nanoid_1.nanoid,
encode: opts === null || opts === void 0 ? void 0 : opts.encode,
decode: opts === null || opts === void 0 ? void 0 : opts.decode,
rolling: (opts === null || opts === void 0 ? void 0 : opts.rolling) || false,
touchAfter: (opts === null || opts === void 0 ? void 0 : opts.touchAfter) ? opts.touchAfter : 0,
autoCommit: typeof (opts === null || opts === void 0 ? void 0 : opts.autoCommit) !== 'undefined' ? opts.autoCommit : true,
};
let sessId = req.headers && req.headers.cookie
? cookie_1.parse(req.headers.cookie)[options.name]
: null;
if (sessId && options.decode)
sessId = options.decode(sessId);
const sess = sessId ? await options.store.__get(sessId) : null;
// This allows both promised-based and callback-based store to work
const store = setupStore(options.store || (memoryStore = memoryStore || new memory_1.default()));
// compat: if rolling is `true`, user might have wanted to touch every time
// thus defaulting options.touchAfter to 0 instead of -1
if (options.rolling && !('touchAfter' in options)) {
console.warn('The use of options.rolling is deprecated. Setting this to `true` without options.touchAfter causes options.touchAfter to be defaulted to `0` (always)');
options.touchAfter = 0;
}
const name = options.name || 'sid';
const commit = async () => {
commitHead(req, res, options);
await save(req, options);
commitHead(res, name, req.session, shouldTouch, options.encode);
await save(store, req.session);
};
const destroy = async () => {
await options.store.__destroy(req.session.id);
// This is a valid TS error, but considering its usage, it's fine.
// @ts-ignore
delete req.session;
await store.__destroy(req.session.id);
req.session = null;
};
if (sess) {
req[SESS_PREV] = stringify(sess);
const { cookie, ...data } = sess;
if (typeof cookie.expires === 'string')
cookie.expires = new Date(cookie.expires);
req.session = {
cookie,
commit,
destroy,
isNew: false,
id: sessId,
};
for (const key in data)
req.session[key] = data[key];
let sessId = req.headers && req.headers.cookie ? cookie_1.parse(req.headers.cookie)[name] : null;
if (sessId && options.decode)
sessId = options.decode(sessId);
// @ts-ignore: req.session as this point is not of type Session
// but SessionData, but the missing keys will be added later
req.session = sessId ? await store.__get(sessId) : null;
if (req.session) {
req.session.commit = commit;
req.session.destroy = destroy;
req.session.isNew = false;
req.session.id = sessId;
// Some store return cookie.expires as string, convert it to Date
if (typeof req.session.cookie.expires === 'string')
req.session.cookie.expires = new Date(req.session.cookie.expires);
}
else {
req[SESS_PREV] = '{}';
req.session = {
cookie: {
path: ((_a = opts === null || opts === void 0 ? void 0 : opts.cookie) === null || _a === void 0 ? void 0 : _a.path) || '/',
maxAge: ((_b = opts === null || opts === void 0 ? void 0 : opts.cookie) === null || _b === void 0 ? void 0 : _b.maxAge) || null,
httpOnly: ((_c = opts === null || opts === void 0 ? void 0 : opts.cookie) === null || _c === void 0 ? void 0 : _c.httpOnly) || true,
domain: ((_d = opts === null || opts === void 0 ? void 0 : opts.cookie) === null || _d === void 0 ? void 0 : _d.domain) || undefined,
sameSite: (_e = opts === null || opts === void 0 ? void 0 : opts.cookie) === null || _e === void 0 ? void 0 : _e.sameSite,
secure: ((_f = opts === null || opts === void 0 ? void 0 : opts.cookie) === null || _f === void 0 ? void 0 : _f.secure) || false,
path: ((_a = options.cookie) === null || _a === void 0 ? void 0 : _a.path) || '/',
httpOnly: ((_b = options.cookie) === null || _b === void 0 ? void 0 : _b.httpOnly) || true,
domain: ((_c = options.cookie) === null || _c === void 0 ? void 0 : _c.domain) || undefined,
sameSite: (_d = options.cookie) === null || _d === void 0 ? void 0 : _d.sameSite,
secure: ((_e = options.cookie) === null || _e === void 0 ? void 0 : _e.secure) || false,
...(((_f = options.cookie) === null || _f === void 0 ? void 0 : _f.maxAge) ? { maxAge: options.cookie.maxAge, expires: new Date() }
: { maxAge: null }),
},

@@ -155,16 +142,36 @@ commit,

isNew: true,
id: options.genid(),
id: (options.genid || nanoid_1.nanoid)(),
};
if ((_g = opts === null || opts === void 0 ? void 0 : opts.cookie) === null || _g === void 0 ? void 0 : _g.maxAge)
req.session.cookie.expires = new Date();
}
// Extend session expiry
if ((req[SESS_TOUCHED] = shouldTouch(req.session.cookie, options.touchAfter))) {
req.session.cookie.expires = new Date(Date.now() + req.session.cookie.maxAge * 1000);
// prevSessStr is used to compare the session later
// for touchability -- that is, we only touch the
// session if it has changed. This check is used
// in autoCommit mode only
const prevSessStr = options.autoCommit !== false
? req.session.isNew
? '{}'
: stringify(req.session)
: undefined;
let shouldTouch = false;
if (req.session.cookie.maxAge) {
if (
// Extend expires either if it is a new session
req.session.isNew ||
// or if touchAfter condition is satsified
(typeof options.touchAfter === 'number' &&
options.touchAfter !== -1 &&
(shouldTouch =
req.session.cookie.maxAge * 1000 -
(req.session.cookie.expires.getTime() - Date.now()) >=
options.touchAfter))) {
req.session.cookie.expires = new Date(Date.now() + req.session.cookie.maxAge * 1000);
}
}
// autocommit
if (options.autoCommit) {
// autocommit: We commit the header and save the session automatically
// by "proxying" res.writeHead and res.end methods. After committing, we
// call the original res.writeHead and res.end.
if (options.autoCommit !== false) {
const oldWritehead = res.writeHead;
res.writeHead = function resWriteHeadProxy(...args) {
commitHead(req, res, options);
commitHead(res, name, req.session, shouldTouch, options.encode);
return oldWritehead.apply(this, args);

@@ -174,3 +181,8 @@ };

res.end = async function resEndProxy(...args) {
await save(req, options);
if (stringify(req.session) !== prevSessStr) {
await save(store, req.session);
}
else if (req.session && shouldTouch && store.__touch) {
await store.__touch(req.session.id, prepareSession(req.session));
}
oldEnd.apply(this, args);

@@ -180,4 +192,4 @@ };

// Compat
req.sessionStore = options.store;
req.sessionStore = store;
}
exports.applySession = applySession;
export { default as withSession } from './withSession';
export { default as session } from './connect';
export { applySession } from './core';
export { promisifyStore, expressSession, Store } from './compat';
export { promisifyStore, expressSession } from './compat';
export { default as MemoryStore } from './store/memory';
export { SessionData, SessionCookieData, SessionStore } from './types';

@@ -6,3 +6,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionStore = exports.MemoryStore = exports.Store = exports.expressSession = exports.promisifyStore = exports.applySession = exports.session = exports.withSession = void 0;
exports.SessionStore = exports.MemoryStore = exports.expressSession = exports.promisifyStore = exports.applySession = exports.session = exports.withSession = void 0;
var withSession_1 = require("./withSession");

@@ -17,3 +17,2 @@ Object.defineProperty(exports, "withSession", { enumerable: true, get: function () { return __importDefault(withSession_1).default; } });

Object.defineProperty(exports, "expressSession", { enumerable: true, get: function () { return compat_1.expressSession; } });
Object.defineProperty(exports, "Store", { enumerable: true, get: function () { return compat_1.Store; } });
var memory_1 = require("./store/memory");

@@ -20,0 +19,0 @@ Object.defineProperty(exports, "MemoryStore", { enumerable: true, get: function () { return __importDefault(memory_1).default; } });

/// <reference types="node" />
import { EventEmitter } from "events";
import { SessionStore, SessionData } from "../types";
import { EventEmitter } from 'events';
import { SessionStore, SessionData } from '../types';
export default class MemoryStore extends EventEmitter implements SessionStore {

@@ -9,5 +9,5 @@ sessions: Record<string, string>;

set(sid: string, sess: SessionData): Promise<void>;
touch(sid: string, session: SessionData): Promise<void | undefined>;
touch(sid: string, session: SessionData): Promise<void>;
all(): Promise<string[]>;
destroy(sid: string): Promise<void>;
}

@@ -32,12 +32,3 @@ "use strict";

touch(sid, session) {
return this.get(sid).then((sess) => {
if (sess) {
const newSess = {
...sess,
cookie: session.cookie,
};
return this.set(sid, newSess);
}
return undefined;
});
return this.set(sid, session);
}

@@ -44,0 +35,0 @@ all() {

import { Store as ExpressStore } from 'express-session';
export declare type SessionData = {
[key: string]: any;
cookie: SessionCookieData;
};
export interface Session extends SessionData {
id: string;
cookie: SessionCookieData;
destroy: () => Promise<void>;
commit(): Promise<void>;
destroy(): Promise<void>;
isNew: boolean;
};
export interface SessionCookieData {
}
export declare type SessionCookieData = {
path: string;
maxAge: number | null;
secure: boolean;
httpOnly: boolean;
domain?: string | undefined;
expires?: Date;
sameSite?: boolean | 'lax' | 'strict' | 'none';
}
} & ({
maxAge: number;
expires: Date;
} | {
maxAge: null;
expires?: undefined;
});
export declare abstract class SessionStore {
abstract get: (sid: string) => Promise<SessionData | null>;
abstract set: (sid: string, sess: SessionData) => Promise<void>;
abstract destroy: (sid: string) => Promise<void>;
abstract touch?: (sid: string, sess: SessionData) => Promise<void>;
on?: (event: string | symbol, listener: (...args: any[]) => void) => this;
abstract get(sid: string): Promise<SessionData | null | undefined>;
abstract set(sid: string, sess: SessionData): Promise<void>;
abstract destroy(sid: string): Promise<void>;
abstract touch?(sid: string, sess: SessionData): Promise<void>;
on?(event: string | symbol, listener: (...args: any[]) => void): this;
}
export interface NormalizedSessionStore {
[key: string]: any;
__get: (sid: string) => Promise<SessionData | null>;
__set: (sid: string, sess: SessionData) => Promise<void>;
__destroy: (sid: string) => Promise<void>;
__touch?: (sid: string, sess: SessionData) => Promise<void>;
export declare type NormalizedSessionStore = {
__get(sid: string): Promise<SessionData | null | undefined>;
__set(sid: string, sess: SessionData): Promise<void>;
__destroy(sid: string): Promise<void>;
__touch(sid: string, sess: SessionData): Promise<void>;
__normalized: true;
}
export interface CookieOptions {
secure?: boolean;
httpOnly?: boolean;
path?: string;
domain?: string;
sameSite?: boolean | 'lax' | 'strict' | 'none';
maxAge?: number | null;
}
} & (SessionStore | ExpressStore);
export interface Options {

@@ -46,7 +44,17 @@ name?: string;

encode?: (rawSid: string) => string;
decode?: (encryptedSid: string) => string;
rolling?: boolean;
decode?: (encryptedSid: string) => string | null;
touchAfter?: number;
cookie?: CookieOptions;
cookie?: {
secure?: boolean;
httpOnly?: boolean;
path?: string;
domain?: string;
sameSite?: boolean | 'lax' | 'strict' | 'none';
maxAge?: number | null;
};
autoCommit?: boolean;
/**
* @deprecated
*/
rolling?: boolean;
}

@@ -26,3 +26,2 @@ "use strict";

WithSession.getInitialProps = async (pageCtx) => {
// @ts-ignore
if (typeof window === 'undefined') {

@@ -29,0 +28,0 @@ await core_1.applySession(pageCtx.req, pageCtx.res, options);

{
"name": "next-session",
"version": "3.3.2",
"version": "3.4.0",
"description": "Simple promise-based session middleware for Next.js",

@@ -36,23 +36,23 @@ "keywords": [

"@types/cookie": "^0.4.0",
"@types/express-session": "^1.17.0",
"@types/jest": "^26.0.10",
"@types/node": "^14.6.2",
"@types/react": "^16.9.48",
"@types/react-dom": "^16.9.8",
"@types/express-session": "^1.17.3",
"@types/jest": "^26.0.15",
"@types/node": "^14.14.9",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/supertest": "^2.0.10",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"@typescript-eslint/eslint-plugin": "^4.8.2",
"@typescript-eslint/parser": "^4.8.2",
"cookie-signature": "^1.1.0",
"eslint": "^7.7.0",
"jest": "^26.4.2",
"next": "9.5.3",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"supertest": "^4.0.2",
"ts-jest": "^26.3.0",
"typescript": "^4.0.2"
"eslint": "^7.14.0",
"jest": "^26.6.3",
"next": "10.0.3",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"supertest": "^6.0.1",
"ts-jest": "^26.4.4",
"typescript": "^4.1.2"
},
"dependencies": {
"cookie": "^0.4.1",
"nanoid": "^3.1.12"
"nanoid": "^3.1.18"
},

@@ -59,0 +59,0 @@ "engines": {

@@ -11,2 +11,4 @@ # next-session

> For a more battle-tested solution, you should use [express-session](https://github.com/expressjs/session) with [next-connect](https://github.com/hoangvvo/next-connect) instead.
## Installation

@@ -182,4 +184,3 @@

| decode | Transforms session ID back while getting from cookie. It should return the encoded/encrypted session ID | undefined |
| touchAfter | Only touch (extend session lifetime despite no modification) after an amount of time to decrease database load. Setting the value to `-1` will disable `touch()`. | `0` (Touch every time) |
| rolling | Extends the life time of the cookie in the browser if the session is touched. This respects touchAfter. | `false` |
| touchAfter | Only touch after an amount of time. Disabled by default or if set to `-1`. See [touchAfter](#touchAfter). | `-1` (Disabled) |
| autoCommit | Automatically commit session. Disable this if you want to manually `session.commit()` | `true` |

@@ -193,2 +194,8 @@ | cookie.secure | Specifies the boolean value for the **Secure** `Set-Cookie` attribute. | `false` |

### touchAfter
Touching refers to the extension of session lifetime, both in browser (by modifying `Expires` attribute in [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) header) and session store (using its respective method). This prevents the session from being expired after a while.
In `autoCommit` mode (which is enabled by default), for optimization, a session is only touched, not saved, if it is not modified. The value of `touchAfter` allows you to skip touching if the session is still recent, thus, decreasing database load.
### encode/decode

@@ -195,0 +202,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