Socket
Socket
Sign inDemoInstall

@hapi/statehood

Package Overview
Dependencies
12
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 6.1.2 to 7.0.0

464

lib/index.js

@@ -14,3 +14,5 @@ 'use strict';

const internals = {};
const internals = {
macPrefix: 'hapi.signed.cookie.1'
};

@@ -23,3 +25,3 @@

isHttpOnly: Joi.boolean(),
isSameSite: Joi.valid('Strict', 'Lax').allow(false),
isSameSite: Joi.valid('Strict', 'Lax', 'None', false),
path: Joi.string().allow(null),

@@ -35,2 +37,3 @@ domain: Joi.string().allow(null),

password: [Joi.string(), Joi.binary(), Joi.object()],
contextualize: Joi.function(),

@@ -58,28 +61,2 @@ // Used by hapi

exports.Definitions = internals.Definitions = function (options) {
this.settings = Hoek.applyToDefaults(internals.defaults, options || {});
Joi.assert(this.settings, internals.schema, 'Invalid state definition defaults');
this.cookies = {};
this.names = [];
};
internals.Definitions.prototype.add = function (name, options) {
Hoek.assert(name && typeof name === 'string', 'Invalid name');
Hoek.assert(!this.cookies[name], 'State already defined:', name);
const settings = Hoek.applyToDefaults(this.settings, options || {}, { nullOverride: true });
Joi.assert(settings, internals.schema, 'Invalid state definition: ' + name);
this.cookies[name] = settings;
this.names.push(name);
};
internals.empty = new internals.Definitions();
// Header format

@@ -108,125 +85,269 @@

