@analog-tools/auth
Version:
Authentication module for AnalogJS applications
1,624 lines • 60 kB
JavaScript
import { createError as d, sendRedirect as b, getQuery as H, getRequestHeaders as q, getRequestURL as X, getHeader as U } from "h3";
import { getSession as C, createRedisStore as $, createMemoryStore as Q, useSession as ee, updateSession as m, destroySession as te } from "@analog-tools/session";
import { TRPCError as se } from "@trpc/server";
var g = /* @__PURE__ */ ((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))(g || {}), h = /* @__PURE__ */ ((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))(h || {});
function F(r) {
return ["trace", "debug", "info", "warn", "error", "fatal", "silent"].includes(r);
}
const A = class A {
/**
* Generate a cache key for memoization (includes serialization options)
* @private
*/
static getCacheKey(e, t, s, i) {
if (!e.stack) return null;
const a = e.stack.split(`
`)[0] || "";
return `${e.name}:${e.message}:${a}:${t}:${s}:${i}`;
}
/**
* Add to cache with size limit enforcement
* @private
*/
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);
}
/**
* Safely serialize any value (Error objects, plain objects, primitives) for logging
*
* @param error - The value to serialize (Error, object, string, etc.)
* @param options - Configuration options for serialization behavior
* @returns Serialized representation - StructuredError for Error objects, string for others
*
* @example
* ```typescript
* // Serialize an Error object
* const error = new Error('Failed to connect');
* const result = ErrorSerializer.serialize(error);
* // Returns: { message: 'Failed to connect', name: 'Error', stack: '...' }
*
* // Serialize a plain object
* const obj = { userId: '123', action: 'login' };
* const result = ErrorSerializer.serialize(obj);
* // Returns: '{\n "userId": "123",\n "action": "login"\n}'
*
* // Serialize with options
* const result = ErrorSerializer.serialize(error, { includeStack: false });
* ```
*/
static serialize(e, t = {}) {
const {
includeStack: s = !0,
maxDepth: i = A.DEFAULT_MAX_DEPTH,
includeNonEnumerable: a = !1
} = t;
if (e instanceof Error) {
const o = this.getCacheKey(e, s, i, a);
if (o) {
const l = this.serializationCache.get(o);
if (l)
return l;
}
const c = this.serializeError(e, s, i, a, /* @__PURE__ */ new WeakSet());
return o && this.addToCache(o, c), c;
}
if (typeof e == "string")
return e;
const n = this.safeStringify(e, i, /* @__PURE__ */ new WeakSet());
if (typeof n == "string")
return n;
try {
return JSON.stringify(n, null, 2);
} catch {
return String(n);
}
}
/**
* Serialize a standard Error object
* @private
*/
static serializeError(e, t, s, i, a = /* @__PURE__ */ new WeakSet()) {
if (a.has(e))
return { message: e.message, name: e.name, [Symbol.for("circular")]: this.CIRCULAR_REF_PLACEHOLDER };
a.add(e);
const n = {
message: e.message,
name: e.name
};
return t && e.stack && (n.stack = e.stack), "cause" in e && e.cause !== void 0 && (e.cause instanceof Error ? n.cause = this.serializeError(e.cause, t, s - 1, i, a) : n.cause = this.safeStringify(e.cause, s - 1, a)), Object.keys(e).forEach((o) => {
if (!(o in n))
try {
const c = e;
n[o] = this.safeStringify(c[o], s - 1, a);
} catch {
n[o] = this.UNABLE_TO_SERIALIZE;
}
}), i && Object.getOwnPropertyNames(e).forEach((o) => {
if (!(o in n) && o !== "stack" && o !== "message" && o !== "name")
try {
const c = Object.getOwnPropertyDescriptor(e, o);
if (c && c.enumerable === !1) {
const l = e;
n[o] = this.safeStringify(l[o], s - 1, a);
}
} catch {
n[o] = this.UNABLE_TO_SERIALIZE;
}
}), n;
}
/**
* Safely stringify any object with circular reference detection
* @private
*/
static safeStringify(e, t, s = /* @__PURE__ */ 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 a = e.map((n) => this.safeStringify(n, t - 1, s));
return s.delete(e), a;
}
const i = {};
for (const [a, n] of Object.entries(e))
try {
i[a] = this.safeStringify(n, t - 1, s);
} catch {
i[a] = this.UNABLE_TO_SERIALIZE;
}
return s.delete(e), i;
} catch {
return s.delete(e), this.UNABLE_TO_SERIALIZE;
}
}
};
A.DEFAULT_MAX_DEPTH = 10, A.CIRCULAR_REF_PLACEHOLDER = "[Circular Reference]", A.MAX_DEPTH_PLACEHOLDER = "[Max Depth Reached]", A.UNABLE_TO_SERIALIZE = "[Unable to serialize]", A.serializationCache = /* @__PURE__ */ new Map(), A.MAX_CACHE_SIZE = 100;
let v = A;
const W = {
highlight: { color: g.LemonYellow, bold: !0 },
accent: { color: g.SkyBlue },
attention: { color: g.RoyalPurple, bold: !0 },
success: { color: g.ForestGreen },
warning: { color: g.TangerineOrange },
error: { color: g.FireRed },
info: { color: g.OceanBlue },
debug: { color: g.SlateGray }
}, K = {
success: "✅",
warning: "⚠️",
error: "❌",
info: "ℹ️",
debug: "🐞"
}, ie = {
trace: g.SlateGray,
// Gray for trace (lowest level)
debug: g.Cyan,
// Cyan for debug info
info: g.ForestGreen,
// Green for general info
warn: g.SunflowerYellow,
// Standard yellow for warnings
error: g.FireRed,
// Red for errors
fatal: g.FireRed,
// Bold + standard red for fatal errors
silent: g.Reset
// Reset for silent
}, M = class M {
/**
* Create a new LoggerStyleEngine
* @param config Configuration for the style engine
*/
constructor(e = {}) {
this.styleCache = /* @__PURE__ */ new Map();
const t = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
this.useColors = e.useColors !== void 0 ? e.useColors : !t, this.globalStyles = { ...W, ...e.styles }, this.globalIcons = { ...K, ...e.icons };
}
/**
* Public method to resolve a style definition and return the ANSI color code (with caching)
* @param style Style definition (semantic name or custom config)
* @returns ANSI color code or undefined if invalid
*/
resolveStyle(e, t = "test", s) {
return this.applyStyle(e, t, s);
}
/**
* Enable or disable colored output
* @param enabled Whether colors should be enabled
*/
setUseColors(e) {
this.useColors = e;
}
/**
* Check if colors are enabled
* @returns Whether colors are enabled
*/
getUseColors() {
return this.useColors;
}
/**
* Update style and icon configuration
* @param styles Partial style configuration to merge
* @param icons Partial icon configuration to merge
*/
updateStyleConfig(e, t) {
this.globalStyles = { ...this.globalStyles, ...e }, this.globalIcons = { ...this.globalIcons, ...t };
}
/**
* Format a log message with color and proper prefix
* @param level Log level for the message
* @param message The message to format
* @param loggerName The name of the logger
* @param context Optional context for the logger
* @param overrideColor Optional color override for the message
* @returns Formatted message with color
*/
formatMessage(e, t, s, i, a) {
const n = i ? `[${s}:${i}]` : `[${s}]`;
if (this.useColors) {
let o = this.getColorForLevel(e);
return a && (o = a), `${o}${n} ${t}${g.Reset}`;
} else
return `${n} ${t}`;
}
/**
* Format message with metadata-based styling and icons
* @param level Log level
* @param message Message to format
* @param loggerName The name of the logger
* @param styling Optional metadata for styling
* @param context Optional context for the logger
* @returns Formatted message with styling and icons
*/
formatMessageWithMetadata(e, t, s, i, a) {
let n = t, o = this.getColorForLevel(e);
if (i != null && i.style) {
const c = this.applyStyle(i.style, s, a);
c && (o = c);
}
return i != null && i.icon && (n = `${this.resolveIcon(i.icon, s, a)} ${t}`), this.formatMessage(e, n, s, a, o);
}
/**
* Parse metadata parameter to separate metadata from additional data
* @param metadataOrData Could be LogStyling or additional data
* @param data Additional data parameters
* @returns Parsed metadata and remaining data
*/
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 all but the last parameter
};
}
return {
metadata: void 0,
restData: s
};
}
/**
* Get color for a specific log level
* @param level The log level
* @returns ANSI color code for the log level
* @private
*/
getColorForLevel(e) {
const t = h[e], s = ie[t];
return e === h.fatal ? `${g.Bold}${s}` : s || g.Reset;
}
/**
* Apply style configuration and return ANSI color code
* @param style Style configuration (semantic name or custom config)
* @param loggerName Logger name for error messages
* @param context Optional context for error messages
* @returns ANSI color code or undefined if invalid
* @private
*/
applyStyle(e, t, s) {
const i = this.getStyleCacheValue(e);
if (i !== void 0) return i;
if (typeof e == "string") {
const a = this.getSemanticStyleColor(e, t, s);
return this.setStyleCache(e, a), a;
}
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 a = this.constructStyleCode(e);
return this.setStyleCache(e, a), a;
}
this.setStyleCache(e, void 0), this.logWarning("Unknown style configuration provided. Only semantic style names or valid ColorEnum objects are allowed.", t, s);
}
// Helper: Validate color
isValidColor(e) {
return Object.values(g).includes(e);
}
// Helper: Validate icon
isValidIcon(e) {
return this.isEmojiIcon(e);
}
// Helper: Memoization get/set
getStyleCacheValue(e) {
return this.styleCache.has(e) ? this.styleCache.get(e) : void 0;
}
setStyleCache(e, t) {
this.styleCache.set(e, t);
}
// Helper: Error logging
logWarning(e, t, s) {
const i = s ? `${t}:${s}` : t;
console.warn(`[${i}] ${e}`);
}
// Helper: Style code construction
constructStyleCode(e) {
let t = e.color.toString();
return e.bold && (t += g.Bold), e.underline && (t += g.Underline), t;
}
/**
* Get ANSI color code for semantic style names
* @param styleName Semantic style name
* @param loggerName Logger name for error messages
* @param context Optional context for error messages
* @returns ANSI color code or undefined if unknown
* @private
*/
getSemanticStyleColor(e, t, s) {
if (this.styleCache.has(e))
return this.styleCache.get(e);
const i = this.globalStyles[e];
if (i) {
let n = i.color.toString();
return i.bold && (n += g.Bold), i.underline && (n += g.Underline), this.styleCache.set(e, n), n;
}
const a = s ? `${t}:${s}` : t;
console.warn(
`[${a}] Unknown semantic style: ${e}. Falling back to default.`
), this.styleCache.set(e, void 0);
}
/**
* Expose style cache for diagnostics/testing
*/
getStyleCache() {
return this.styleCache;
}
/**
* Resolve icon to emoji character
* @param icon Icon name or custom string
* @param loggerName Logger name for error messages
* @param context Optional context for error messages
* @returns Emoji character
* @private
*/
resolveIcon(e, t, s) {
if (this.isValidIcon(e))
return e;
const i = [
"success",
"warning",
"error",
"info",
"debug"
], a = i.find((n) => n === e);
return a && this.globalIcons[a] ? this.globalIcons[a] : (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);
}
/**
* Check if the provided icon is a valid emoji from our Icon type
* @param icon Icon to check
* @returns True if it's a valid emoji icon
* @private
*/
isEmojiIcon(e) {
return [
"✅",
"⚠️",
"❌",
"ℹ️",
"🐞",
"⭐️",
"🚀",
"🔥",
"✔️",
"✖️",
"❓",
"🔒",
"🔓",
"⏳",
"🕒",
"⬆️",
"⬇️",
"➡️",
"⬅️",
"📁",
"📄",
"👤",
"👥",
"✏️",
"➕",
"➖",
"🔔",
"⚡️",
"🎁",
"🐛",
"🌟",
"❤️",
"👀",
"⚙️",
"🔧",
"🔨",
"🔑",
"🎉",
"📝",
"🚨",
"📅",
"💡",
"🔍",
"🔗",
"🔖",
"📌",
"📎",
"✉️",
"📞",
"🌍",
"☁️",
"🌈",
"🌙",
"☀️",
"❄️",
"✨",
"🎵",
"📷",
"🎥",
"🎤",
"🔊",
"🔋",
"🗑️",
"💰",
"💳",
"🎂",
"🏅",
"🏆",
"👑",
"🛸",
"🛡️",
"🛑",
"▶️",
"⏸️",
"⏺️",
"⏪",
"⏩",
"🔁",
"🔀",
"🎲",
"🎈",
"🍪",
"☕️",
"🍵",
"🍺",
"🍷",
"🍕",
"🍔",
"🍟",
"🍎",
"🍌",
"🍒",
"🍋",
"🥕",
"🌽",
"🥦",
"🥚",
"🧀",
"🍞",
"🍰",
"🍦",
"🍫",
"🍿",
"🥓",
"🍤",
"🐟",
"🦀",
"🐙",
"🐋",
"🐬",
"🐧",
"🐸",
"🐢",
"🐍",
"🐉",
"🦄",
"🐱",
"🐶",
"🐭",
"🐰",
"🐻",
"🐼",
"🐨",
"🐯",
"🦁",
"🐒",
"🐘",
"🐎",
"🐄",
"🐖",
"🐑",
"🐔",
"🦆",
"🦢",
"🦉",
"🦅",
"🦜",
"🦚",
"🦩",
"🦋",
"🐝",
"🐜",
"🐞",
"🕷️",
"🦂",
"🐌",
"🪱",
"🐛",
"🦗",
"🦟",
"🪰",
"🪳",
"🪲"
].includes(e);
}
};
M.INJECTABLE = !0;
let L = M;
class I extends Error {
constructor(e) {
super(e), this.name = "LoggerError";
}
}
const D = {
windowMs: 5e3,
// 5 seconds
flushOnCritical: !0
}, re = [h.error, h.fatal];
class ae {
constructor(e, t) {
this.config = e, this.formatMessage = t, this.entries = /* @__PURE__ */ new Map();
}
/**
* Add a message to the deduplicator
*/
addMessage(e, t, s = "") {
if (!this.config.enabled || this.config.flushOnCritical && re.includes(e))
return !0;
const i = this.generateFingerprint(t, e, s), a = this.entries.get(i);
return a ? a.count++ : this.entries.set(i, {
message: t,
level: e,
context: s,
firstSeen: Date.now(),
count: 1
}), this.scheduleFlush(), !1;
}
/**
* Flush all pending messages immediately
*/
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 the deduplicator and clean up resources
*/
destroy() {
this.flushTimer && (clearTimeout(this.flushTimer), this.flushTimer = void 0), this.entries.clear();
}
/**
* Generate a fingerprint for message deduplication
*/
generateFingerprint(e, t, s) {
return `${t}:${s}:${e}`;
}
/**
* Schedule a flush after the configured window
*/
scheduleFlush() {
this.flushTimer || (this.flushTimer = setTimeout(() => {
this.flush();
}, this.config.windowMs));
}
/**
* Output a batched message with count if needed
*/
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 h.trace:
console.trace(s);
break;
case h.debug:
console.debug(s);
break;
case h.info:
console.info(s);
break;
case h.warn:
console.warn(s);
break;
case h.error:
console.error(s);
break;
case h.fatal:
console.error(s);
break;
}
}
}
const B = class B {
/**
* Create a new LoggerService
* @param config The logger configuration
* @param styleEngine Optional style engine (will be created if not provided)
* @param context Optional context for child logger
* @param parent Optional parent logger for child logger
*/
constructor(e = {}, t, s, i) {
var a, n;
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(h).includes(e.level))
throw new I(`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 ?? ((a = process.env.LOG_DISABLED_CONTEXTS) == null ? void 0 : a.split(",")) ?? []
), this.styleEngine = t || new L({
useColors: e.useColors,
styles: { ...W, ...e.styles },
icons: { ...K, ...e.icons }
}), (n = e.deduplication) != null && n.enabled) {
const o = {
enabled: !0,
windowMs: e.deduplication.windowMs ?? D.windowMs,
flushOnCritical: e.deduplication.flushOnCritical ?? D.flushOnCritical
}, c = (l, u, p) => this.styleEngine.formatMessage(l, u, this.name, p);
this.deduplicator = new ae(o, c);
}
}
}
/**
* Check if this logger's context is enabled
* Always returns true for root logger
*/
isContextEnabled() {
return this.context ? !(this.parentLogger || this).disabledContexts.includes(this.context) : !0;
}
/**
* Set disabled contexts for this logger
* @param contexts Array of context names to disable
*/
setDisabledContexts(e) {
this.disabledContexts = e;
}
/**
* Get the current log level
* @returns The current log level
*/
getLogLevel() {
return this.logLevel;
}
/**
* Get disabled contexts
* @returns Array of disabled context names
*/
getDisabledContexts() {
return (this.parentLogger || this).disabledContexts || [];
}
/**
* Enable or disable colored output
* @param enabled Whether colors should be enabled
*/
setUseColors(e) {
(this.parentLogger || this).styleEngine.setUseColors(e);
}
/**
* Check if colors are enabled
* @returns Whether colors are enabled
*/
getUseColors() {
return (this.parentLogger || this).styleEngine.getUseColors();
}
/**
* Create a child logger with a specific context
* @param context The context for the child logger
* @returns A new logger instance with the given context
*/
forContext(e) {
return this.childLoggers[e] || (this.childLoggers[e] = new B({}, void 0, e, this)), this.childLoggers[e];
}
/**
* Start a new log group with the given name
* All subsequent log messages will be indented
* @param groupName The name of the group
*/
group(e) {
var i;
if (!this.isContextEnabled()) return;
(((i = this.parentLogger) == null ? void 0 : i.activeGroups) || this.activeGroups).push(e);
const s = this.styleEngine.formatMessage(
h.info,
`Group: ${e}`,
this.name,
this.context
);
console.group(`${s} ▼`);
}
/**
* End a log group with the given name
* Subsequent log messages will no longer be indented
* @param groupName The name of the group (optional, will end the most recent group if not provided)
*/
groupEnd(e) {
var s;
if (!this.isContextEnabled()) return;
const t = ((s = this.parentLogger) == null ? void 0 : s.activeGroups) || this.activeGroups;
if (e) {
const i = t.lastIndexOf(e);
if (i !== -1) {
const a = t.splice(i);
for (let n = 0; n < a.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 > h.trace) return;
const { metadata: i, restData: a } = this.styleEngine.parseMetadataParameter(
t,
s
);
if (this.handleDeduplication(h.trace, e, i, a))
if (i) {
const n = this.styleEngine.formatMessageWithMetadata(
h.trace,
e,
this.name,
i,
this.context
);
console.trace(n, ...a || []);
} else {
const n = this.styleEngine.formatMessage(
h.trace,
e,
this.name,
this.context
);
console.trace(n, ...a || []);
}
}
debug(e, t, ...s) {
if (!this.isContextEnabled() || this.logLevel > h.debug) return;
const { metadata: i, restData: a } = this.styleEngine.parseMetadataParameter(
t,
s
);
if (this.handleDeduplication(h.debug, e, i, a))
if (i) {
const n = this.styleEngine.formatMessageWithMetadata(
h.debug,
e,
this.name,
i,
this.context
);
console.debug(n, ...a || []);
} else {
const n = this.styleEngine.formatMessage(
h.debug,
e,
this.name,
this.context
);
console.debug(n, ...a || []);
}
}
info(e, t, ...s) {
if (!this.isContextEnabled() || this.logLevel > h.info) return;
const { metadata: i, restData: a } = this.styleEngine.parseMetadataParameter(
t,
s
);
if (this.handleDeduplication(h.info, e, i, a))
if (i) {
const n = this.styleEngine.formatMessageWithMetadata(
h.info,
e,
this.name,
i,
this.context
);
console.info(n, ...a || []);
} else {
const n = this.styleEngine.formatMessage(
h.info,
e,
this.name,
this.context
);
console.info(n, ...a || []);
}
}
warn(e, t, ...s) {
if (!this.isContextEnabled() || this.logLevel > h.warn) return;
const { metadata: i, restData: a } = this.styleEngine.parseMetadataParameter(
t,
s
);
if (this.handleDeduplication(h.warn, e, i, a))
if (i) {
const n = this.styleEngine.formatMessageWithMetadata(
h.warn,
e,
this.name,
i,
this.context
);
console.warn(n, ...a || []);
} else {
const n = this.styleEngine.formatMessage(
h.warn,
e,
this.name,
this.context
);
console.warn(n, ...a || []);
}
}
error(e, t, s, ...i) {
if (!this.isContextEnabled() || this.logLevel > h.error) return;
const { message: a, serializedError: n, context: o, data: c } = this.parseErrorParameters(
e,
t,
s,
i
), l = c || [];
let u, p = l;
if (l.length > 0) {
const w = l[l.length - 1];
w && typeof w == "object" && !Array.isArray(w) && ("style" in w || "icon" in w) && (u = w, p = l.slice(0, -1));
}
const S = [this.styleEngine.formatMessageWithMetadata(
h.error,
a,
this.name,
u,
this.context
)];
n && S.push(n), o && S.push(o), console.error(...S, ...p);
}
fatal(e, t, s, ...i) {
if (!this.isContextEnabled() || this.logLevel > h.fatal) return;
if (typeof e == "string" && t && typeof t == "object" && !Array.isArray(t) && ("style" in t || "icon" in t)) {
const w = t, Z = this.styleEngine.formatMessageWithMetadata(
h.fatal,
`FATAL: ${e}`,
this.name,
w,
this.context
);
console.error(Z);
return;
}
const { message: a, serializedError: n, context: o, data: c } = this.parseErrorParameters(
e,
t,
s,
i
), l = c || [];
let u, p = l;
if (l.length > 0) {
const w = l[l.length - 1];
w && typeof w == "object" && !Array.isArray(w) && ("style" in w || "icon" in w) && (u = w, p = l.slice(0, -1));
}
const S = [this.styleEngine.formatMessageWithMetadata(
h.fatal,
`FATAL: ${a}`,
this.name,
u,
this.context
)];
n && S.push(n), o && S.push(o), console.error(...S, ...p);
}
/**
* Parse and normalize error method parameters to support multiple overloads
*
* Handles various parameter combinations:
* - error(Error) -> extracts message and serializes error
* - error(message) -> uses message as-is
* - error(message, Error) -> uses message and serializes error
* - error(message, LogContext) -> uses message and context
* - error(message, Error, LogContext) -> uses all three with proper typing
* - error(message, ...data) -> backwards compatibility mode
*
* @private
* @param messageOrError - String message or Error object
* @param errorOrContext - Error object, LogContext, or additional data
* @param contextOrData - LogContext or additional data
* @param additionalData - Extra data for backwards compatibility
* @returns Parsed parameters with normalized structure
*/
parseErrorParameters(e, t, s, i = []) {
if (e instanceof Error && t === void 0)
return {
message: e.message,
serializedError: v.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: v.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: v.serialize(t),
context: s,
data: i
};
const a = typeof e == "string" ? e : "Unknown error", n = t ? v.serialize(t) : void 0, o = s !== void 0 ? [s, ...i] : i;
return { message: a, serializedError: n, data: o };
}
/**
* Type guard to check if an object is LogMetadata
*
* LogMetadata is defined as a plain object that is not:
* - null or undefined
* - An Array
* - An Error instance
* - A Date instance
* - A RegExp instance
* - A Function
*
* @private
* @param obj - Object to check
* @returns True if object matches LogMetadata interface
*/
isLogContext(e) {
return typeof e == "object" && e !== null && !Array.isArray(e) && !(e instanceof Error) && !(e instanceof Date) && !(e instanceof RegExp) && typeof e != "function";
}
/**
* Cast a string to a LogLevel, with runtime validation and warning for invalid levels
* @param level String representation of log level
* @returns Numeric LogLevel
* @private
*/
castLoglevel(e) {
if (F(e))
switch (e) {
case "trace":
return h.trace;
case "debug":
return h.debug;
case "info":
return h.info;
case "warn":
return h.warn;
case "error":
return h.error;
case "fatal":
return h.fatal;
case "silent":
return h.silent;
}
const t = e.toLowerCase();
throw F(t) ? new I(
`Invalid log level: ${e}. Log levels are case-sensitive. Valid levels: trace, debug, info, warn, error, fatal, silent.`
) : new I(
`Invalid log level: ${e}. Valid levels: trace, debug, info, warn, error, fatal, silent.`
);
}
/**
* Safely format a message using the style engine
* Catches and logs any errors from the style engine
* @param args Arguments for the style engine formatMessage method
* @returns The formatted message
* @private
*/
safeFormatMessage(e, t, s, i = "", a = "") {
try {
return this.styleEngine.formatMessage(e, t, s, i, a);
} catch (n) {
throw this.error("Style engine failure", n), new I("Style engine failure");
}
}
/**
* Handle deduplication for a log message
* @param level Log level
* @param message Message text
* @returns True if message should be logged immediately, false if batched
* @private
*/
shouldLogImmediately(e, t) {
const i = (this.parentLogger || this).deduplicator;
if (!i)
return !0;
try {
return i.addMessage(e, t, this.context);
} catch (a) {
return console.error("Logger deduplication error:", a), !0;
}
}
/**
* Deduplication handler for all log methods
* Returns true if the message should be logged immediately, false if batched
*/
handleDeduplication(e, t, s, i) {
return !s && i.length === 0 ? this.shouldLogImmediately(e, t) : !0;
}
};
B.INJECTABLE = !0;
let k = B, E = null;
function Y() {
return E || (E = new ne()), E;
}
class ne {
constructor() {
this.serviceMap = /* @__PURE__ */ new Map();
}
/**
* Register a service with a token
* @param token - The injection token for the service
* @param properties - The constructor parameters for the service class
*/
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.`
);
}
/**
* Get a service by its token
* @param token - The injection token for the service
* @returns The requested service or undefined if not found
*/
getService(e) {
if (this.isServiceInjectable(e))
return this.hasService(e) || this.register(e), this.serviceMap.get(this.getInjcectableName(e));
}
/**
* Check if a service is registered
* @param token - The injection token for the service
* @returns True if the service is registered
*/
hasService(e) {
return this.serviceMap.has(this.getInjcectableName(e));
}
/**
* Check if a service class is injectable
* @param token - The injection token for the service
* @returns True if the service has the INJECTABLE static property set to true
*/
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.`
);
}
/**
* Clean up all services when the game is being destroyed
*/
destroy() {
this.serviceMap.clear();
}
}
function oe(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 f(r, e = {}) {
return oe(Y(), r, e);
}
function J(r, ...e) {
Y().register(r, ...e);
}
const R = class R {
constructor(e) {
this.storageConfig = e, this.logger = f(k).forContext("SessionService");
}
async initSession(e) {
if (C(e))
this.logger.debug(
"Session already exists, skipping initialization"
);
else {
if (this.logger.info("Creating new session"), !this.store)
if (this.storageConfig.type === "redis") {
const s = { ...this.storageConfig.config };
if ("url" in s)
this.store = $(s);
else if ("host" in s) {
const i = {
...s,
port: typeof s.port == "string" ? parseInt(s.port, 10) : s.port
};
this.store = $(i);
} else
throw new Error("Invalid Redis configuration: must provide either url or host/port");
} else
this.store = Q();
await ee(e, {
store: this.store,
secret: this.storageConfig.config.sessionSecret || "change-me-in-production",
name: "auth.session",
maxAge: 3600 * 24,
// 24 hours
cookie: {
httpOnly: !0,
secure: process.env.NODE_ENV === "production",
sameSite: "lax"
// Always use 'lax' to allow OAuth redirects
},
// Initialize default session structure with auth property
generate: () => ({
auth: {
isAuthenticated: !1
}
})
});
}
}
/**
* Get a session by session ID
* @param sessionId The session ID
* @returns The session object or null if not found
*/
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;
}
}
/**
* Get all active sessions from storage
* @returns Array of session objects with update capability
*/
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.config.prefix && s.startsWith(`${this.storageConfig.config.prefix}:`) ? s.substring(`${this.storageConfig.config.prefix}:`.length) : s,
data: i,
update: (n) => {
const o = n(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);
const t = C(e);
t != null && t.auth && await m(e, (s) => {
const i = { ...s };
return delete i.auth, i;
}), await te(e);
} catch (t) {
throw this.logger.error("Session destruction failed", t), d({
statusCode: 500,
message: "Session handling failed"
});
}
}
async getSessionData(e, t) {
await this.initSession(e);
const s = C(e);
return this.logger.debug(
"Retrieved session data",
s
), (s == null ? void 0 : s[t]) || null;
}
async setSessionData(e, t, s) {
await this.initSession(e), await m(e, (i) => ({
...i,
[t]: s
}));
}
async isValidSession(e) {
return await this.initSession(e), !!C(e);
}
};
R.INJECTABLE = !0;
let T = R;
const _ = class _ {
constructor(e) {
this.openIDConfigCache = null, this.configLastFetched = null, this.CONFIG_CACHE_TTL = 36e5, this.TOKEN_REFRESH_SAFETY_MARGIN = 300, this.logger = f(k).forContext(
"OAuthAuthenticationService"
), J(T, e.sessionStorage), this.config = e;
}
// 5 minutes in seconds
/**
* Validate that the service has been properly initialized
* @throws Error if mandatory configuration is missing
*/
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."
);
}
// Rest of the code converted to class methods...
/**
* Initialize session for the request
*/
async initSession(e) {
return await f(T).initSession(e);
}
getConfig() {
return this.validateConfiguration(), this.config;
}
/**
* Safely access a configuration value
* @param key The configuration key to retrieve
* @param fallbackValue Optional fallback value if the config value doesn't exist
* @returns The configuration value or fallback value
* @throws Error if the configuration value doesn't exist and no fallback is provided
*/
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;
}
/**
* Check if the route is unprotected
* @param path The request path
* @returns True if the route is unprotected, false otherwise
*/
isUnprotectedRoute(e) {
const t = this.getConfigValue(
"unprotectedRoutes",
[]
);
return Array.isArray(t) && t.some((s) => e.startsWith(s));
}
/**
* Get OAuth authorization URL for login
*/
async getAuthorizationUrl(e, t) {
this.validateConfiguration();
const s = await this.getOpenIDConfiguration(), i = this.getConfigValue("audience", void 0), a = {
response_type: "code",
client_id: this.getConfigValue("clientId"),
redirect_uri: t || this.getConfigValue("callbackUri"),
scope: this.getConfigValue("scope"),
state: e,
...i ? { audience: i } : {}
}, n = new URLSearchParams(a);
return `${s.authorization_endpoint}?${n.toString()}`;
}
/**
* Exchange authorization code for tokens
*/
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 a = await i.json();
throw this.logger.error("Error exchanging code for tokens", a), d({
statusCode: 401,
message: "Failed to exchange authorization code"
});
}
return await i.json();
}
/**
* Refresh access token using refresh token
*/
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), d({
statusCode: 401,
message: "Failed to refresh token"
});
}
return await s.json();
} catch (s) {
throw this.logger.error("Error during token refresh", s), d({
statusCode: 401,
message: "Failed to refresh authentication token"
});
}
}
/**
* Get user info from OAuth provider with improved error handling and retry logic
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async getUserInfo(e, t = 3) {
const s = await this.getOpenIDConfiguration();
let i = null;
for (let a = 1; a <= t; a++)
try {
const n = await fetch(s.userinfo_endpoint, {
headers: {
Authorization: `Bearer ${e}`
},
// Add timeout to prevent hanging requests
signal: AbortSignal.timeout(1e4)
// 10 second timeout
});
if (!n.ok) {
const c = await n.json().catch(() => ({ error: "Unknown error" }));
if (this.logger.error("Error getting user info", c, {
attempt: a,
maxRetries: t
}), n.status === 401)
throw d({
statusCode: 401,
message: "Authentication token is invalid or expired"
});
if (n.status === 429) {
const l = parseInt(
n.headers.get("Retry-After") || "5",
10
);
await new Promise(
(u) => setTimeout(u, l * 1e3)
);
continue;
} else if (n.status >= 500) {
await new Promise((l) => setTimeout(l, a * 1e3));
continue;
} else
throw d({
statusCode: n.status,
message: `Failed to get user info: ${c.error || "Unknown error"}`
});
}
const o = await n.json();
if (!o || !o.sub && !o.id)
throw d({
statusCode: 500,
message: "Invalid user data received from provider"
});
return o;
} catch (n) {
if (i = n, n instanceof Error && "statusCode" in n && n.statusCode === 401)
throw n;
if ((n instanceof TypeError || n instanceof Error && n.name === "AbortError") && (this.logger.error("Network error fetching user info", n, {
attempt: a,
maxRetries: t
}), a < t)) {
await new Promise(
(o) => setTimeout(o, Math.pow(2, a) * 500)
);
continue;
}
if (a === t)
throw this.logger.error("Failed to get user info after multiple attempts", {
maxRetries: t
}), d({
statusCode: 500,
message: "Failed to get user info after multiple attempts",
cause: i
});
}
throw d({
statusCode: 500,
message: "Unexpected error getting user info"
});
}
/**
* Handle OAuth callback
*/
async handleCallback(e, t, s) {
var S;
if (!s)
throw d({
statusCode: 400,
message: "Invalid state parameter"
});
const i = await this.exchangeCodeForTokens(t), { access_token: a, id_token: n, refresh_token: o, expires_in: c } = i, l = await this.getUserInfo(a), u = this.getConfigValue("userHandler", void 0);
let p = l;
u && "createOrUpdateUser" in u && (p = await ((S = u.createOrUpdateUser) == null ? void 0 : S.call(u, l)));
const x = {
isAuthenticated: !0,
accessToken: a,
idToken: n,
refreshToken: o,
expiresAt: Date.now() + c * 1e3,
userInfo: l
};
return await m(e, () => ({ user: p, auth: x })), this.logger.debug("Authentication session data saved successfully"), { user: p, tokens: i };
}
/**
* Calculate if token needs refresh based on safety margin
* @param expiresAt Timestamp when token expires
* @returns True if token should be refreshed
*/
shouldRefreshToken(e) {
const t = Date.now(), s = this.TOKEN_REFRESH_SAFETY_MARGIN * 1e3;
return t + s > e;
}
/**
* Serverless-compatible method to refresh expiring tokens
* This should be called by a scheduled function/CRON job
* rather than using setInterval which doesn't work reliably in serverless
*/
async refreshExpiringTokens() {
var e;
this.logger.debug("Starting bulk token refresh process");
try {
const s = await f(T).getActiveSessions();
let i = 0, a = 0;
const n = s.length;
this.logger.debug(`Found ${n} active sessions to check`);
for (const c of s)
try {
if (!((e = c.data.auth) != null && e.isAuthenticated) || !c.data.auth.refreshToken || !c.data.auth.expiresAt) {
this.logger.debug(`Skipping session ${c.id} - no valid auth data`);
continue;
}
if (!this.shouldRefreshToken(c.data.auth.expiresAt)) {
this.logger.debug(`Skipping session ${c.id} - token not expiring soon`);
continue;
}
this.logger.debug(`Refreshing token for session ${c.id}`);
const l = await this.refreshTokens(c.data.auth.refreshToken);
c.update((u) => {
const p = u.auth;
return p ? {
...u,
auth: {
...p,
accessToken: l.access_token,
idToken: l.id_token || p.idToken,
refreshToken: l.refresh_token || p.refreshToken,
expiresAt: Date.now() + l.expires_in * 1e3
}
} : u;
}), await c.save(), i++, this.logger.debug(`Successfully refreshed token for session ${c.id}`);
} catch (l) {
a++, this.logger.error(
"Failed to refresh token for session",
l,
{ sessionId: c.id }
);
try {
c.update((u) => {
const p = u.auth;
return p ? {
...u,
auth: {
...p,
isAuthenticated: !1
}
} : u;
}), await c.save();
} catch (u) {
this.logger.error(
"Failed to update session after refresh failure",
u,
{ sessionId: c.id }
);
}
}
const o = { refreshed: i, failed: a, total: n };
return this.logger.info("Bulk token refresh completed", o), o;
} catch (t) {
throw this.logger.error("Error during bulk token refresh", t), d({
statusCode: 500,
message: "Failed to refresh expiring tokens"
});
}
}
/**
* Check if user is authenticated
*/
async isAuthenticated(e) {
await f(T).initSession(e);
const t = await C(e);
if (!(t != null && t.auth))
return await m(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 m(e, (i) => {
var a, n;
return {
auth: {
...i.auth,
isAuthenticated: !0,
accessToken: s.access_token,
idToken: s.id_token || ((a = t == null ? void 0 : t.auth) == null ? void 0 : a.idToken),
refreshToken: s.refresh_token || ((n = t == null ? void 0 : t.auth) == null ? void 0 : n.refreshToken),
expiresAt: