UNPKG

@analog-tools/auth

Version:

Authentication module for AnalogJS applications

4 lines (3 loc) 34.5 kB
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("h3"),d=require("@analog-tools/session"),K=require("@trpc/server");var f=(r=>(r.SkyBlue="\x1B[94m",r.OceanBlue="\x1B[34m",r.MidnightBlue="\x1B[38;5;17m",r.SkyBlueBg="\x1B[104m",r.OceanBlueBg="\x1B[44m",r.MidnightBlueBg="\x1B[48;5;17m",r.MintGreen="\x1B[92m",r.ForestGreen="\x1B[32m",r.EmeraldGreen="\x1B[38;5;28m",r.MintGreenBg="\x1B[102m",r.ForestGreenBg="\x1B[42m",r.EmeraldGreenBg="\x1B[48;5;28m",r.LemonYellow="\x1B[93m",r.SunflowerYellow="\x1B[33m",r.GoldYellow="\x1B[38;5;220m",r.LemonYellowBg="\x1B[103m",r.SunflowerYellowBg="\x1B[43m",r.GoldYellowBg="\x1B[48;5;220m",r.RoseRed="\x1B[91m",r.FireRed="\x1B[31m",r.BurgundyRed="\x1B[38;5;88m",r.RoseRedBg="\x1B[101m",r.FireRedBg="\x1B[41m",r.BurgundyRedBg="\x1B[48;5;88m",r.LavenderPurple="\x1B[95m",r.RoyalPurple="\x1B[38;5;93m",r.DeepPurple="\x1B[38;5;54m",r.LavenderPurpleBg="\x1B[105m",r.RoyalPurpleBg="\x1B[48;5;93m",r.DeepPurpleBg="\x1B[48;5;54m",r.PeachOrange="\x1B[38;5;215m",r.TangerineOrange="\x1B[38;5;208m",r.AmberOrange="\x1B[38;5;214m",r.PeachOrangeBg="\x1B[48;5;215m",r.TangerineOrangeBg="\x1B[48;5;208m",r.AmberOrangeBg="\x1B[48;5;214m",r.SilverGray="\x1B[37m",r.SlateGray="\x1B[90m",r.CharcoalGray="\x1B[38;5;238m",r.SilverGrayBg="\x1B[47m",r.SlateGrayBg="\x1B[100m",r.CharcoalGrayBg="\x1B[48;5;238m",r.PureBlack="\x1B[30m",r.PureWhite="\x1B[97m",r.PureBlackBg="\x1B[40m",r.PureWhiteBg="\x1B[107m",r.Cyan="\x1B[36m",r.Reset="\x1B[0m",r.Bold="\x1B[1m",r.Dim="\x1B[2m",r.Underline="\x1B[4m",r.Blink="\x1B[5m",r.Reverse="\x1B[7m",r.Hidden="\x1B[8m",r))(f||{}),c=(r=>(r[r.trace=0]="trace",r[r.debug=1]="debug",r[r.info=2]="info",r[r.warn=3]="warn",r[r.error=4]="error",r[r.fatal=5]="fatal",r[r.silent=6]="silent",r))(c||{});function R(r){return["trace","debug","info","warn","error","fatal","silent"].includes(r)}const m=class m{static getCacheKey(e,t,s,i){if(!e.stack)return null;const n=e.stack.split(` `)[0]||"";return`${e.name}:${e.message}:${n}:${t}:${s}:${i}`}static addToCache(e,t){if(this.serializationCache.size>=this.MAX_CACHE_SIZE){const s=this.serializationCache.keys().next().value;s&&this.serializationCache.delete(s)}this.serializationCache.set(e,t)}static serialize(e,t={}){const{includeStack:s=!0,maxDepth:i=m.DEFAULT_MAX_DEPTH,includeNonEnumerable:n=!1}=t;if(e instanceof Error){const o=this.getCacheKey(e,s,i,n);if(o){const h=this.serializationCache.get(o);if(h)return h}const l=this.serializeError(e,s,i,n,new WeakSet);return o&&this.addToCache(o,l),l}if(typeof e=="string")return e;const a=this.safeStringify(e,i,new WeakSet);if(typeof a=="string")return a;try{return JSON.stringify(a,null,2)}catch{return String(a)}}static serializeError(e,t,s,i,n=new WeakSet){if(n.has(e))return{message:e.message,name:e.name,[Symbol.for("circular")]:this.CIRCULAR_REF_PLACEHOLDER};n.add(e);const a={message:e.message,name:e.name};return t&&e.stack&&(a.stack=e.stack),"cause"in e&&e.cause!==void 0&&(e.cause instanceof Error?a.cause=this.serializeError(e.cause,t,s-1,i,n):a.cause=this.safeStringify(e.cause,s-1,n)),Object.keys(e).forEach(o=>{if(!(o in a))try{const l=e;a[o]=this.safeStringify(l[o],s-1,n)}catch{a[o]=this.UNABLE_TO_SERIALIZE}}),i&&Object.getOwnPropertyNames(e).forEach(o=>{if(!(o in a)&&o!=="stack"&&o!=="message"&&o!=="name")try{const l=Object.getOwnPropertyDescriptor(e,o);if(l&&l.enumerable===!1){const h=e;a[o]=this.safeStringify(h[o],s-1,n)}}catch{a[o]=this.UNABLE_TO_SERIALIZE}}),a}static safeStringify(e,t,s=new WeakSet){if(t<=0)return this.MAX_DEPTH_PLACEHOLDER;if(e===null||typeof e!="object")return e;if(s.has(e))return this.CIRCULAR_REF_PLACEHOLDER;s.add(e);try{if(Array.isArray(e)){const n=e.map(a=>this.safeStringify(a,t-1,s));return s.delete(e),n}const i={};for(const[n,a]of Object.entries(e))try{i[n]=this.safeStringify(a,t-1,s)}catch{i[n]=this.UNABLE_TO_SERIALIZE}return s.delete(e),i}catch{return s.delete(e),this.UNABLE_TO_SERIALIZE}}};m.DEFAULT_MAX_DEPTH=10,m.CIRCULAR_REF_PLACEHOLDER="[Circular Reference]",m.MAX_DEPTH_PLACEHOLDER="[Max Depth Reached]",m.UNABLE_TO_SERIALIZE="[Unable to serialize]",m.serializationCache=new Map,m.MAX_CACHE_SIZE=100;let E=m;const V={highlight:{color:f.LemonYellow,bold:!0},accent:{color:f.SkyBlue},attention:{color:f.RoyalPurple,bold:!0},success:{color:f.ForestGreen},warning:{color:f.TangerineOrange},error:{color:f.FireRed},info:{color:f.OceanBlue},debug:{color:f.SlateGray}},G={success:"✅",warning:"⚠️",error:"❌",info:"ℹ️",debug:"🐞"},Y={trace:f.SlateGray,debug:f.Cyan,info:f.ForestGreen,warn:f.SunflowerYellow,error:f.FireRed,fatal:f.FireRed,silent:f.Reset},x=class x{constructor(e={}){this.styleCache=new Map;const t=process.env.NODE_ENV==="test"||process.env.VITEST==="true";this.useColors=e.useColors!==void 0?e.useColors:!t,this.globalStyles={...V,...e.styles},this.globalIcons={...G,...e.icons}}resolveStyle(e,t="test",s){return this.applyStyle(e,t,s)}setUseColors(e){this.useColors=e}getUseColors(){return this.useColors}updateStyleConfig(e,t){this.globalStyles={...this.globalStyles,...e},this.globalIcons={...this.globalIcons,...t}}formatMessage(e,t,s,i,n){const a=i?`[${s}:${i}]`:`[${s}]`;if(this.useColors){let o=this.getColorForLevel(e);return n&&(o=n),`${o}${a} ${t}${f.Reset}`}else return`${a} ${t}`}formatMessageWithMetadata(e,t,s,i,n){let a=t,o=this.getColorForLevel(e);if(i?.style){const l=this.applyStyle(i.style,s,n);l&&(o=l)}return i?.icon&&(a=`${this.resolveIcon(i.icon,s,n)} ${t}`),this.formatMessage(e,a,s,n,o)}parseMetadataParameter(e,t=[]){if(e&&typeof e=="object"&&!Array.isArray(e)&&("style"in e||"icon"in e))return{metadata:e,restData:t};const s=e!==void 0?[e,...t]:t;if(s.length>0){const i=s[s.length-1];if(i&&typeof i=="object"&&!Array.isArray(i)&&("style"in i||"icon"in i))return{metadata:i,restData:s.slice(0,-1)}}return{metadata:void 0,restData:s}}getColorForLevel(e){const t=c[e],s=Y[t];return e===c.fatal?`${f.Bold}${s}`:s||f.Reset}applyStyle(e,t,s){const i=this.getStyleCacheValue(e);if(i!==void 0)return i;if(typeof e=="string"){const n=this.getSemanticStyleColor(e,t,s);return this.setStyleCache(e,n),n}if(typeof e=="object"&&"color"in e){if(!this.isValidColor(e.color)){this.setStyleCache(e,void 0),this.logWarning("Invalid color provided. Only predefined ColorEnum values are allowed.",t,s);return}const n=this.constructStyleCode(e);return this.setStyleCache(e,n),n}this.setStyleCache(e,void 0),this.logWarning("Unknown style configuration provided. Only semantic style names or valid ColorEnum objects are allowed.",t,s)}isValidColor(e){return Object.values(f).includes(e)}isValidIcon(e){return this.isEmojiIcon(e)}getStyleCacheValue(e){return this.styleCache.has(e)?this.styleCache.get(e):void 0}setStyleCache(e,t){this.styleCache.set(e,t)}logWarning(e,t,s){const i=s?`${t}:${s}`:t;console.warn(`[${i}] ${e}`)}constructStyleCode(e){let t=e.color.toString();return e.bold&&(t+=f.Bold),e.underline&&(t+=f.Underline),t}getSemanticStyleColor(e,t,s){if(this.styleCache.has(e))return this.styleCache.get(e);const i=this.globalStyles[e];if(i){let a=i.color.toString();return i.bold&&(a+=f.Bold),i.underline&&(a+=f.Underline),this.styleCache.set(e,a),a}const n=s?`${t}:${s}`:t;console.warn(`[${n}] Unknown semantic style: ${e}. Falling back to default.`),this.styleCache.set(e,void 0)}getStyleCache(){return this.styleCache}resolveIcon(e,t,s){if(this.isValidIcon(e))return e;const i=["success","warning","error","info","debug"],n=i.find(a=>a===e);return n&&this.globalIcons[n]?this.globalIcons[n]:(i.includes(e)?this.logWarning(`Unknown icon: ${e}. Expected a valid emoji or semantic icon name.`,t,s):this.logWarning(`Invalid icon: ${e}. Expected a valid emoji or semantic icon name.`,t,s),e)}isEmojiIcon(e){return["✅","⚠️","❌","ℹ️","🐞","⭐️","🚀","🔥","✔️","✖️","❓","🔒","🔓","⏳","🕒","⬆️","⬇️","➡️","⬅️","📁","📄","👤","👥","✏️","➕","➖","🔔","⚡️","🎁","🐛","🌟","❤️","👀","⚙️","🔧","🔨","🔑","🎉","📝","🚨","📅","💡","🔍","🔗","🔖","📌","📎","✉️","📞","🌍","☁️","🌈","🌙","☀️","❄️","✨","🎵","📷","🎥","🎤","🔊","🔋","🗑️","💰","💳","🎂","🏅","🏆","👑","🛸","🛡️","🛑","▶️","⏸️","⏺️","⏪","⏩","🔁","🔀","🎲","🎈","🍪","☕️","🍵","🍺","🍷","🍕","🍔","🍟","🍎","🍌","🍒","🍋","🥕","🌽","🥦","🥚","🧀","🍞","🍰","🍦","🍫","🍿","🥓","🍤","🐟","🦀","🐙","🐋","🐬","🐧","🐸","🐢","🐍","🐉","🦄","🐱","🐶","🐭","🐰","🐻","🐼","🐨","🐯","🦁","🐒","🐘","🐎","🐄","🐖","🐑","🐔","🦆","🦢","🦉","🦅","🦜","🦚","🦩","🦋","🐝","🐜","🐞","🕷️","🦂","🐌","🪱","🐛","🦗","🦟","🪰","🪳","🪲"].includes(e)}};x.INJECTABLE=!0;let I=x;class T extends Error{constructor(e){super(e),this.name="LoggerError"}}const _={windowMs:5e3,flushOnCritical:!0},q=[c.error,c.fatal];class J{constructor(e,t){this.config=e,this.formatMessage=t,this.entries=new Map}addMessage(e,t,s=""){if(!this.config.enabled||this.config.flushOnCritical&&q.includes(e))return!0;const i=this.generateFingerprint(t,e,s),n=this.entries.get(i);return n?n.count++:this.entries.set(i,{message:t,level:e,context:s,firstSeen:Date.now(),count:1}),this.scheduleFlush(),!1}flush(){if(this.entries.size!==0){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=void 0);for(const e of this.entries.values())this.outputMessage(e);this.entries.clear()}}destroy(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=void 0),this.entries.clear()}generateFingerprint(e,t,s){return`${t}:${s}:${e}`}scheduleFlush(){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flush()},this.config.windowMs))}outputMessage(e){let t=e.message;e.count>1&&(t=`${t}${e.count})`);const s=this.formatMessage(e.level,t,e.context);switch(e.level){case c.trace:console.trace(s);break;case c.debug:console.debug(s);break;case c.info:console.info(s);break;case c.warn:console.warn(s);break;case c.error:console.error(s);break;case c.fatal:console.error(s);break}}}const v=class v{constructor(e={},t,s,i){if(this.config=e,this.childLoggers={},this.disabledContexts=[],this.activeGroups=[],i)this.parentLogger=i,this.name=i.name,this.context=s,this.logLevel=i.getLogLevel(),this.styleEngine=i.styleEngine;else{if(typeof e.level=="string"&&!Object.keys(c).includes(e.level))throw new T(`Invalid log level: ${e.level}`);if(this.logLevel=this.castLoglevel(e.level||process.env.LOG_LEVEL||"info"),this.name=e.name||"analog-tools",this.setDisabledContexts(e.disabledContexts??process.env.LOG_DISABLED_CONTEXTS?.split(",")??[]),this.styleEngine=t||new I({useColors:e.useColors,styles:{...V,...e.styles},icons:{...G,...e.icons}}),e.deduplication?.enabled){const n={enabled:!0,windowMs:e.deduplication.windowMs??_.windowMs,flushOnCritical:e.deduplication.flushOnCritical??_.flushOnCritical},a=(o,l,h)=>this.styleEngine.formatMessage(o,l,this.name,h);this.deduplicator=new J(n,a)}}}isContextEnabled(){return this.context?!(this.parentLogger||this).disabledContexts.includes(this.context):!0}setDisabledContexts(e){this.disabledContexts=e}getLogLevel(){return this.logLevel}getDisabledContexts(){return(this.parentLogger||this).disabledContexts||[]}setUseColors(e){(this.parentLogger||this).styleEngine.setUseColors(e)}getUseColors(){return(this.parentLogger||this).styleEngine.getUseColors()}forContext(e){return this.childLoggers[e]||(this.childLoggers[e]=new v({},void 0,e,this)),this.childLoggers[e]}group(e){if(!this.isContextEnabled())return;(this.parentLogger?.activeGroups||this.activeGroups).push(e);const s=this.styleEngine.formatMessage(c.info,`Group: ${e}`,this.name,this.context);console.group(`${s} ▼`)}groupEnd(e){if(!this.isContextEnabled())return;const t=this.parentLogger?.activeGroups||this.activeGroups;if(e){const s=t.lastIndexOf(e);if(s!==-1){const i=t.splice(s);for(let n=0;n<i.length;n++)console.groupEnd(),console.log("")}}else t.length>0&&(t.pop(),console.groupEnd(),console.log(""))}trace(e,t,...s){if(!this.isContextEnabled()||this.logLevel>c.trace)return;const{metadata:i,restData:n}=this.styleEngine.parseMetadataParameter(t,s);if(this.handleDeduplication(c.trace,e,i,n))if(i){const a=this.styleEngine.formatMessageWithMetadata(c.trace,e,this.name,i,this.context);console.trace(a,...n||[])}else{const a=this.styleEngine.formatMessage(c.trace,e,this.name,this.context);console.trace(a,...n||[])}}debug(e,t,...s){if(!this.isContextEnabled()||this.logLevel>c.debug)return;const{metadata:i,restData:n}=this.styleEngine.parseMetadataParameter(t,s);if(this.handleDeduplication(c.debug,e,i,n))if(i){const a=this.styleEngine.formatMessageWithMetadata(c.debug,e,this.name,i,this.context);console.debug(a,...n||[])}else{const a=this.styleEngine.formatMessage(c.debug,e,this.name,this.context);console.debug(a,...n||[])}}info(e,t,...s){if(!this.isContextEnabled()||this.logLevel>c.info)return;const{metadata:i,restData:n}=this.styleEngine.parseMetadataParameter(t,s);if(this.handleDeduplication(c.info,e,i,n))if(i){const a=this.styleEngine.formatMessageWithMetadata(c.info,e,this.name,i,this.context);console.info(a,...n||[])}else{const a=this.styleEngine.formatMessage(c.info,e,this.name,this.context);console.info(a,...n||[])}}warn(e,t,...s){if(!this.isContextEnabled()||this.logLevel>c.warn)return;const{metadata:i,restData:n}=this.styleEngine.parseMetadataParameter(t,s);if(this.handleDeduplication(c.warn,e,i,n))if(i){const a=this.styleEngine.formatMessageWithMetadata(c.warn,e,this.name,i,this.context);console.warn(a,...n||[])}else{const a=this.styleEngine.formatMessage(c.warn,e,this.name,this.context);console.warn(a,...n||[])}}error(e,t,s,...i){if(!this.isContextEnabled()||this.logLevel>c.error)return;const{message:n,serializedError:a,context:o,data:l}=this.parseErrorParameters(e,t,s,i),h=l||[];let g,S=h;if(h.length>0){const w=h[h.length-1];w&&typeof w=="object"&&!Array.isArray(w)&&("style"in w||"icon"in w)&&(g=w,S=h.slice(0,-1))}const C=[this.styleEngine.formatMessageWithMetadata(c.error,n,this.name,g,this.context)];a&&C.push(a),o&&C.push(o),console.error(...C,...S)}fatal(e,t,s,...i){if(!this.isContextEnabled()||this.logLevel>c.fatal)return;if(typeof e=="string"&&t&&typeof t=="object"&&!Array.isArray(t)&&("style"in t||"icon"in t)){const w=t,W=this.styleEngine.formatMessageWithMetadata(c.fatal,`FATAL: ${e}`,this.name,w,this.context);console.error(W);return}const{message:n,serializedError:a,context:o,data:l}=this.parseErrorParameters(e,t,s,i),h=l||[];let g,S=h;if(h.length>0){const w=h[h.length-1];w&&typeof w=="object"&&!Array.isArray(w)&&("style"in w||"icon"in w)&&(g=w,S=h.slice(0,-1))}const C=[this.styleEngine.formatMessageWithMetadata(c.fatal,`FATAL: ${n}`,this.name,g,this.context)];a&&C.push(a),o&&C.push(o),console.error(...C,...S)}parseErrorParameters(e,t,s,i=[]){if(e instanceof Error&&t===void 0)return{message:e.message,serializedError:E.serialize(e),data:[]};if(typeof e=="string"&&t===void 0)return{message:e,data:[]};if(typeof e=="string"&&t instanceof Error&&s===void 0)return{message:e,serializedError:E.serialize(t),data:[]};if(typeof e=="string"&&this.isLogContext(t)&&s===void 0)return{message:e,context:t,data:[]};if(typeof e=="string"&&t instanceof Error&&this.isLogContext(s))return{message:e,serializedError:E.serialize(t),context:s,data:i};const n=typeof e=="string"?e:"Unknown error",a=t?E.serialize(t):void 0,o=s!==void 0?[s,...i]:i;return{message:n,serializedError:a,data:o}}isLogContext(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)&&!(e instanceof Error)&&!(e instanceof Date)&&!(e instanceof RegExp)&&typeof e!="function"}castLoglevel(e){if(R(e))switch(e){case"trace":return c.trace;case"debug":return c.debug;case"info":return c.info;case"warn":return c.warn;case"error":return c.error;case"fatal":return c.fatal;case"silent":return c.silent}const t=e.toLowerCase();throw R(t)?new T(`Invalid log level: ${e}. Log levels are case-sensitive. Valid levels: trace, debug, info, warn, error, fatal, silent.`):new T(`Invalid log level: ${e}. Valid levels: trace, debug, info, warn, error, fatal, silent.`)}safeFormatMessage(e,t,s,i="",n=""){try{return this.styleEngine.formatMessage(e,t,s,i,n)}catch(a){throw this.error("Style engine failure",a),new T("Style engine failure")}}shouldLogImmediately(e,t){const i=(this.parentLogger||this).deduplicator;if(!i)return!0;try{return i.addMessage(e,t,this.context)}catch(n){return console.error("Logger deduplication error:",n),!0}}handleDeduplication(e,t,s,i){return!s&&i.length===0?this.shouldLogImmediately(e,t):!0}};v.INJECTABLE=!0;let A=v,B=null;function O(){return B||(B=new Z),B}class Z{constructor(){this.serviceMap=new Map}register(e,...t){if(this.isServiceInjectable(e)&&(!this.hasService(e)||this.getService(e)===void 0)){if(t===void 0||t.length===0){this.serviceMap.set(this.getInjcectableName(e),new e);return}this.serviceMap.set(this.getInjcectableName(e),new e(...t))}}registerAsUndefined(e){if(this.isServiceInjectable(e))this.serviceMap.set(this.getInjcectableName(e),void 0);else throw new Error(`Service with token ${this.getInjcectableName(e)} is not injectable. Ensure it has the INJECTABLE static property set to true.`)}registerCustomServiceInstance(e,t){if(this.isServiceInjectable(e))this.serviceMap.set(this.getInjcectableName(e),t);else throw new Error(`Service with token ${this.getInjcectableName(e)} is not injectable. Ensure it has the INJECTABLE static property set to true.`)}getService(e){if(this.isServiceInjectable(e))return this.hasService(e)||this.register(e),this.serviceMap.get(this.getInjcectableName(e))}hasService(e){return this.serviceMap.has(this.getInjcectableName(e))}isServiceInjectable(e){const t=e.INJECTABLE;return t!==void 0&&t!==!1}getInjcectableName(e){if(this.isServiceInjectable(e)){const t=e.INJECTABLE;return typeof t!="string"?e.name:t}throw new Error(`Service with token ${e.name} is not injectable. Ensure it has the INJECTABLE static property set to true.`)}destroy(){this.serviceMap.clear()}}function X(r,e,t={}){const{required:s=!0}=t,i=r.getService(e);if(!i&&s)throw new Error(`Service with token ${e||"unknown"} not found in registry`);return i}function p(r,e={}){return X(O(),r,e)}function H(r,...e){O().register(r,...e)}const L=class L{constructor(e){this.storageConfig=e,this.logger=p(A).forContext("SessionService")}async initSession(e){d.getSession(e)?this.logger.debug("Session already exists, skipping initialization"):(this.logger.info("Creating new session"),this.store||(this.store=await d.createUnstorageStore(this.storageConfig.driver)),await d.useSession(e,{store:this.store,secret:this.storageConfig.sessionSecret||"change-me-in-production",name:"auth.session",maxAge:3600*24,cookie:{httpOnly:!0,secure:process.env.NODE_ENV==="production",sameSite:"lax"},generate:()=>({auth:{isAuthenticated:!1}})}))}async getSession(e){try{const t=await this.store.getItem(e);return t?{id:e,data:t,save:async()=>{await this.store.setItem(e,t)}}:null}catch(t){return this.logger.error("Error retrieving session",t,{sessionId:e}),null}}async getActiveSessions(){try{const e=await this.store.getKeys();return(await Promise.all(e.map(async s=>{const i=await this.store.getItem(s);return i?{id:this.storageConfig.prefix&&s.startsWith(`${this.storageConfig.prefix}:`)?s.substring(`${this.storageConfig.prefix}:`.length):s,data:i,update:a=>{const o=a(i);Object.assign(i,o)},save:async()=>{await this.store.setItem(s,i)}}:null}))).filter(s=>s!==null)}catch(e){return this.logger.error("Error retrieving active sessions",e),[]}}async destroyAuthSession(e){try{await this.initSession(e),d.getSession(e)?.auth&&await d.updateSession(e,s=>{const i={...s};return delete i.auth,i}),await d.destroySession(e)}catch(t){throw this.logger.error("Session destruction failed",t),u.createError({statusCode:500,message:"Session handling failed"})}}async getSessionData(e,t){await this.initSession(e);const s=d.getSession(e);return this.logger.debug("Retrieved session data",s),s?.[t]||null}async setSessionData(e,t,s){await this.initSession(e),await d.updateSession(e,i=>({...i,[t]:s}))}async isValidSession(e){return await this.initSession(e),!!d.getSession(e)}};L.INJECTABLE=!0;let k=L;const M=class M{constructor(e){this.openIDConfigCache=null,this.configLastFetched=null,this.CONFIG_CACHE_TTL=36e5,this.TOKEN_REFRESH_SAFETY_MARGIN=300,this.logger=p(A).forContext("OAuthAuthenticationService"),H(k,e.sessionStorage),this.config=e}validateConfiguration(){if(!this.config.issuer||!this.config.clientId||!this.config.clientSecret||!this.config.callbackUri)throw new Error("OAuth Authentication Service not properly initialized. Make sure to call AnalogAuth() with valid configuration before using authentication features.")}async initSession(e){return await p(k).initSession(e)}getConfig(){return this.validateConfiguration(),this.config}getConfigValue(e,t){const s=this.config[e];if(s===void 0||typeof s=="string"&&s===""){if(t!==void 0)return t;if(e==="userHandler"||e==="logoutUrl"||e==="audience")return;if(e==="unprotectedRoutes")return[];throw new Error(`Configuration value for '${e}' doesn't exist`)}return s}isUnprotectedRoute(e){const t=this.getConfigValue("unprotectedRoutes",[]);return Array.isArray(t)?t.some(s=>{if(s.endsWith("*")){const a=s.slice(0,-1);if(!e.startsWith(a))return!1;const o=e.slice(a.length);return o.length>0&&o!=="/"}const i=s.endsWith("/")?s:s+"/",n=e.endsWith("/")?e:e+"/";return e===s||n===i}):!1}async getAuthorizationUrl(e,t){this.validateConfiguration();const s=await this.getOpenIDConfiguration(),i=this.getConfigValue("audience",void 0),n={response_type:"code",client_id:this.getConfigValue("clientId"),redirect_uri:t||this.getConfigValue("callbackUri"),scope:this.getConfigValue("scope"),state:e,...i?{audience:i}:{}},a=new URLSearchParams(n);return`${s.authorization_endpoint}?${a.toString()}`}async exchangeCodeForTokens(e,t){const s=await this.getOpenIDConfiguration(),i=await fetch(s.token_endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:this.getConfigValue("clientId"),client_secret:this.getConfigValue("clientSecret"),code:e,redirect_uri:t||this.getConfigValue("callbackUri")}).toString()});if(!i.ok){const n=await i.json();throw this.logger.error("Error exchanging code for tokens",n),u.createError({statusCode:401,message:"Failed to exchange authorization code"})}return await i.json()}async refreshTokens(e){const t=await this.getOpenIDConfiguration();try{const s=await fetch(t.token_endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",client_id:this.getConfigValue("clientId"),client_secret:this.getConfigValue("clientSecret"),refresh_token:e}).toString()});if(!s.ok){const i=await s.json().catch(()=>({error:"Unknown error"}));throw this.logger.error("Error refreshing token",i),u.createError({statusCode:401,message:"Failed to refresh token"})}return await s.json()}catch(s){throw this.logger.error("Error during token refresh",s),u.createError({statusCode:401,message:"Failed to refresh authentication token"})}}async getUserInfo(e,t=3){const s=await this.getOpenIDConfiguration();let i=null;for(let n=1;n<=t;n++)try{const a=await fetch(s.userinfo_endpoint,{headers:{Authorization:`Bearer ${e}`},signal:AbortSignal.timeout(1e4)});if(!a.ok){const l=await a.json().catch(()=>({error:"Unknown error"}));if(this.logger.error("Error getting user info",l,{attempt:n,maxRetries:t}),a.status===401)throw u.createError({statusCode:401,message:"Authentication token is invalid or expired"});if(a.status===429){const h=parseInt(a.headers.get("Retry-After")||"5",10);await new Promise(g=>setTimeout(g,h*1e3));continue}else if(a.status>=500){await new Promise(h=>setTimeout(h,n*1e3));continue}else throw u.createError({statusCode:a.status,message:`Failed to get user info: ${l.error||"Unknown error"}`})}const o=await a.json();if(!o||!o.sub&&!o.id)throw u.createError({statusCode:500,message:"Invalid user data received from provider"});return o}catch(a){if(i=a,a instanceof Error&&"statusCode"in a&&a.statusCode===401)throw a;if((a instanceof TypeError||a instanceof Error&&a.name==="AbortError")&&(this.logger.error("Network error fetching user info",a,{attempt:n,maxRetries:t}),n<t)){await new Promise(o=>setTimeout(o,Math.pow(2,n)*500));continue}if(n===t)throw this.logger.error("Failed to get user info after multiple attempts",{maxRetries:t}),u.createError({statusCode:500,message:"Failed to get user info after multiple attempts",cause:i})}throw u.createError({statusCode:500,message:"Unexpected error getting user info"})}async handleCallback(e,t,s){if(!s)throw u.createError({statusCode:400,message:"Invalid state parameter"});const i=await this.exchangeCodeForTokens(t),{access_token:n,id_token:a,refresh_token:o,expires_in:l}=i,h=await this.getUserInfo(n),g=this.getConfigValue("userHandler",void 0);let S=h;g&&"createOrUpdateUser"in g&&(S=await g.createOrUpdateUser?.(h));const b={isAuthenticated:!0,accessToken:n,idToken:a,refreshToken:o,expiresAt:Date.now()+l*1e3,userInfo:h};return await d.updateSession(e,()=>({user:S,auth:b})),this.logger.debug("Authentication session data saved successfully"),{user:S,tokens:i}}shouldRefreshToken(e){const t=Date.now(),s=this.TOKEN_REFRESH_SAFETY_MARGIN*1e3;return t+s>e}async refreshExpiringTokens(){this.logger.debug("Starting bulk token refresh process");try{const t=await p(k).getActiveSessions();let s=0,i=0;const n=t.length;this.logger.debug(`Found ${n} active sessions to check`);for(const o of t)try{if(!o.data.auth?.isAuthenticated||!o.data.auth.refreshToken||!o.data.auth.expiresAt){this.logger.debug(`Skipping session ${o.id} - no valid auth data`);continue}if(!this.shouldRefreshToken(o.data.auth.expiresAt)){this.logger.debug(`Skipping session ${o.id} - token not expiring soon`);continue}this.logger.debug(`Refreshing token for session ${o.id}`);const l=await this.refreshTokens(o.data.auth.refreshToken);o.update(h=>{const g=h.auth;return g?{...h,auth:{...g,accessToken:l.access_token,idToken:l.id_token||g.idToken,refreshToken:l.refresh_token||g.refreshToken,expiresAt:Date.now()+l.expires_in*1e3}}:h}),await o.save(),s++,this.logger.debug(`Successfully refreshed token for session ${o.id}`)}catch(l){i++,this.logger.error("Failed to refresh token for session",l,{sessionId:o.id});try{o.update(h=>{const g=h.auth;return g?{...h,auth:{...g,isAuthenticated:!1}}:h}),await o.save()}catch(h){this.logger.error("Failed to update session after refresh failure",h,{sessionId:o.id})}}const a={refreshed:s,failed:i,total:n};return this.logger.info("Bulk token refresh completed",a),a}catch(e){throw this.logger.error("Error during bulk token refresh",e),u.createError({statusCode:500,message:"Failed to refresh expiring tokens"})}}async isAuthenticated(e){await p(k).initSession(e);const t=await d.getSession(e);if(!t?.auth)return await d.updateSession(e,()=>({auth:{isAuthenticated:!1}})),!1;if(!t.auth.isAuthenticated)return!1;if(t.auth.expiresAt&&t.auth.expiresAt<Date.now())if(t.auth.refreshToken)try{const s=await this.refreshTokens(t.auth.refreshToken);return await d.updateSession(e,i=>({auth:{...i.auth,isAuthenticated:!0,accessToken:s.access_token,idToken:s.id_token||t?.auth?.idToken,refreshToken:s.refresh_token||t?.auth?.refreshToken,expiresAt:Date.now()+s.expires_in*1e3}})),!0}catch(s){return this.logger.error("Error refreshing token",s),await d.updateSession(e,i=>({auth:{...i.auth,isAuthenticated:!1}})),this.logger.info("error occurred while refreshing token"),this.logger.groupEnd("OAuthAuthenticationService.isAuthenticated "+e.path),!1}else return this.logger.info(" No refresh token available"),this.logger.groupEnd("OAuthAuthenticationService.isAuthenticated "+e.path),!1;return t.auth.expiresAt&&this.shouldRefreshToken(t.auth.expiresAt)&&setTimeout(async()=>{try{const s=await d.getSession(e);if(!s?.auth?.isAuthenticated||!s.auth.refreshToken)return;const i=await this.refreshTokens(s.auth.refreshToken);await d.updateSession(e,n=>({auth:{...n.auth,isAuthenticated:!0,accessToken:i.access_token,idToken:i.id_token||s?.auth?.idToken,refreshToken:i.refresh_token||s?.auth?.refreshToken,expiresAt:Date.now()+i.expires_in*1e3}})),this.logger.debug("Background token refresh completed")}catch(s){this.logger.error("Background token refresh failed",s)}},0),!0}async getAuthenticatedUser(e){if(!await this.isAuthenticated(e))return null;const t=d.getSession(e),s=this.getConfigValue("userHandler",void 0);return s&&"mapUserToLocal"in s?s.mapUserToLocal?.(t.auth?.userInfo):t.auth?.userInfo}async revokeToken(e){const t=await this.getOpenIDConfiguration();if(!(await fetch(t.revocation_endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:this.getConfigValue("clientId"),client_secret:this.getConfigValue("clientSecret"),token:e})})).ok)throw new Error("Failed to revoke token")}async logout(e){await p(k).initSession(e);const t=d.getSession(e),s=await this.getOpenIDConfiguration();if(t?.auth?.accessToken)try{await this.revokeToken(t.auth.accessToken)}catch(a){this.logger.error("Failed to revoke access token",a)}if(t?.auth?.refreshToken)try{await this.revokeToken(t.auth.refreshToken)}catch(a){this.logger.error("Failed to revoke refresh token",a)}const i=new URL(s.end_session_endpoint);i.searchParams.append("client_id",this.getConfigValue("clientId"));const n=this.getConfigValue("logoutUrl");return n&&i.searchParams.append("returnTo",n),await d.updateSession(e,()=>({auth:{isAuthenticated:!1},user:null})),i.toString()}async getOpenIDConfiguration(){const e=Date.now();if(this.openIDConfigCache&&this.configLastFetched&&e-this.configLastFetched<this.CONFIG_CACHE_TTL)return this.openIDConfigCache;try{const t=await fetch(`${this.getConfigValue("issuer")}/.well-known/openid-configuration`);if(!t.ok)throw new Error(`Failed to fetch OpenID configuration: ${t.statusText}`);const s=await t.json();return this.openIDConfigCache=s,this.configLastFetched=e,s}catch(t){throw this.logger.error("Error fetching OpenID configuration",t),u.createError({statusCode:500,message:"Failed to fetch OpenID configuration"})}}};M.INJECTABLE=!0;let y=M;const U={path:"authenticated",handler:async r=>{const t={authenticated:await p(y).isAuthenticated(r)};return p(A).forContext("AuthMiddleware").info("User authentication status checked",t),t}},D={path:"callback",handler:async r=>{const e=p(y);if(await e.initSession(r),await e.isAuthenticated(r))return u.sendRedirect(r,"/");const t=u.getQuery(r),s=t.code,i=t.state,a=d.getSession(r)?.state;if(!i||!a||i!==a)throw u.createError({statusCode:400,message:"Invalid or missing state parameter. Authentication flow may have been tampered with.",statusMessage:"Authorization Failed"});await d.updateSession(r,h=>{const g={...h};return delete g.state,g}),await e.handleCallback(r,s,i);const l=d.getSession(r)?.redirectUrl||"/";return await d.updateSession(r,h=>{const g={...h};return delete g.redirectUrl,g}),u.sendRedirect(r,l)}},Q=globalThis.crypto,ee=()=>Q.randomUUID(),$={path:"login",handler:async r=>{const e=p(y);await e.initSession(r);const t=ee();await d.updateSession(r,a=>({...a,state:t}));const i=u.getQuery(r).redirect_uri,n=await e.getAuthorizationUrl(t,i);return u.sendRedirect(r,n)}},F={path:"logout",handler:async r=>{const e=p(A).forContext("LogoutRoute");try{const t=p(y);await t.initSession(r);const s=await t.logout(r);return u.sendRedirect(r,s)}catch(t){throw e.error("Logout failed",t),u.createError({statusCode:500,message:"Logout failed"})}}},P={path:"protected-data",handler:async r=>({message:"This is protected data that requires authentication",user:await p(y).getAuthenticatedUser(r)})},j={path:"refresh-tokens",handler:async r=>{const e=p(A).forContext("TokenRefresh"),t=p(y),s=t.getConfig().tokenRefreshApiKey;if(!s)throw e.error("Token refresh API key not configured in either AnalogAuthConfig.tokenRefreshApiKey or TOKEN_REFRESH_API_KEY env variable"),u.createError({statusCode:500,message:"Server configuration error"});const i=u.getRequestHeaders(r).authorization;if(!i||`Bearer ${s}`!==i)throw e.warn("Unauthorized token refresh attempt"),u.createError({statusCode:401,message:"Unauthorized"});try{const n=await t.refreshExpiringTokens();return e.info("Token refresh job completed",{refreshed:n.refreshed,failed:n.failed,total:n.total}),{success:!0,...n}}catch(n){throw e.error("Error in token refresh job",n),u.createError({statusCode:500,message:"Failed to refresh tokens"})}}},z={path:"user",handler:async r=>{const e=p(y);if(await e.initSession(r),!await e.isAuthenticated(r))throw u.createError({statusCode:401,message:"Unauthorized"});return e.getAuthenticatedUser(r)}};function te(){return{[U.path]:U.handler,[D.path]:D.handler,[$.path]:$.handler,[F.path]:F.handler,[P.path]:P.handler,[j.path]:j.handler,[z.path]:z.handler}}function se(r){return new URL(r,"http://dummy-base").pathname.split("/").filter(Boolean).pop()??""}async function ie(r){if(r.path.includes("/api/auth/")){const e=se(r.path);if(!e)throw u.createError({statusCode:400,statusMessage:"Missing path parameter"});await p(y).initSession(r);const s=te();if(s[e])return s[e](r);throw u.createError({statusCode:404,statusMessage:`Authentication route '${e}' not found`})}}async function N(r){const e=p(y);return await e.initSession(r),e.isAuthenticated(r)}async function re(r){const e=u.getRequestURL(r).pathname,t=p(y),s=p(A).forContext("AuthMiddleware");if(s.info("Processing authentication middleware",e),e.startsWith("/api/auth/login")||e.startsWith("/api/auth/callback")||e.startsWith("/api/auth/authenticated")||t.isUnprotectedRoute(e)||e.startsWith("/api/trpc"))return;const i=u.getHeader(r,"fetch");if(u.getHeader(r,"ssr")!=="true"&&(await t.initSession(r),!await N(r))){if(i==="true")throw new K.TRPCError({code:"UNAUTHORIZED",message:"User is not authenticated"});s.debug("Redirecting to login page",{path:e}),await u.sendRedirect(r,"/api/auth/login")}if(i==="true")return{name:"TrpcError",code:"NOT_IMPLEMENTED",message:"SSR is not supported for this route"}}async function ne(r,e){const s=["issuer","clientId","clientSecret","callbackUri"].filter(i=>!r[i]);if(s.length>0)throw new Error(`AnalogAuth initialization failed: Missing mandatory configuration values: ${s.join(", ")}`);return H(y,r),await re(e),ie(e)}exports.checkAuthentication=N;exports.useAnalogAuth=ne; //# sourceMappingURL=index.cjs.map