hono-sess
Version:
A Simple Session Middleware for Hono
294 lines • 11.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryStore = exports.Session = exports.Cookie = exports.Store = void 0;
const cookie_1 = require("hono/cookie");
const crypto_1 = require("crypto");
const cookie_2 = require("./cookie");
Object.defineProperty(exports, "Cookie", { enumerable: true, get: function () { return cookie_2.Cookie; } });
const memory_1 = require("./memory");
Object.defineProperty(exports, "MemoryStore", { enumerable: true, get: function () { return memory_1.MemoryStore; } });
const session_1 = require("./session");
Object.defineProperty(exports, "Session", { enumerable: true, get: function () { return session_1.Session; } });
const store_1 = require("./store");
Object.defineProperty(exports, "Store", { enumerable: true, get: function () { return store_1.Store; } });
const utils_1 = require("./utils");
let env = process.env.NODE_ENV;
__exportStar(require("./types"), exports);
const session = ({ cookie = {}, genid = crypto_1.default.randomUUID, name = 'connect.sid', store = new memory_1.MemoryStore(), proxy = false, resave = false, rolling = false, saveUninitialized = true, secret = 'dev-secret', unset = 'keep', }) => {
if (typeof genid !== 'function')
throw new TypeError('genid option must be a function');
if (resave === undefined) {
(0, utils_1.deprecate)('undefined resave option; provide resave option');
resave = true;
}
if (saveUninitialized === undefined) {
(0, utils_1.deprecate)('undefined saveUninitialized option; provide saveUninitialized option');
saveUninitialized = true;
}
if (unset !== 'destroy' && unset !== 'keep')
throw new TypeError('unset option must be "destroy" or "keep"');
const unsetDestroy = unset === 'destroy';
if (Array.isArray(secret) && secret.length === 0) {
throw new TypeError('secret option array must contain one or more strings');
}
if (secret && !Array.isArray(secret))
secret = [secret];
if (!secret)
(0, utils_1.deprecate)('req.secret; provide secret option');
if (env === 'production' && store instanceof memory_1.MemoryStore)
console.warn(utils_1.warning);
store.generate = function (req) {
req.sessionID = genid();
req.session = new session_1.Session(req, null);
req.session.cookie = new cookie_2.Cookie(cookie);
req.raw.sessionID = req.sessionID;
if (cookie.secure === 'auto') {
req.session.cookie.secure = (0, utils_1.issecure)(req, proxy);
}
};
const storeImplementsTouch = typeof store.touch === 'function';
let storeReady = true;
store.on('disconnect', function ondisconnect() {
storeReady = false;
});
store.on('connect', function onconnect() {
storeReady = true;
});
return async function session(context, next) {
const c = context;
if (c.req.session) {
return await next();
}
if (!storeReady) {
(0, utils_1.debug)('store is disconnected');
return await next();
}
if (c.req.path.indexOf(cookie.path || '/') !== 0) {
(0, utils_1.debug)('pathname mismatch');
return await next();
}
if (!secret) {
console.error('secret option required for sessions');
return await next();
}
let secrets = secret;
let cookieId = null;
let originalHash = null;
let originalId = null;
let savedHash = null;
let touched = false;
c.req.sessionStore = store;
const signedCookie = (((await (0, cookie_1.getSignedCookie)(c, secrets.join(' '), name)) || undefined));
cookieId = signedCookie;
if (cookieId) {
c.req.sessionID = cookieId;
}
const getNext = () => next().then(async (nextResult) => {
if (shouldDestroy(c.req)) {
(0, utils_1.debug)('destroying');
await new Promise((resolve, reject) => {
store.destroy(c.req.sessionID, (err) => {
if (err)
reject(err);
(0, utils_1.debug)('destroyed');
resolve();
});
});
}
if (!c.req.session) {
(0, utils_1.debug)('no session at post next');
return nextResult;
}
if (!touched) {
c.req.session.touch();
touched = true;
}
if (shouldSave(c.req)) {
await new Promise((resolve, reject) => {
c.req.session.save((err) => {
if (err) {
reject(err);
}
resolve();
});
});
}
else if (storeImplementsTouch && shouldTouch(c.req)) {
(0, utils_1.debug)('touching');
await new Promise((resolve, reject) => {
store.touch?.(c.req.sessionID, c.req.session, (err) => {
if (err) {
reject(err);
}
(0, utils_1.debug)('touched');
resolve();
});
});
}
await handleCookies();
return nextResult;
});
const handleCookies = async () => {
if (!c.req.session) {
(0, utils_1.debug)('no session');
return;
}
if (!shouldSetCookie(c.req)) {
(0, utils_1.debug)('should not set cookie');
return;
}
if (c.req.session.cookie.secure && !(0, utils_1.issecure)(c.req, proxy)) {
(0, utils_1.debug)('not secured');
return;
}
if (!touched) {
c.req.session.touch();
touched = true;
(0, utils_1.debug)('touched');
}
try {
(0, utils_1.debug)('setting cookie');
const cookieData = c.req.session.cookie.data;
await (0, cookie_1.setSignedCookie)(c, name, c.req.sessionID, secrets.join(' '), (0, utils_1.expressCookieOptionsToHonoCookieOptions)(cookieData, c.req, proxy));
return;
}
catch (err) {
console.error(err);
return;
}
};
function generate() {
(0, utils_1.debug)('generating');
store.generate(c.req);
originalId = c.req.sessionID;
originalHash = (0, utils_1.hash)(c.req.session);
wrapmethods(c.req.session);
}
function inflate(req, session) {
(0, utils_1.debug)('inflating');
store.createSession(req, session);
originalId = req.sessionID;
originalHash = (0, utils_1.hash)(session);
if (!resave) {
savedHash = originalHash;
}
wrapmethods(session);
}
function rewrapmethods(session, callback) {
(0, utils_1.debug)('rewrapmethods');
return function () {
if (c.req.session !== session) {
wrapmethods(c.req.session);
}
callback.apply(this, arguments);
};
}
function wrapmethods(session) {
const _reload = session.reload;
const _save = session.save;
function reload(callback) {
(0, utils_1.debug)('reloading %s', session.id);
_reload.call(session, rewrapmethods(session, callback));
}
function save() {
(0, utils_1.debug)('saving %s', session.id);
savedHash = (0, utils_1.hash)(session);
_save.apply(session, arguments);
}
Object.defineProperty(session, 'reload', {
configurable: true,
enumerable: false,
value: reload,
writable: true,
});
Object.defineProperty(session, 'save', {
configurable: true,
enumerable: false,
value: save,
writable: true,
});
}
function isModified(session) {
return originalId !== session.id || originalHash !== (0, utils_1.hash)(session);
}
function isSaved(session) {
return originalId === session.id && savedHash === (0, utils_1.hash)(session);
}
function shouldDestroy(req) {
return req.sessionID && unsetDestroy && req.session == null;
}
function shouldSave(req) {
if (typeof req.sessionID !== 'string') {
(0, utils_1.debug)('session ignored because of bogus req.sessionID %o', req.sessionID);
return false;
}
return !saveUninitialized && !savedHash && cookieId !== req.sessionID
? isModified(req.session)
: !isSaved(req.session);
}
function shouldTouch(req) {
if (typeof req.sessionID !== 'string') {
(0, utils_1.debug)('session ignored because of bogus req.sessionID %o', req.sessionID);
return false;
}
return cookieId === req.sessionID && !shouldSave(req);
}
function shouldSetCookie(req) {
if (typeof req.sessionID !== 'string') {
(0, utils_1.debug)('session ignored because of bogus req.sessionID %o');
return false;
}
return cookieId !== req.sessionID
? saveUninitialized || isModified(req.session)
: rolling ||
(req.session.cookie.expires != null && isModified(req.session));
}
if (!c.req.sessionID) {
(0, utils_1.debug)('no SID sent, generating session');
generate();
return getNext();
}
(0, utils_1.debug)('fetching %s', c.req.sessionID);
return new Promise((resolve) => {
store.get(c.req.sessionID, (err, session) => {
if (err && err.code !== 'ENOENT') {
(0, utils_1.debug)('error %j', err);
resolve(getNext());
return;
}
try {
if (err || !session) {
(0, utils_1.debug)('no session found');
generate();
}
else {
(0, utils_1.debug)('session found');
inflate(c.req, session);
}
}
catch (e) {
console.error(e);
resolve(getNext());
return;
}
resolve(getNext());
});
});
};
};
exports.default = session;
//# sourceMappingURL=index.js.map