New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@fastify/secure-session

Package Overview
Dependencies
Maintainers
18
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fastify/secure-session - npm Package Compare versions

Comparing version

to
6.1.0

test/fieldName.js

190

index.js

@@ -11,3 +11,3 @@ 'use strict'

get (target, prop) {
// Calling functions eg request.session.get('key') or request.session.set('key', 'value')
// Calling functions eg request[sessionName].get('key') or request[sessionName].set('key', 'value')
if (typeof target[prop] === 'function') {

@@ -21,3 +21,3 @@ return new Proxy(target[prop], {

// accessing own properties, eg request.session.changed
// accessing own properties, eg request[sessionName].changed
if (Object.prototype.hasOwnProperty.call(target, prop)) {

@@ -31,3 +31,3 @@ return target[prop]

set (target, prop, value) {
// modifying own properties, eg request.session.changed
// modifying own properties, eg request[sessionName].changed
if (Object.prototype.hasOwnProperty.call(target, prop)) {

@@ -45,67 +45,87 @@ target[prop] = value

function fastifySecureSession (fastify, options, next) {
let key
if (options.secret) {
if (Buffer.byteLength(options.secret) < 32) {
return next(new Error('secret must be at least 32 bytes'))
}
if (!(options instanceof Array)) {
options = [options]
}
key = Buffer.allocUnsafe(sodium.crypto_secretbox_KEYBYTES)
let defaultSessionName
const sessionNames = new Map()
// static salt to be used for key derivation, not great for security,
// but better than nothing
let salt = Buffer.from('mq9hDxBVDbspDR6nLfFT1g==', 'base64')
for (const sessionOptions of options) {
const sessionName = sessionOptions.sessionName || 'session'
const cookieName = sessionOptions.cookieName || sessionName
const cookieOptions = sessionOptions.cookieOptions || sessionOptions.cookie || {}
if (options.salt) {
salt = (Buffer.isBuffer(options.salt)) ? options.salt : Buffer.from(options.salt, 'ascii')
}
let key
if (sessionOptions.secret) {
if (Buffer.byteLength(sessionOptions.secret) < 32) {
return next(new Error('secret must be at least 32 bytes'))
}
if (Buffer.byteLength(salt) !== sodium.crypto_pwhash_SALTBYTES) {
return next(new Error('salt must be length ' + sodium.crypto_pwhash_SALTBYTES))
key = Buffer.allocUnsafe(sodium.crypto_secretbox_KEYBYTES)
// static salt to be used for key derivation, not great for security,
// but better than nothing
let salt = Buffer.from('mq9hDxBVDbspDR6nLfFT1g==', 'base64')
if (sessionOptions.salt) {
salt = (Buffer.isBuffer(sessionOptions.salt)) ? sessionOptions.salt : Buffer.from(sessionOptions.salt, 'ascii')
}
if (Buffer.byteLength(salt) !== sodium.crypto_pwhash_SALTBYTES) {
return next(new Error('salt must be length ' + sodium.crypto_pwhash_SALTBYTES))
}
sodium.crypto_pwhash(key,
Buffer.from(sessionOptions.secret),
salt,
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE,
sodium.crypto_pwhash_ALG_DEFAULT)
}
sodium.crypto_pwhash(key,
Buffer.from(options.secret),
salt,
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE,
sodium.crypto_pwhash_ALG_DEFAULT)
}
if (sessionOptions.key) {
key = sessionOptions.key
if (typeof key === 'string') {
key = Buffer.from(key, 'base64')
} else if (key instanceof Array) {
try {
key = key.map(ensureBufferKey)
} catch (error) {
return next(error)
}
} else if (!(key instanceof Buffer)) {
return next(new Error('key must be a string or a Buffer'))
}
if (options.key) {
key = options.key
if (typeof key === 'string') {
key = Buffer.from(key, 'base64')
} else if (key instanceof Array) {
try {
key = key.map(ensureBufferKey)
} catch (error) {
return next(error)
if (!(key instanceof Array) && isBufferKeyLengthInvalid(key)) {
return next(new Error(`key must be ${sodium.crypto_secretbox_KEYBYTES} bytes`))
} else if (key instanceof Array && key.every(isBufferKeyLengthInvalid)) {
return next(new Error(`key lengths must be ${sodium.crypto_secretbox_KEYBYTES} bytes`))
}
} else if (!(key instanceof Buffer)) {
return next(new Error('key must be a string or a Buffer'))
}
if (!(key instanceof Array) && isBufferKeyLengthInvalid(key)) {
return next(new Error(`key must be ${sodium.crypto_secretbox_KEYBYTES} bytes`))
} else if (key instanceof Array && key.every(isBufferKeyLengthInvalid)) {
return next(new Error(`key lengths must be ${sodium.crypto_secretbox_KEYBYTES} bytes`))
if (!key) {
return next(new Error('key or secret must specified'))
}
}
if (!key) {
return next(new Error('key or secret must specified'))
}
if (!(key instanceof Array)) {
key = [key]
}
if (!(key instanceof Array)) {
key = [key]
}
// just to add something to the shape
// TODO verify if it helps the perf
fastify.decorateRequest(sessionName, null)
const cookieName = options.cookieName || 'session'
const cookieOptions = options.cookieOptions || options.cookie || {}
sessionNames.set(sessionName, {
cookieName,
cookieOptions,
key
})
// just to add something to the shape
// TODO verify if it helps the perf
fastify.decorateRequest('session', null)
if (!defaultSessionName) {
defaultSessionName = sessionName
}
}
fastify.decorate('decodeSecureSession', (cookie, log = fastify.log) => {
fastify.decorate('decodeSecureSession', (cookie, log = fastify.log, sessionName = defaultSessionName) => {
if (cookie === undefined) {

@@ -117,2 +137,8 @@ // there is no cookie

if (!sessionNames.has(sessionName)) {
throw new Error('Unknown session key.')
}
const { key } = sessionNames.get(sessionName)
// do not use destructuring or it will deopt

@@ -168,3 +194,9 @@ const split = cookie.split(';')

fastify.decorate('encodeSecureSession', (session) => {
fastify.decorate('encodeSecureSession', (session, sessionName = defaultSessionName) => {
if (!sessionNames.has(sessionName)) {
throw new Error('Unknown session key.')
}
const { key } = sessionNames.get(sessionName)
const nonce = genNonce()

@@ -194,6 +226,8 @@ const msg = Buffer.from(JSON.stringify(session[kObj]))

fastify.addHook('onRequest', (request, reply, next) => {
const cookie = request.cookies[cookieName]
const result = fastify.decodeSecureSession(cookie, request.log)
for (const [sessionName, { cookieName }] of sessionNames.entries()) {
const cookie = request.cookies[cookieName]
const result = fastify.decodeSecureSession(cookie, request.log, sessionName)
request.session = new Proxy((result || new Session({})), sessionProxyHandler)
request[sessionName] = new Proxy((result || new Session({})), sessionProxyHandler)
}

@@ -204,29 +238,29 @@ next()

fastify.addHook('onSend', (request, reply, payload, next) => {
const session = request.session
for (const [sessionName, { cookieName, cookieOptions }] of sessionNames.entries()) {
const session = request[sessionName]
if (!session || !session.changed) {
if (!session || !session.changed) {
// nothing to do
request.log.trace('@fastify/secure-session: there is no session or the session didn\'t change, leaving it as is')
next()
return
} else if (session.deleted) {
request.log.debug('@fastify/secure-session: deleting session')
const tmpCookieOptions = Object.assign(
{},
cookieOptions,
session[kCookieOptions],
{ expires: new Date(0), maxAge: 0 }
request.log.trace('@fastify/secure-session: there is no session or the session didn\'t change, leaving it as is')
continue
} else if (session.deleted) {
request.log.debug('@fastify/secure-session: deleting session')
const tmpCookieOptions = Object.assign(
{},
cookieOptions,
session[kCookieOptions],
{ expires: new Date(0), maxAge: 0 }
)
reply.setCookie(cookieName, '', tmpCookieOptions)
continue
}
request.log.trace('@fastify/secure-session: setting session')
reply.setCookie(
cookieName,
fastify.encodeSecureSession(session, sessionName),
Object.assign({}, cookieOptions, session[kCookieOptions])
)
reply.setCookie(cookieName, '', tmpCookieOptions)
next()
return
}
request.log.trace('@fastify/secure-session: setting session')
reply.setCookie(
cookieName,
fastify.encodeSecureSession(session),
Object.assign({}, cookieOptions, session[kCookieOptions])
)
next()

@@ -233,0 +267,0 @@ })

{
"name": "@fastify/secure-session",
"version": "6.0.0",
"version": "6.1.0",
"description": "Create a secure stateless cookie session for Fastify",

@@ -39,3 +39,3 @@ "main": "index.js",

"tap": "^16.1.0",
"tsd": "^0.25.0"
"tsd": "^0.28.0"
},

@@ -42,0 +42,0 @@ "dependencies": {

@@ -34,2 +34,8 @@ # @fastify/secure-session

If you don't want to use `npx`, you can still generate the `secret-key` installing the `@fastify/secure-session` library with your choice package manager, and then:
```sh
./node_modules/@fastify/secure-session/genkey.js > secret_key
```
Then, register the plugin as follows:

@@ -45,3 +51,5 @@

fastify.register(require('@fastify/secure-session'), {
// the name of the session cookie, defaults to 'session'
// the name of the attribute decorated on the request-object, defaults to 'session'
sessionName: 'session',
// the name of the session cookie, defaults to value of sessionName
cookieName: 'my-session-cookie',

@@ -58,2 +66,6 @@ // adapt this to point to the directory where secret-key is located

request.session.set('data', request.body)
// or when using a custom sessionName:
request.customSessionName.set('data', request.body)
reply.send('hello world')

@@ -84,2 +96,31 @@ })

### Multiple sessions
If you want to use multiple sessions, you have to supply an array of options when registering the plugin. It supports the same options as a single session but in this case, the `sessionName` name is mandatory.
```js
fastify.register(require('@fastify/secure-session'), [{
sessionName: 'mySession',
cookieName: 'my-session-cookie',
key: fs.readFileSync(path.join(__dirname, 'secret-key')),
cookie: {
path: '/'
}
}, {
sessionName: 'myOtherSession',
key: fs.readFileSync(path.join(__dirname, 'another-secret-key')),
cookie: {
path: '/path',
maxAge: 100
}
}])
fastify.post('/', (request, reply) => {
request.mySession.set('data', request.body)
request.myOtherSession.set('data', request.body)
reply.send('hello world')
})
```
### Using keys as strings

@@ -258,3 +299,3 @@

// .options takes any parameter that you can pass to setCookie
request.session.options({ maxAge: 1000 * 60 * 60 })
request.session.options({ maxAge: 60 * 60 }); // 3600 seconds => maxAge is always passed in seconds
reply.send('hello world')

@@ -279,2 +320,10 @@ })

When using multiple sessions, you will have to provide the sessionName when encoding and decoding the session.
```js
fastify.encodeSecureSession(request.session, 'mySecondSession')
fastify.decodeSecureSession(request.cookies['session'], undefined, 'mySecondSession')
```
## Add TypeScript types

@@ -297,2 +346,21 @@

When using a custom sessionName or using multiple sessions the types should be configured as follows:
```ts
interface FooSessionData {
foo: string;
}
declare module "fastify" {
interface FastifyRequest {
foo: Session<FooSessionData>;
}
}
fastify.get('/', (request, reply) => {
request.foo.get('foo'); // typed `string | undefined`
reply.send('hello world')
})
```
## TODO

@@ -299,0 +367,0 @@

@@ -9,3 +9,3 @@ 'use strict'

tap.test('it exposes encode and decode decorators for other libraries to use', async t => {
t.plan(8)
t.plan(10)

@@ -29,2 +29,5 @@ const fastify = require('fastify')({

t.throws(() => fastify.encodeSecureSession(fastify.createSecureSession({ foo: 'bar' }), 'key-does-not-exist'), {}, 'Unknown session key.')
t.throws(() => fastify.decodeSecureSession('bogus', undefined, 'key-does-not-exist'), {}, 'Unknown session key.')
const cookie = fastify.encodeSecureSession(fastify.createSecureSession({ foo: 'bar' }))

@@ -31,0 +34,0 @@ const decoded = fastify.decodeSecureSession(cookie)

@@ -8,4 +8,4 @@ /// <reference types="node" />

createSecureSession(data?: Record<string, any>): fastifySecureSession.Session
decodeSecureSession(cookie: string, log?: FastifyBaseLogger): fastifySecureSession.Session | null
encodeSecureSession(session: fastifySecureSession.Session): string
decodeSecureSession(cookie: string, log?: FastifyBaseLogger, sessionName?: string): fastifySecureSession.Session | null
encodeSecureSession(session: fastifySecureSession.Session, sessionName?: string): string
}

@@ -18,11 +18,12 @@

type FastifySecureSession = FastifyPluginCallback<fastifySecureSession.SecureSessionPluginOptions>;
type FastifySecureSession = FastifyPluginCallback<fastifySecureSession.SecureSessionPluginOptions | (fastifySecureSession.SecureSessionPluginOptions & Required<Pick<fastifySecureSession.SecureSessionPluginOptions, 'sessionName'>>)[]>;
declare namespace fastifySecureSession {
export type Session = Partial<SessionData> & {
export type Session<T = SessionData> = Partial<T> & {
changed: boolean;
deleted: boolean;
get<Key extends keyof SessionData>(key: Key): SessionData[Key] | undefined;
set<Key extends keyof SessionData>(key: Key, value: SessionData[Key] | undefined): void;
data(): SessionData | undefined;
get<Key extends keyof T>(key: Key): T[Key] | undefined;
get(key: string): any | undefined;
set<Key extends keyof T>(key: Key, value: T[Key] | undefined): void;
data(): T | undefined;
delete(): void;

@@ -39,2 +40,3 @@ options(opts: CookieSerializeOptions): void;

cookieName?: string
sessionName?: string
} & ({ key: string | Buffer | (string | Buffer)[] } | {

@@ -41,0 +43,0 @@ secret: string | Buffer,

@@ -15,2 +15,4 @@ import SecureSessionPlugin, { Session, SessionData } from "..";

app.register(SecureSessionPlugin, { secret: "foo", salt: "bar" });
app.register(SecureSessionPlugin, { sessionName: "foo", key: "bar" });
app.register(SecureSessionPlugin, [{ sessionName: "foo", key: "bar" }, { sessionName: "bar", key: "bar" }]);

@@ -23,2 +25,12 @@ declare module ".." {

interface FooSessionData {
foo: string;
}
declare module "fastify" {
interface FastifyRequest {
foo: Session<FooSessionData>;
}
}
app.get("/not-websockets", async (request, reply) => {

@@ -35,3 +47,9 @@ expectType<FastifyRequest>(request);

request.session.delete();
request.session.options({ maxAge: 42 });
request.session.options({ maxAge: 42 })
request.foo.set("foo", "bar");
expectType<string | undefined>(request.foo.get("foo"));
expectType<any>(request.foo.get("baz"));
request.foo.delete();
request.foo.options({ maxAge: 42 });
});

@@ -38,0 +56,0 @@