internals.Definitions.prototype.parse = async function (cookies) {
exports.Definitions = class {
const state = {};
const names = [];
const verify = cookies.replace(internals.parseRx, ($0, $1, $2, $3) => {
constructor(options) {
const name = $1;
const value = $2 || $3 || '';
this.settings = Hoek.applyToDefaults(internals.defaults, options || {});
Joi.assert(this.settings, internals.schema, 'Invalid state definition defaults');
if (state[name]) {
if (!Array.isArray(state[name])) {
state[name] = [state[name]];
this.cookies = {};
this.names = [];
}
add(name, options) {
Hoek.assert(name && typeof name === 'string', 'Invalid name');
Hoek.assert(!this.cookies[name], 'State already defined:', name);
const settings = Hoek.applyToDefaults(this.settings, options || {}, { nullOverride: true });
Joi.assert(settings, internals.schema, 'Invalid state definition: ' + name);
this.cookies[name] = settings;
this.names.push(name);
}
async parse(cookies) {
const state = {};
const names = [];
const verify = cookies.replace(internals.parseRx, ($0, $1, $2, $3) => {
const name = $1;
const value = $2 || $3 || '';
if (state[name]) {
if (!Array.isArray(state[name])) {
state[name] = [state[name]];
}
state[name].push(value);
}
else {
state[name] = value;
names.push(name);
}
state[name].push(value);
}
else {
state[name] = value;
names.push(name);
}
return '';
});
return '';
});
// Validate cookie header syntax
// Validate cookie header syntax
const failed = []; // All errors
const failed = []; // All errors
if (verify !== '') {
if (!this.settings.ignoreErrors) {
throw Boom.badRequest('Invalid cookie header');
}
if (verify !== '') {
if (!this.settings.ignoreErrors) {
throw Boom.badRequest('Invalid cookie header');
failed.push({ settings: this.settings, reason: `Header contains unexpected syntax: ${verify}` });
}
failed.push({ settings: this.settings, reason: `Header contains unexpected syntax: ${verify}` });
}
// Collect errors
// Collect errors
const errored = []; // Unignored errors
const record = (reason, name, value, definition) => {
const errored = []; // Unignored errors
const record = (reason, name, value, definition) => {
const details = {
name,
value,
settings: definition,
reason: typeof reason === 'string' ? reason : reason.message
};
const details = {
name,
value,
settings: definition,
reason: typeof reason === 'string' ? reason : reason.message
failed.push(details);
if (!definition.ignoreErrors) {
errored.push(details);
}
};
failed.push(details);
if (!definition.ignoreErrors) {
errored.push(details);
}
};
// Parse cookies
// Parse cookies
const parsed = {};
for (let i = 0; i < names.length; ++i) {
const name = names[i];
const value = state[name];
const definition = this.cookies[name] || this.settings;
const parsed = {};
for (let i = 0; i < names.length; ++i) {
const name = names[i];
const value = state[name];
const definition = this.cookies[name] || this.settings;
// Validate cookie
// Validate cookie
if (definition.strictHeader) {
const reason = internals.validate(name, state);
if (reason) {
record(reason, name, value, definition);
continue;
}
}
if (definition.strictHeader) {
const reason = internals.validate(name, state);
if (reason) {
record(reason, name, value, definition);
// Check cookie format
if (definition.encoding === 'none') {
parsed[name] = value;
continue;
}
// Single value
if (!Array.isArray(value)) {
try {
const unsigned = await internals.unsign(name, value, definition);
const result = await internals.decode(unsigned, definition);
parsed[name] = result;
}
catch (err) {
Bounce.rethrow(err, 'system');
record(err, name, value, definition);
}
continue;
}
// Array
const arrayResult = [];
for (let j = 0; j < value.length; ++j) {
const arrayValue = value[j];
try {
const unsigned = await internals.unsign(name, arrayValue, definition);
const result = await internals.decode(unsigned, definition);
arrayResult.push(result);
}
catch (err) {
Bounce.rethrow(err, 'system');
record(err, name, value, definition);
}
}
parsed[name] = arrayResult;
}
// Check cookie format
if (errored.length) {
const error = Boom.badRequest('Invalid cookie value', errored);
error.states = parsed;
error.failed = failed;
throw error;
}
if (definition.encoding === 'none') {
parsed[name] = value;
continue;
return { states: parsed, failed };
}
async format(cookies, context) {
if (!cookies ||
Array.isArray(cookies) && !cookies.length) {
return [];
}
// Single value
if (!Array.isArray(cookies)) {
cookies = [cookies];
}
if (!Array.isArray(value)) {
try {
const unsigned = await internals.unsign(name, value, definition);
const result = await internals.decode(unsigned, definition);
parsed[name] = result;
const header = [];
for (let i = 0; i < cookies.length; ++i) {
const cookie = cookies[i];
// Apply definition to local configuration
const base = this.cookies[cookie.name] || this.settings;
let definition = cookie.options ? Hoek.applyToDefaults(base, cookie.options, { nullOverride: true }) : base;
// Contextualize definition
if (definition.contextualize) {
if (definition === base) {
definition = Hoek.clone(definition);
}
await definition.contextualize(definition, context);
}
catch (err) {
Bounce.rethrow(err, 'system');
record(err, name, value, definition);
// Validate name
const nameRx = definition.strictHeader ? internals.validateRx.nameRx.strict : internals.validateRx.nameRx.loose;
if (!nameRx.test(cookie.name)) {
throw Boom.badImplementation('Invalid cookie name: ' + cookie.name);
}
continue;
}
// Prepare value (encode, sign)
// Array
const value = await exports.prepareValue(cookie.name, cookie.value, definition);
const arrayResult = [];
for (let j = 0; j < value.length; ++j) {
const arrayValue = value[j];
// Validate prepared value
try {
const unsigned = await internals.unsign(name, arrayValue, definition);
const result = await internals.decode(unsigned, definition);
arrayResult.push(result);
const valueRx = definition.strictHeader ? internals.validateRx.valueRx.strict : internals.validateRx.valueRx.loose;
if (value &&
(typeof value !== 'string' || !value.match(valueRx))) {
throw Boom.badImplementation('Invalid cookie value: ' + cookie.value);
}
catch (err) {
Bounce.rethrow(err, 'system');
record(err, name, value, definition);
// Construct cookie
let segment = cookie.name + '=' + (value || '');
if (definition.ttl !== null &&
definition.ttl !== undefined) { // Can be zero
const expires = new Date(definition.ttl ? Date.now() + definition.ttl : 0);
segment = segment + '; Max-Age=' + Math.floor(definition.ttl / 1000) + '; Expires=' + expires.toUTCString();
}
if (definition.isSecure) {
segment = segment + '; Secure';
}
if (definition.isHttpOnly) {
segment = segment + '; HttpOnly';
}
if (definition.isSameSite) {
segment = `${segment}; SameSite=${definition.isSameSite}`;
}
if (definition.domain) {
const domain = definition.domain.toLowerCase();
if (!domain.match(internals.validateRx.domainLabelLenRx)) {
throw Boom.badImplementation('Cookie domain too long: ' + definition.domain);
}
if (!domain.match(internals.validateRx.domainRx)) {
throw Boom.badImplementation('Invalid cookie domain: ' + definition.domain);
}
segment = segment + '; Domain=' + domain;
}
if (definition.path) {
if (!definition.path.match(internals.validateRx.pathRx)) {
throw Boom.badImplementation('Invalid cookie path: ' + definition.path);
}
segment = segment + '; Path=' + definition.path;
}
header.push(segment);
}
parsed[name] = arrayResult;
return header;
}
if (errored.length) {
const error = Boom.badRequest('Invalid cookie value', errored);
error.states = parsed;
error.failed = failed;
throw error;
passThrough(header, fallback) {
if (!this.names.length) {
return header;
}
const exclude = [];
for (let i = 0; i < this.names.length; ++i) {
const name = this.names[i];
const definition = this.cookies[name];
const passCookie = definition.passThrough !== undefined ? definition.passThrough : fallback;
if (!passCookie) {
exclude.push(name);
}
}
return exports.exclude(header, exclude);
}
return { states: parsed, failed };
};

@@ -252,5 +373,2 @@

internals.macPrefix = 'hapi.signed.cookie.1';
internals.unsign = async function (name, value, definition) {

@@ -310,3 +428,3 @@

if (definition.encoding === 'base64json') {
const decoded = (Buffer.from(value, 'base64')).toString('binary');
const decoded = Buffer.from(value, 'base64').toString('binary');
try {

@@ -321,3 +439,3 @@ return Bourne.parse(decoded);

if (definition.encoding === 'base64') {
return (Buffer.from(value, 'base64')).toString('binary');
return Buffer.from(value, 'base64').toString('binary');
}

@@ -331,94 +449,2 @@

internals.Definitions.prototype.format = async function (cookies) {
if (!cookies ||
(Array.isArray(cookies) && !cookies.length)) {
return [];
}
if (!Array.isArray(cookies)) {
cookies = [cookies];
}
const header = [];
for (let i = 0; i < cookies.length; ++i) {
const cookie = cookies[i];
// Apply definition to local configuration
const base = this.cookies[cookie.name] || this.settings;
const definition = cookie.options ? Hoek.applyToDefaults(base, cookie.options, { nullOverride: true }) : base;
// Validate name
const nameRx = (definition.strictHeader ? internals.validateRx.nameRx.strict : internals.validateRx.nameRx.loose);
if (!nameRx.test(cookie.name)) {
throw Boom.badImplementation('Invalid cookie name: ' + cookie.name);
}
// Prepare value (encode, sign)
const value = await exports.prepareValue(cookie.name, cookie.value, definition);
// Validate prepared value
const valueRx = (definition.strictHeader ? internals.validateRx.valueRx.strict : internals.validateRx.valueRx.loose);
if (value &&
(typeof value !== 'string' || !value.match(valueRx))) {
throw Boom.badImplementation('Invalid cookie value: ' + cookie.value);
}
// Construct cookie
let segment = cookie.name + '=' + (value || '');
if (definition.ttl !== null &&
definition.ttl !== undefined) { // Can be zero
const expires = new Date(definition.ttl ? Date.now() + definition.ttl : 0);
segment = segment + '; Max-Age=' + Math.floor(definition.ttl / 1000) + '; Expires=' + expires.toUTCString();
}
if (definition.isSecure) {
segment = segment + '; Secure';
}
if (definition.isHttpOnly) {
segment = segment + '; HttpOnly';
}
if (definition.isSameSite) {
segment = segment + `; SameSite=${definition.isSameSite}`;
}
if (definition.domain) {
const domain = definition.domain.toLowerCase();
if (!domain.match(internals.validateRx.domainLabelLenRx)) {
throw Boom.badImplementation('Cookie domain too long: ' + definition.domain);
}
if (!domain.match(internals.validateRx.domainRx)) {
throw Boom.badImplementation('Invalid cookie domain: ' + definition.domain);
}
segment = segment + '; Domain=' + domain;
}
if (definition.path) {
if (!definition.path.match(internals.validateRx.pathRx)) {
throw Boom.badImplementation('Invalid cookie path: ' + definition.path);
}
segment = segment + '; Path=' + definition.path;
}
header.push(segment);
}
return header;
};
exports.prepareValue = async function (name, value, options) {

@@ -454,3 +480,3 @@

if (options.encoding === 'base64') {
return (Buffer.from(value, 'binary')).toString('base64');
return Buffer.from(value, 'binary').toString('base64');
}

@@ -460,3 +486,3 @@

const stringified = JSON.stringify(value);
return (Buffer.from(stringified, 'binary')).toString('base64');
return Buffer.from(stringified, 'binary').toString('base64');
}

@@ -484,22 +510,2 @@

internals.Definitions.prototype.passThrough = function (header, fallback) {
if (!this.names.length) {
return header;
}
const exclude = [];
for (let i = 0; i < this.names.length; ++i) {
const name = this.names[i];
const definition = this.cookies[name];
const passCookie = definition.passThrough !== undefined ? definition.passThrough : fallback;
if (!passCookie) {
exclude.push(name);
}
}
return exports.exclude(header, exclude);
};
exports.exclude = function (cookies, excludes) {

@@ -506,0 +512,0 @@

{
"name": "@hapi/statehood",
"description": "HTTP State Management Utilities",
"version": "6.1.2",
"version": "7.0.0",
"repository": "git://github.com/hapijs/statehood",

@@ -6,0 +6,0 @@ "main": "lib/index.js",

<a href="http://hapijs.com"><img src="https://raw.githubusercontent.com/hapijs/assets/master/images/family.png" width="180px" align="right" /></a>
# statehood
# @hapi/statehood

@@ -5,0 +5,0 @@ HTTP State Management Utilities.

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc