UNPKG

@nodefony/http-bundle

Version:

Nodefony Framework Bundle HTTP

534 lines (492 loc) 15.4 kB
/* * * CLASS SESSION */ nodefony.register("Session", function () { const checkSecureReferer = function (context) { let host = null; switch (this.context.type) { case "HTTP": case "HTTPS": case "HTTP2": host = context.getHost(); break; case "WEBSOCKET": case "WEBSOCKET SECURE": host = this.context.request.httpRequest.headers.host; break; } let meta = this.getMetaBag("host"); if (host === meta) { return host; } else { this.manager.log("SESSION START WARNING REFERRER NOT SAME, HOST : " + host + " ,META STORAGE :" + meta, "WARNING"); throw { meta: meta, host: host }; } }; const setMetasSession = function () { let time = new Date(); let ua= null; this.setMetaBag("lifetime", this.settings.cookie.maxAge); this.setMetaBag("context", this.contextSession || null); this.setMetaBag("request", this.context.type); this.setMetaBag("created", time); try { this.setMetaBag("remoteAddress", this.context.getRemoteAddress()); this.setMetaBag("host", this.context.getHost()); ua = this.context.getUserAgent(); } catch (e) { } if (ua) { this.setMetaBag("user_agent", ua); } else { this.setMetaBag("user_agent", "Not Defined"); } }; const Session = class Session extends nodefony.Container { constructor(name, settings, storage, manager) { super(); if (!storage) { this.status = "disabled"; } else { this.status = "none"; // active | disabled } this.manager = manager; this.strategy = this.manager.sessionStrategy; this.strategyNone = false; this.log = manager.log.bind(manager); this.setName(name); this.id = null; this.settings = settings; this.storage = storage; this.context = null; this.contextSession = "default"; this.lifetime = this.settings.cookie.maxAge; this.saved = false; this.flashBag = {}; this.key = this.settings.encrypt.password this.iv = this.settings.encrypt.iv } get(name) { if (!name) { return this.protoService.prototype; } return super.get(name); } encrypt(text) { const cipher = crypto.createCipheriv('aes-256-ctr', this.key, this.iv) let encrypted = cipher.update(text, 'utf8', 'hex') encrypted += cipher.final('hex') return encrypted } decrypt(text) { const decipher = crypto.createDecipheriv('aes-256-ctr', this.key, this.iv) let decrypted = decipher.update(text, 'hex', 'utf8') decrypted += decipher.final('utf8') return decrypted } create(lifetime, id) { this.id = id || this.setId(); setMetasSession.call(this); this.manager.log("NEW SESSION CREATE : " + this.id, "DEBUG"); try{ this.cookieSession = this.setCookieSession(lifetime); }catch(e){ throw new Error(`Request Finish can't create cookieSession`); //this.log(`,"WARNING") } this.status = "active"; return this; } start(context, contextSession) { this.context = context; if (!contextSession) { contextSession = this.contextSession; } if (this.settings.use_only_cookies) { this.applyTranId = 0; } else { this.applyTranId = this.settings.use_trans_sid; } try { let ret = this.checkStatus(); switch (ret) { case false: return new Promise((resolve /*, reject*/ ) => { return resolve(this); }); case "restart": return this.start(context, contextSession); default: return this.getSession(contextSession); } } catch (e) { return new Promise((resolve, reject) => { this.log(e,"ERROR"); return reject(e); }); } } checkStatus() { switch (this.status) { case "active": this.manager.log("SESSION ALLREADY STARTED ==> " + this.name + " : " + this.id, "WARNING"); return false; case "disabled": try { this.storage = this.manager.initializeStorage(); if (this.storage) { this.status = "none"; return "restart"; } } catch (e) { this.manager.log("SESSION STORAGE HANDLER NOT FOUND ", "ERROR"); throw new Error("SESSION STORAGE HANDLER NOT FOUND "); } break; default: return true; } } getSession(contextSession) { if (this.settings.use_cookies) { if (this.context.cookieSession) { this.id = this.getId(this.context.cookieSession.value); this.cookieSession = this.context.cookieSession; } this.applyTranId = 0; } if ((!this.settings.use_only_cookies) && (!this.id)) { if (this.name in this.context.request.query) { this.id = this.getId(this.context.request.query[this.name]); } } if (this.id) { return this.checkChangeContext(contextSession); } else { return new Promise((resolve, reject) => { this.clear(); try { return resolve(this.create(this.lifetime, null)); } catch (e) { return reject(e); } }); } } async checkChangeContext(contextSession) { // change context session if (contextSession && this.contextSession !== contextSession) { this.manager.log(`SESSION CONTEXT CHANGE : ${this.contextSession} ==> ${contextSession}`); switch (this.strategy) { case "migrate": return this.storage.start(this.id, this.contextSession) .then(async (result) => { this.deSerialize(result); if (!this.isValidSession(result, this.context)) { this.manager.log("INVALID SESSION ==> " + this.name + " : " + this.id, "WARNING"); await this.destroy(); this.contextSession = contextSession; return this.create(this.lifetime, null); } await this.remove(); this.manager.log(`STRATEGY MIGRATE SESSION ==> ${this.name} : ${this.id}`, "DEBUG"); this.migrated = true; this.contextSession = contextSession; return this.create(this.lifetime, null); }).catch((error) => { throw error; }); case "invalidate": this.manager.log("STRATEGY INVALIDATE SESSION ==> " + this.name + " : " + this.id, "DEBUG"); await this.destroy(); this.contextSession = contextSession; return new Promise((resolve, reject) => { try { return resolve(this.create(this.lifetime, null)); } catch (e) { return reject(e); } }); case "none": this.strategyNone = true; break; } if (!this.strategyNone) { return new Promise((resolve /*, reject*/ ) => { return resolve(this); }); } } return this.storage.start(this.id, this.contextSession) .then(async (result) => { if (result && Object.keys(result).length) { this.deSerialize(result); if (!this.isValidSession(result, this.context)) { this.manager.log("SESSION ==> " + this.name + " : " + this.id + " session invalid ", "ERROR"); await this.invalidate(); } } else { if (this.settings.use_strict_mode) { if (!this.strategyNone) { this.manager.log("SESSION ==> " + this.name + " : " + this.id + " use_strict_mode ", "ERROR"); await this.invalidate(); } } } this.status = "active"; return this; }).catch(async (error) => { if (error) { this.manager.log("SESSION ==> " + this.name + " : " + this.id + " " + error, "ERROR"); if (!this.strategyNone) { await this.invalidate(); } throw error; } }); } isValidSession(data, context) { if (this.settings.referer_check) { try { checkSecureReferer.call(this, context); } catch (e) { this.manager.log("SESSION REFERER ERROR SESSION ==> " + this.name + " : " + this.id, "WARNING"); return false; } } let lastUsed = new Date(this.getMetaBag("lastUsed")).getTime(); let now = new Date().getTime(); if (this.lifetime === 0) { /*if ( lastUsed && lastUsed + ( this.settings.gc_maxlifetime * 1000 ) < now ){ this.manager.log("SESSION INVALIDE gc_maxlifetime ==> " + this.name + " : "+ this.id, "WARNING"); return false ; }*/ return true; } if (lastUsed && lastUsed + (this.lifetime * 1000) < now) { this.manager.log("SESSION INVALIDE lifetime ==> " + this.name + " : " + this.id, "WARNING"); return false; } return true; } attributes() { return this.protoService.prototype; } getAttributes() { return this.attributes(); } metaBag() { return this.protoParameters.prototype; } getMetas() { return this.metaBag(); } setMetaBag(key, value) { return this.setParameters(key, value); } getMetaBag(key) { return this.getParameters(key); } getFlashBag(key) { //this.log("GET FlashBag : " + key ,"WARNING") let res = this.flashBag[key]; if (res) { this.log("Delete FlashBag : " + key, "DEBUG"); delete this.flashBag[key]; return res; } return null; } setFlashBag(key, value) { if (!key) { throw new Error("FlashBag key must be define : " + key); } if (!value) { this.log("ADD FlashBag : " + key + " value not defined ", "WARNING"); } else { this.log("ADD FlashBag : " + key, "DEBUG"); } return this.flashBag[key] = value; } flashBags() { return this.flashBag; } clearFlashBags() { delete this.flashBag; this.flashBag = {}; } clearFlashBag(key) { if (!key) { throw new Error("clearFlashBag key must be define : " + key); } if (this.flashBag[key]) { delete this.flashBag[key]; } } deleteCookieSession() { let settings = nodefony.extend({}, this.settings.cookie); let cookie = new nodefony.cookies.cookie(this.name, "", settings); this.context.response.setCookie(cookie); this.cookieSession = null; this.context.cookieSession = null; return cookie; } setCookieSession(leftTime) { let settings = null; if (leftTime) { settings = nodefony.extend({}, this.settings.cookie); settings.maxAge = leftTime; } else { settings = this.settings.cookie; } let cookie = new nodefony.cookies.cookie(this.name, this.id, settings); this.context.response.addCookie(cookie); return cookie; } serialize(user) { let obj = { Attributes: this.protoService.prototype, metaBag: this.protoParameters.prototype, flashBag: this.flashBag, username: user }; return obj; } deSerialize(obj) { //var obj = JSON.parse(data); for (let attr in obj.Attributes) { this.set(attr, obj.Attributes[attr]); } for (let meta in obj.metaBag) { //console.log(meta + " : " + obj.metaBag[meta]) this.setMetaBag(meta, obj.metaBag[meta]); } for (let flash in obj.flashBag) { this.setFlashBag(flash, obj.flashBag[flash]); } } remove(cookieDelete) { try { return this.storage.destroy(this.id, this.contextSession) .then(() => { if (cookieDelete) { this.deleteCookieSession(); this.saved = true; } }).catch((e) => { this.manager.log(e, "ERROR"); throw e; }); } catch (e) { this.manager.log(e, "ERROR"); throw e; } } delete(cookieDelete = false) { return this.destroy(cookieDelete); } destroy(cookieDelete) { this.clear(); return this.remove(cookieDelete); } clear() { delete this.protoService; this.protoService = function () {}; delete this.protoParameters; this.protoParameters = function () {}; delete this.services; this.services = new this.protoService(); delete this.parameters; this.parameters = new this.protoParameters(); this.clearFlashBags(); } async invalidate(lifetime, id) { this.manager.log("INVALIDATE SESSION ==>" + this.name + " : " + this.id, "DEBUG"); if (!lifetime) { lifetime = this.lifetime; } await this.destroy(); try { return this.create(lifetime, id); } catch (e) { this.log(e, "WARNING") return this; } } async migrate(destroy, lifetime, id) { this.manager.log("MIGRATE SESSION ==>" + this.name + " : " + this.id, "DEBUG"); if (!lifetime) { lifetime = this.lifetime; } if (destroy) { await this.remove(destroy); } try { return this.create(lifetime, id); } catch (e) { this.log(e, "WARNING") return this; } } setId() { let ip = ""; try { ip = this.context.getRemoteAddress(); } catch (e) {} let date = new Date().getTime(); let concat = ip + date + this.randomValueHex(16) + Math.random() * 10; let hash = null; switch (this.settings.hash_function) { case "md5": hash = crypto.createHash('md5'); break; case "sha1": hash = crypto.createHash('sha1'); break; default: hash = crypto.createHash('md5'); } let res = hash.update(concat).digest("hex"); return this.encrypt(res + ":" + this.contextSession); } getId(value) { let res = this.decrypt(value); this.contextSession = res.split(':')[1]; return value; } save(user, sessionContext) { return this.storage.write(this.id, this.serialize(user), sessionContext).then(( /*result*/ ) => { if (!this.context) { throw new Error("SAVE SESSION ERROR context already deleted "); } else { this.saved = true; if (this.context) { this.context.fire("onSaveSession", this); } return this; } }).catch((error) => { //console.trace(error); //this.log(error, "ERROR"); this.saved = false; throw error; }); } getName() { return this.name; } setName(name) { this.name = name || this.settings.name; } randomValueHex(len) { return crypto.randomBytes(Math.ceil(len / 2)) .toString('hex') // convert to hexadecimal format .slice(0, len); // return required number of characters } }; return Session; });