advanced-ussd-builder
Version:
Advanced USSD Menu Builder with persistent state and navigation
2 lines (1 loc) • 4.69 kB
JavaScript
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?function(s,e,t,r){r===void 0&&(r=t);var n=Object.getOwnPropertyDescriptor(e,t);(!n||("get"in n?!e.__esModule:n.writable||n.configurable))&&(n={enumerable:!0,get:function(){return e[t]}}),Object.defineProperty(s,r,n)}:function(s,e,t,r){r===void 0&&(r=t),s[r]=e[t]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(s,e){Object.defineProperty(s,"default",{enumerable:!0,value:e})}:function(s,e){s.default=e}),__importStar=this&&this.__importStar||function(){var s=function(e){return s=Object.getOwnPropertyNames||function(t){var r=[];for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(r[r.length]=n);return r},s(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var r=s(e),n=0;n<r.length;n++)r[n]!=="default"&&__createBinding(t,e,r[n]);return __setModuleDefault(t,e),t}}();Object.defineProperty(exports,"__esModule",{value:!0}),exports.SecureSessionManager=void 0;const crypto=__importStar(require("crypto")),events_1=require("events");class SecureSessionManager extends events_1.EventEmitter{constructor(e={}){super(),this.revokedSessions=new Set;const t=e.masterKey||process.env.SESSION_MASTER_KEY;if(!t||t.length<32)throw new Error("SESSION_MASTER_KEY must be at least 32 characters. Set it in environment variables.");this.encryptionKey=this.deriveKey(t,"encryption"),this.signingKey=this.deriveKey(t,"signing"),this.sessionTTL=e.sessionTTL||18e5,this.maxSessionAge=e.maxSessionAge||864e5,this.regenerateOnAuth=e.regenerateOnAuth!==!1}deriveKey(e,t){return crypto.pbkdf2Sync(e,`session-${t}`,1e5,32,"sha256")}generateSessionId(){return crypto.randomBytes(32).toString("hex")}createSession(e,t){const r=Date.now(),n=this.generateSessionId(),i={sessionId:n,phoneNumber:e,authenticated:!1,createdAt:r,lastActivity:r,expiresAt:r+this.sessionTTL,metadata:t||{}};return this.emit("session-created",{sessionId:n,phoneNumber:e}),i}encryptData(e){const t=crypto.randomBytes(16),r=crypto.createCipheriv("aes-256-gcm",this.encryptionKey,t),n=JSON.stringify(e),i=Buffer.concat([r.update(n,"utf8"),r.final()]),a=r.getAuthTag();return Buffer.concat([t,a,i]).toString("base64")}decryptData(e){try{const t=Buffer.from(e,"base64");if(t.length<32)throw new Error("Invalid encrypted data format");const r=t.slice(0,16),n=t.slice(16,32),i=t.slice(32),a=crypto.createDecipheriv("aes-256-gcm",this.encryptionKey,r);a.setAuthTag(n);const o=Buffer.concat([a.update(i),a.final()]);return JSON.parse(o.toString("utf8"))}catch(t){throw t.message.includes("Unsupported state or unable to authenticate data")?new Error("Session integrity check failed - data may have been tampered with"):t}}createSessionToken(e){const t={sid:e.sessionId,iat:Date.now(),exp:e.expiresAt},r=this.encryptData(e),n={...t,data:r},i=Buffer.from(JSON.stringify(n)).toString("base64url"),a=this.sign(i);return`${i}.${a}`}verifySessionToken(e){try{const[t,r]=e.split(".");if(!t||!r)throw new Error("Invalid token format");if(!this.verifySignature(t,r))throw new Error("Invalid session signature");const n=JSON.parse(Buffer.from(t,"base64url").toString());if(n.exp<Date.now())throw new Error("Session expired");const i=this.decryptData(n.data);if(i.sessionId!==n.sid)throw new Error("Session ID mismatch");if(Date.now()-i.createdAt>this.maxSessionAge)throw new Error("Session too old");return i}catch(t){throw this.emit("session-verification-failed",{error:t.message}),t}}touchSession(e){const t=Date.now();return{...e,lastActivity:t,expiresAt:t+this.sessionTTL}}authenticateSession(e,t){const r={...e,userId:t,authenticated:!0,lastActivity:Date.now()};return this.regenerateOnAuth&&(r.sessionId=this.generateSessionId(),this.emit("session-regenerated",{oldSessionId:e.sessionId,newSessionId:r.sessionId})),this.emit("session-authenticated",{sessionId:r.sessionId,userId:t}),r}invalidateSession(e){this.revokedSessions.add(e),this.emit("session-invalidated",{sessionId:e})}sign(e){const t=crypto.createHmac("sha256",this.signingKey);return t.update(e),t.digest("base64url")}verifySignature(e,t){const r=this.sign(e);return crypto.timingSafeEqual(Buffer.from(t),Buffer.from(r))}isSessionValid(e){if(this.revokedSessions.has(e.sessionId))return!1;const t=Date.now();return!(e.expiresAt<t||t-e.createdAt>this.maxSessionAge||t-e.lastActivity>this.sessionTTL)}getSessionMetadata(e){return{sessionId:e.sessionId.substring(0,8)+"...",authenticated:e.authenticated,createdAt:new Date(e.createdAt).toISOString(),lastActivity:new Date(e.lastActivity).toISOString(),expiresAt:new Date(e.expiresAt).toISOString(),hasUserId:!!e.userId,hasPhoneNumber:!!e.phoneNumber}}}exports.SecureSessionManager=SecureSessionManager;