Last active
April 3, 2025 18:35
-
-
Save mikaelvesavuori/f838c02e4da491ce53368b52264330d2 to your computer and use it in GitHub Desktop.
Revisions
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 3 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1055,7 +1055,10 @@ var MikroMail = class { `Warning: No MX records found for recipient domain: ${recipient}` ); } console.log('BEFORE SENDING') const result = await this.smtpClient.sendEmail(emailOptions); console.log('EMAIL RESULT', result) console.log('AFTER SENDING') if (result.success) console.log(`Message ID: ${result.messageId}`); else console.error(`Failed to send email: ${result.error}`); await this.smtpClient.close(); -
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 1750 additions and 1044 deletions.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 69 additions and 68 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -465,6 +465,7 @@ var SMTPClient = class { retryDelay: config.retryDelay ?? 1e3, skipAuthentication: config.skipAuthentication || false }; console.log('SMTP CONFIG', this.config) this.socket = null; this.connected = false; this.lastCommand = ""; @@ -1013,7 +1014,7 @@ var MikroMail = class { // node_modules/mikroauth/lib/index.mjs var g = () => { const o = T(process.env.DEBUG) || false; return { auth: { jwtSecret: process.env.AUTH_JWT_SECRET || "your-jwt-secret", magicLinkExpirySeconds: 900, jwtExpirySeconds: 3600, refreshTokenExpirySeconds: 604800, maxActiveSessions: 3, appUrl: process.env.APP_URL || "http://localhost:3000", templates: null, debug: o }, email: { emailSubject: "Your Secure Login Link", user: process.env.EMAIL_USER || "", host: process.env.EMAIL_HOST || "", password: process.env.EMAIL_PASSWORD || "", port: 465, secure: true, maxRetries: 2, debug: o }, storage: { databaseDirectory: "mikroauth", encryptionKey: process.env.STORAGE_KEY || "", debug: o }, server: { port: Number(process.env.PORT) || 3e3, host: process.env.HOST || "0.0.0.0", useHttps: false, useHttp2: false, sslCert: "", sslKey: "", sslCa: "", rateLimit: { enabled: true, requestsPerMinute: 100 }, allowedDomains: ["*"], debug: o } }; }; function T(o) { @@ -1027,17 +1028,17 @@ var y = class { this.secret = e; } sign(e, t = {}) { const r = { alg: this.algorithm, typ: "JWT" }, i = Math.floor(Date.now() / 1e3), s = { ...e, iat: i }; t.exp !== void 0 && (s.exp = i + t.exp), t.notBefore !== void 0 && (s.nbf = i + t.notBefore), t.issuer && (s.iss = t.issuer), t.audience && (s.aud = t.audience), t.subject && (s.sub = t.subject), t.jwtid && (s.jti = t.jwtid); const a = this.base64UrlEncode(JSON.stringify(r)), d = this.base64UrlEncode(JSON.stringify(s)), l = `${a}.${d}`, c = this.createSignature(l); return `${l}.${c}`; } verify(e, t = {}) { const r = this.decode(e); if (r.header.alg !== this.algorithm) throw new Error(`Invalid algorithm. Expected ${this.algorithm}, got ${r.header.alg}`); const [i, s] = e.split("."), a = `${i}.${s}`; if (this.createSignature(a) !== r.signature) throw new Error("Invalid signature"); const l = r.payload, c = Math.floor(Date.now() / 1e3), u = t.clockTolerance || 0; if (l.exp !== void 0 && l.exp + u < c) throw new Error("Token expired"); if (l.nbf !== void 0 && l.nbf - u > c) throw new Error("Token not yet valid"); if (t.issuer && l.iss !== t.issuer) throw new Error("Invalid issuer"); @@ -1046,17 +1047,17 @@ var y = class { return l; } decode(e) { const t = e.split("."); if (t.length !== 3) throw new Error("Invalid token format"); try { const [r, i, s] = t, a = JSON.parse(this.base64UrlDecode(r)), d = JSON.parse(this.base64UrlDecode(i)); return { header: a, payload: d, signature: s }; } catch { throw new Error("Failed to decode token"); } } createSignature(e) { const t = f.createHmac("sha256", this.secret).update(e).digest(); return this.base64UrlEncode(t); } base64UrlEncode(e) { @@ -1199,65 +1200,65 @@ var m = class { clearInterval(this.expiryCheckInterval), this.data.clear(), this.collections.clear(), this.expiryEmitter.removeAllListeners(); } checkExpiredItems() { const e = Date.now(); for (const [t, r] of this.data.entries()) r.expiry && r.expiry < e && (this.data.delete(t), this.expiryEmitter.emit("expired", t)); for (const [t, r] of this.collections.entries()) r.expiry && r.expiry < e && (this.collections.delete(t), this.expiryEmitter.emit("expired", t)); } async set(e, t, r) { const i = r ? Date.now() + r * 1e3 : null; this.data.set(e, { value: t, expiry: i }); } async get(e) { const t = this.data.get(e); return t ? t.expiry && t.expiry < Date.now() ? (this.data.delete(e), null) : t.value : null; } async delete(e) { this.data.delete(e), this.collections.delete(e); } async addToCollection(e, t, r) { this.collections.has(e) || this.collections.set(e, { items: [], expiry: r ? Date.now() + r * 1e3 : null }); const i = this.collections.get(e); r && (i.expiry = Date.now() + r * 1e3), i.items.push(t); } async removeFromCollection(e, t) { const r = this.collections.get(e); r && (r.items = r.items.filter((i) => i !== t)); } async getCollection(e) { const t = this.collections.get(e); return t ? [...t.items] : []; } async getCollectionSize(e) { const t = this.collections.get(e); return t ? t.items.length : 0; } async removeOldestFromCollection(e) { const t = this.collections.get(e); return !t || t.items.length === 0 ? null : t.items.shift() || null; } async findKeys(e) { const t = e.replace(/\*/g, ".*").replace(/\?/g, "."), r = new RegExp(`^${t}$`), i = Array.from(this.data.keys()).filter((a) => r.test(a)), s = Array.from(this.collections.keys()).filter((a) => r.test(a)); return [.../* @__PURE__ */ new Set([...i, ...s])]; } }; function S(o) { if (!o || o.trim() === "" || (o.match(/@/g) || []).length !== 1) return false; const [t, r] = o.split("@"); return !(!t || !r || o.includes("..") || !I(t) || !$(r)); } function I(o) { return o.startsWith('"') && o.endsWith('"') ? !o.slice(1, -1).includes('"') : o.length > 64 || o.startsWith(".") || o.endsWith(".") ? false : /^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$/.test(o); } function $(o) { if (o.startsWith("[") && o.endsWith("]")) { const t = o.slice(1, -1); return t.startsWith("IPv6:") ? R(t.slice(5)) : M(t); } const e = o.split("."); if (e.length === 0) return false; for (const t of e) if (!t || t.length > 63 || !/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(t)) return false; if (e.length > 1) { const t = e[e.length - 1]; if (!/^[a-zA-Z]{2,}$/.test(t)) return false; } return true; @@ -1267,12 +1268,12 @@ function M(o) { } function R(o) { if (!/^[a-fA-F0-9:]+$/.test(o)) return false; const t = o.split(":"); return !(t.length < 2 || t.length > 8); } var n = g(); var P = (o) => { const e = { configFilePath: "mikroauth.config.json", args: process.argv, options: [{ flag: "--jwtSecret", path: "auth.jwtSecret", defaultValue: n.auth.jwtSecret }, { flag: "--magicLinkExpirySeconds", path: "auth.magicLinkExpirySeconds", defaultValue: n.auth.magicLinkExpirySeconds }, { flag: "--jwtExpirySeconds", path: "auth.jwtExpirySeconds", defaultValue: n.auth.jwtExpirySeconds }, { flag: "--refreshTokenExpirySeconds", path: "auth.refreshTokenExpirySeconds", defaultValue: n.auth.refreshTokenExpirySeconds }, { flag: "--maxActiveSessions", path: "auth.maxActiveSessions", defaultValue: n.auth.maxActiveSessions }, { flag: "--appUrl", path: "auth.appUrl", defaultValue: n.auth.appUrl }, { flag: "--debug", path: "auth.debug", isFlag: true, defaultValue: n.auth.debug }, { flag: "--emailSubject", path: "email.emailSubject", defaultValue: "Your Secure Login Link" }, { flag: "--emailHost", path: "email.host", defaultValue: n.email.host }, { flag: "--emailUser", path: "email.user", defaultValue: n.email.user }, { flag: "--emailPassword", path: "email.password", defaultValue: n.email.password }, { flag: "--emailPort", path: "email.port", defaultValue: n.email.port }, { flag: "--emailSecure", path: "email.secure", isFlag: true, defaultValue: n.email.secure }, { flag: "--emailMaxRetries", path: "email.maxRetries", defaultValue: n.email.maxRetries }, { flag: "--debug", path: "email.debug", isFlag: true, defaultValue: n.email.debug }, { flag: "--dir", path: "storage.databaseDirectory", defaultValue: n.storage.databaseDirectory }, { flag: "--encryptionKey", path: "storage.encryptionKey", defaultValue: n.storage.encryptionKey }, { flag: "--debug", path: "storage.debug", defaultValue: n.storage.debug }, { flag: "--port", path: "server.port", defaultValue: n.server.port }, { flag: "--host", path: "server.host", defaultValue: n.server.host }, { flag: "--https", path: "server.useHttps", isFlag: true, defaultValue: n.server.useHttps }, { flag: "--https", path: "server.useHttp2", isFlag: true, defaultValue: n.server.useHttp2 }, { flag: "--cert", path: "server.sslCert", defaultValue: n.server.sslCert }, { flag: "--key", path: "server.sslKey", defaultValue: n.server.sslKey }, { flag: "--ca", path: "server.sslCa", defaultValue: n.server.sslCa }, { flag: "--ratelimit", path: "server.rateLimit.enabled", defaultValue: n.server.rateLimit.enabled, isFlag: true }, { flag: "--rps", path: "server.rateLimit.requestsPerMinute", defaultValue: n.server.rateLimit.requestsPerMinute }, { flag: "--allowed", path: "server.allowedDomains", defaultValue: n.server.allowedDomains, parser: parsers.array }, { flag: "--debug", path: "server.debug", isFlag: true, defaultValue: n.server.debug }] }; return o && (e.config = o), e; }; var v = { linkSent: "If a matching account was found, a magic link has been sent.", revokedSuccess: "All other sessions revoked successfully.", logoutSuccess: "Logged out successfully." }; @@ -1283,14 +1284,14 @@ var E = class { jwtService; templates; constructor(e, t, r) { const i = new MikroConf(P({ auth: e.auth, email: e.email })).get(); i.auth.debug && console.log("Using configuration:", i), this.config = i, this.email = t || new h(), this.storage = r || new m(), this.jwtService = new y(i.auth.jwtSecret), this.templates = new w(i?.auth.templates), this.checkIfUsingDefaultCredentialsInProduction(); } checkIfUsingDefaultCredentialsInProduction() { process.env.NODE_ENV === "production" && this.config.auth.jwtSecret === g().auth.jwtSecret && (console.error("WARNING: Using default secrets in production environment!"), process.exit(1)); } generateToken(e) { const t = Date.now().toString(), r = f.randomBytes(32).toString("hex"); return f.createHash("sha256").update(`${e}:${t}:${r}`).digest("hex"); } generateJsonWebToken(e) { @@ -1300,65 +1301,65 @@ var E = class { return f.randomBytes(40).toString("hex"); } async trackSession(e, t, r) { const i = `sessions:${e}`; if (await this.storage.getCollectionSize(i) >= this.config.auth.maxActiveSessions) { const a = await this.storage.removeOldestFromCollection(i); a && await this.storage.delete(`refresh:${a}`); } await this.storage.addToCollection(i, t, this.config.auth.refreshTokenExpirySeconds), await this.storage.set(`refresh:${t}`, JSON.stringify(r), this.config.auth.refreshTokenExpirySeconds); } generateMagicLinkUrl(e) { const { token: t, email: r } = e; try { return new URL(this.config.auth.appUrl), `${this.config.auth.appUrl}?token=${encodeURIComponent(t)}&email=${encodeURIComponent(r)}`; } catch { throw new Error("Invalid base URL configuration"); } } async createMagicLink(e) { const { email: t, ip: r } = e; if (!S(t)) throw new Error("Valid email required"); try { const i = this.generateToken(t), s = `magic_link:${i}`, a = { email: t, ipAddress: r || "unknown", createdAt: Date.now() }; await this.storage.set(s, JSON.stringify(a), this.config.auth.magicLinkExpirySeconds); const d = await this.storage.findKeys("magic_link:*"); for (const u of d) { if (u === s) continue; const p = await this.storage.get(u); if (p) try { JSON.parse(p).email === t && await this.storage.delete(u); } catch { } } console.log('this.config.email',this.config.email) const l = this.generateMagicLinkUrl({ token: i, email: t }), c = Math.ceil(this.config.auth.magicLinkExpirySeconds / 60); return await this.email.sendMail({ from: this.config.email.user, to: t, subject: this.config.email.emailSubject, text: this.templates.getText(l, c), html: this.templates.getHtml(l, c) }), { message: v.linkSent }; } catch (i) { throw console.error(`Failed to process magic link request: ${i}`), new Error("Failed to process magic link request"); } } async verifyToken(e) { const { token: t, email: r } = e; try { const i = `magic_link:${t}`, s = await this.storage.get(i); if (!s) throw new Error("Invalid or expired token"); const a = JSON.parse(s); if (a.email !== r) throw new Error("Email mismatch"); const d = a.username, l = a.role; await this.storage.delete(i); const c = f.randomBytes(16).toString("hex"), u = this.generateRefreshToken(), p = { sub: r, username: d, role: l, jti: c, lastLogin: a.createdAt, metadata: { ip: a.ipAddress }, exp: Math.floor(Date.now() / 1e3) + 60 * 60 * 24 }, b = this.jwtService.sign(p, { exp: this.config.auth.jwtExpirySeconds }); return await this.trackSession(r, u, { ...a, tokenId: c, createdAt: Date.now() }), { accessToken: b, refreshToken: u, exp: this.config.auth.jwtExpirySeconds, tokenType: "Bearer" }; } catch (i) { throw console.error("Token verification error:", i), new Error("Verification failed"); } } async refreshAccessToken(e) { try { const t = await this.storage.get(`refresh:${e}`); if (!t) throw new Error("Invalid or expired refresh token"); const r = JSON.parse(t), i = r.email; if (!i) throw new Error("Invalid refresh token data"); const s = r.username, a = r.role, d = f.randomBytes(16).toString("hex"), l = { sub: i, username: s, role: a, jti: d, lastLogin: r.lastLogin || r.createdAt, metadata: { ip: r.ipAddress } }, c = this.jwtService.sign(l, { exp: this.config.auth.jwtExpirySeconds }); return r.lastUsed = Date.now(), await this.storage.set(`refresh:${e}`, JSON.stringify(r), this.config.auth.refreshTokenExpirySeconds), { accessToken: c, refreshToken: e, exp: this.config.auth.jwtExpirySeconds, tokenType: "Bearer" }; } catch (t) { throw console.error("Token refresh error:", t), new Error("Token refresh failed"); @@ -1374,12 +1375,12 @@ var E = class { async logout(e) { try { if (!e || typeof e != "string") throw new Error("Refresh token is required"); const t = await this.storage.get(`refresh:${e}`); if (!t) return { message: v.logoutSuccess }; const i = JSON.parse(t).email; if (!i) throw new Error("Invalid refresh token data"); await this.storage.delete(`refresh:${e}`); const s = `sessions:${i}`; return await this.storage.removeFromCollection(s, e), { message: v.logoutSuccess }; } catch (t) { throw console.error("Logout error:", t), new Error("Logout failed"); @@ -1388,11 +1389,11 @@ var E = class { async getSessions(e) { try { if (!e.user?.email) throw new Error("User not authenticated"); const t = e.user.email, r = e.body?.refreshToken, i = `sessions:${t}`, a = (await this.storage.getCollection(i)).map(async (l) => { try { const c = await this.storage.get(`refresh:${l}`); if (!c) return await this.storage.removeFromCollection(i, l), null; const u = JSON.parse(c); return { id: `${l.substring(0, 8)}...`, createdAt: u.createdAt || 0, lastLogin: u.lastLogin || u.createdAt || 0, lastUsed: u.lastUsed || u.createdAt || 0, metadata: { ip: u.ipAddress }, isCurrentSession: l === r }; } catch { return await this.storage.removeFromCollection(i, l), null; @@ -1406,20 +1407,20 @@ var E = class { async revokeSessions(e) { try { if (!e.user?.email) throw new Error("User not authenticated"); const t = e.user.email, r = e.body?.refreshToken, i = `sessions:${t}`, s = await this.storage.getCollection(i); for (const a of s) r && a === r || await this.storage.delete(`refresh:${a}`); return await this.storage.delete(i), r && await this.storage.get(`refresh:${r}`) && await this.storage.addToCollection(i, r, this.config.auth.refreshTokenExpirySeconds), { message: v.revokedSuccess }; } catch (t) { throw console.error("Revoke sessions error:", t), new Error("Failed to revoke sessions"); } } authenticate(e, t) { try { const r = e.headers?.authorization; if (!r || !r.startsWith("Bearer ")) throw new Error("Authentication required"); const i = r.split(" ")[1]; try { const s = this.verify(i); e.user = { email: s.sub }, t(); } catch { throw new Error("Invalid or expired token"); @@ -1444,48 +1445,48 @@ var x = class { await this.db.close(); } async set(e, t, r) { const i = `${this.PREFIX_KV}${e}`, s = r ? Date.now() + r * 1e3 : void 0; await this.db.write({ tableName: this.TABLE_NAME, key: i, value: t, expiration: s }); } async get(e) { const t = `${this.PREFIX_KV}${e}`, r = await this.db.get({ tableName: this.TABLE_NAME, key: t }); return r || null; } async delete(e) { const t = `${this.PREFIX_KV}${e}`; await this.db.delete({ tableName: this.TABLE_NAME, key: t }); } async addToCollection(e, t, r) { let i = `${this.PREFIX_COLLECTION}${e}`, s = await this.db.get({ tableName: this.TABLE_NAME, key: i }), a = []; s && (a = JSON.parse(s)), a.includes(t) || a.push(t); const d = r ? Date.now() + r * 1e3 : void 0; await this.db.write({ tableName: this.TABLE_NAME, key: i, value: JSON.stringify(a), expiration: d }); } async removeFromCollection(e, t) { const r = `${this.PREFIX_COLLECTION}${e}`, i = await this.db.get({ tableName: this.TABLE_NAME, key: r }); if (!i) return; let s = JSON.parse(i); s = s.filter((a) => a !== t), await this.db.write({ tableName: this.TABLE_NAME, key: r, value: JSON.stringify(s) }); } async getCollection(e) { const t = `${this.PREFIX_COLLECTION}${e}`, r = await this.db.get({ tableName: this.TABLE_NAME, key: t }); return r ? JSON.parse(r) : []; } async getCollectionSize(e) { return (await this.getCollection(e)).length; } async removeOldestFromCollection(e) { const t = `${this.PREFIX_COLLECTION}${e}`, r = await this.db.get({ tableName: this.TABLE_NAME, key: t }); if (!r) return null; const i = JSON.parse(r); if (i.length === 0) return null; const s = i.shift(); return await this.db.write({ tableName: this.TABLE_NAME, key: t, value: JSON.stringify(i) }), s; } async findKeys(e) { const t = e.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, "."), r = new RegExp(`^${t}$`); return (await this.db.get({ tableName: this.TABLE_NAME })).filter((s) => { const a = s[0]; return typeof a == "string" && a.startsWith(this.PREFIX_KV); }).map((s) => s[0].substring(this.PREFIX_KV.length)).filter((s) => r.test(s)); } -
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 5770 additions and 36 deletions.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 36 additions and 6492 deletions.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 4 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1676,6 +1676,10 @@ var E = class { console.log('result', l); console.log(666); console.log('THIS CONFIG', this.config); console.log('DEFAULTS', g()); return ( await this.email.sendMail({ from: process.env.EMAIL_FROM || g().email.user, -
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1642,8 +1642,8 @@ var E = class { async createMagicLink(e) { console.log('createMagicLink'); const { email: t, ip: r } = e; console.log('email', t); console.log('ip', r); if (!S(t)) throw new Error('Valid email required'); try { console.log(111); -
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 6488 additions and 36 deletions.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . 1 changed file with 24 additions and 24 deletions.There are no files selected for viewing
-
mikaelvesavuori revised this gist
Apr 3, 2025 . No changes.There are no files selected for viewing
-
mikaelvesavuori created this gist
Mar 29, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,123 @@ // MikroChat - See LICENSE file for copyright and license details. var V=class{db;PREFIX_KV="kv:";PREFIX_COLLECTION="coll:";TABLE_NAME="mikroauth";constructor(e){this.db=e}async start(){await this.db.start()}async close(){await this.db.close()}async set(e,t,r){let s=`${this.PREFIX_KV}${e}`,n=r?Date.now()+r*1e3:void 0;await this.db.write({tableName:this.TABLE_NAME,key:s,value:t,expiration:n})}async get(e){let t=`${this.PREFIX_KV}${e}`,r=await this.db.get({tableName:this.TABLE_NAME,key:t});return r||null}async delete(e){let t=`${this.PREFIX_KV}${e}`;await this.db.delete({tableName:this.TABLE_NAME,key:t})}async addToCollection(e,t,r){let s=`${this.PREFIX_COLLECTION}${e}`;console.log(`Adding to collection | Key: "${s}" | Item: "${t}" |`);let n=await this.db.get({tableName:this.TABLE_NAME,key:s}),i=[];n&&(i=JSON.parse(n)),i.includes(t)||i.push(t);let o=r?Date.now()+r*1e3:void 0;await this.db.write({tableName:this.TABLE_NAME,key:s,value:JSON.stringify(i),expiration:o})}async removeFromCollection(e,t){let r=`${this.PREFIX_COLLECTION}${e}`;console.log("Removing from collection",r);let s=await this.db.get({tableName:this.TABLE_NAME,key:r});if(!s)return;let n=JSON.parse(s);n=n.filter(i=>i!==t),await this.db.write({tableName:this.TABLE_NAME,key:r,value:JSON.stringify(n)})}async getCollection(e){let t=`${this.PREFIX_COLLECTION}${e}`;console.log("Getting collection",t);let r=await this.db.get({tableName:this.TABLE_NAME,key:t});return r?JSON.parse(r):[]}async getCollectionSize(e){let t=await this.getCollection(e);return console.log(`The collection size of "${e}" is ${t.length}`),t.length}async removeOldestFromCollection(e){let t=`${this.PREFIX_COLLECTION}${e}`,r=await this.db.get({tableName:this.TABLE_NAME,key:t});if(!r)return null;let s=JSON.parse(r);if(s.length===0)return null;let n=s.shift();return await this.db.write({tableName:this.TABLE_NAME,key:t,value:JSON.stringify(s)}),n}async findKeys(e){let t=e.replace(/\./g,"\\.").replace(/\*/g,".*").replace(/\?/g,"."),r=new RegExp(`^${t}$`);return(await this.db.get({tableName:this.TABLE_NAME})).filter(n=>{let i=n[0];return typeof i=="string"&&i.startsWith(this.PREFIX_KV)}).map(n=>n[0].substring(this.PREFIX_KV.length)).filter(n=>r.test(n))}};var ee=class extends Error{constructor(e){super(),this.name="ValidationError",this.message=e,this.cause={statusCode:400}}};import{existsSync as Ne,readFileSync as je}from"node:fs";var te=class{config;defaults={configFilePath:"mikromail.config.json",args:[]};constructor(e){let t=e?.config||{},r=e?.configFilePath||this.defaults.configFilePath,s=e?.args||this.defaults.args;this.config=this.create(r,s,t)}create(e,t,r){let s={host:"",user:"",password:"",port:465,secure:!0,debug:!1,maxRetries:2},n={};if(Ne(e))try{let o=je(e,"utf8");n=JSON.parse(o),console.log(`Loaded configuration from ${e}`)}catch(o){console.error(`Error reading config file: ${o instanceof Error?o.message:String(o)}`)}let i=this.parseCliArgs(t);return{...s,...r,...n,...i}}parseCliArgs(e){let t={};for(let r=2;r<e.length;r++)switch(e[r]){case"--host":r+1<e.length&&(t.host=e[++r]);break;case"--user":r+1<e.length&&(t.user=e[++r]);break;case"--password":r+1<e.length&&(t.password=e[++r]);break;case"--port":if(r+1<e.length){let n=Number.parseInt(e[++r],10);Number.isNaN(n)||(t.port=n)}break;case"--secure":t.secure=!0;break;case"--debug":t.debug=!0;break;case"--retries":if(r+1<e.length){let n=Number.parseInt(e[++r],10);Number.isNaN(n)||(t.maxRetries=n)}break}return t}validate(){if(!this.config.host)throw new ee("Host value not found")}get(){return this.validate(),this.config}};import{promises as Re}from"node:dns";function C(e){try{let[t,r]=e.split("@");if(!t||t.length>64||t.startsWith(".")||t.endsWith(".")||t.includes("..")||!/^[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~.]+$/.test(t)||!r||r.length>255)return!1;if(r.startsWith("[")&&r.endsWith("]")){let n=r.slice(1,-1);return n.startsWith("IPv6:")?!0:/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/.test(n)}if(r.startsWith(".")||r.endsWith(".")||r.includes(".."))return!1;let s=r.split(".");if(s.length<2||s[s.length-1].length<2)return!1;for(let n of s)if(!n||n.length>63||!/^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?$/.test(n))return!1;return!0}catch{return!1}}async function Ve(e){try{let t=await Re.resolveMx(e);return!!t&&t.length>0}catch{return!1}}async function re(e){try{let t=e.split("@")[1];return t?await Ve(t):!1}catch{return!1}}import{Buffer as k}from"node:buffer";import F from"node:crypto";import Fe from"node:net";import He from"node:os";import se from"node:tls";var ie=class{config;socket;connected;lastCommand;serverCapabilities;secureMode;retryCount;maxEmailSize=10485760;constructor(e){this.config={host:e.host,user:e.user,password:e.password,port:e.port??(e.secure?465:587),secure:e.secure??!0,debug:e.debug??!1,timeout:e.timeout??1e4,clientName:e.clientName??He.hostname(),maxRetries:e.maxRetries??3,retryDelay:e.retryDelay??1e3,skipAuthentication:e.skipAuthentication||!1},this.socket=null,this.connected=!1,this.lastCommand="",this.serverCapabilities=[],this.secureMode=this.config.secure,this.retryCount=0}log(e,t=!1){this.config.debug&&console.log(`${t?"SMTP ERROR: ":"SMTP: "}${e}`)}async connect(){return new Promise((e,t)=>{let r=setTimeout(()=>{t(new Error(`Connection timeout after ${this.config.timeout}ms`)),this.socket?.destroy()},this.config.timeout);try{this.config.secure?this.createTLSConnection(r,e,t):this.createPlainConnection(r,e,t)}catch(s){clearTimeout(r),this.log(`Failed to create socket: ${s.message}`,!0),t(s)}})}createTLSConnection(e,t,r){this.socket=se.connect({host:this.config.host,port:this.config.port,rejectUnauthorized:!0,minVersion:"TLSv1.2",ciphers:"HIGH:!aNULL:!MD5:!RC4"}),this.setupSocketEventHandlers(e,t,r)}createPlainConnection(e,t,r){this.socket=Fe.createConnection({host:this.config.host,port:this.config.port}),this.setupSocketEventHandlers(e,t,r)}setupSocketEventHandlers(e,t,r){this.socket&&(this.socket.once("error",s=>{clearTimeout(e),this.log(`Connection error: ${s.message}`,!0),r(new Error(`SMTP connection error: ${s.message}`))}),this.socket.once("connect",()=>{this.log("Connected to SMTP server"),clearTimeout(e),this.socket.once("data",s=>{let n=s.toString().trim();this.log(`Server greeting: ${n}`),n.startsWith("220")?(this.connected=!0,this.secureMode=this.config.secure,t()):(r(new Error(`Unexpected server greeting: ${n}`)),this.socket.destroy())})}),this.socket.once("close",s=>{this.connected?this.log(`Connection closed${s?" with error":""}`):(clearTimeout(e),r(new Error("Connection closed before initialization completed"))),this.connected=!1}))}async upgradeToTLS(){if(!(!this.socket||this.secureMode))return new Promise((e,t)=>{let s={socket:this.socket,host:this.config.host,rejectUnauthorized:!0,minVersion:"TLSv1.2",ciphers:"HIGH:!aNULL:!MD5:!RC4"},n=se.connect(s);n.once("error",i=>{this.log(`TLS upgrade error: ${i.message}`,!0),t(new Error(`STARTTLS error: ${i.message}`))}),n.once("secureConnect",()=>{this.log("Connection upgraded to TLS"),n.authorized?(this.socket=n,this.secureMode=!0,e()):t(new Error(`TLS certificate verification failed: ${n.authorizationError}`))})})}async sendCommand(e,t,r=this.config.timeout){if(!this.socket||!this.connected)throw new Error("Not connected to SMTP server");return new Promise((s,n)=>{let i=setTimeout(()=>{this.socket?.removeListener("data",a),n(new Error(`Command timeout after ${r}ms: ${e}`))},r),o="",a=c=>{o+=c.toString();let l=o.split(`\r `);if(l.length>0&&l[l.length-1]===""){let u=l[l.length-2]||"",h=/^(\d{3})(.?)/.exec(u);h?.[1]&&h[2]!=="-"&&(this.socket?.removeListener("data",a),clearTimeout(i),this.log(`SMTP Response: ${o.trim()}`),h[1]===t.toString()?s(o.trim()):n(new Error(`SMTP Error: ${o.trim()}`)))}};this.socket.on("data",a),e.startsWith("AUTH PLAIN")||e.startsWith("AUTH LOGIN")||this.lastCommand==="AUTH LOGIN"&&!e.startsWith("AUTH")?this.log("SMTP Command: [Credentials hidden]"):this.log(`SMTP Command: ${e}`),this.lastCommand=e,this.socket.write(`${e}\r `)})}parseCapabilities(e){let t=e.split(`\r `);this.serverCapabilities=[];for(let r=1;r<t.length;r++){let s=t[r];if(s.match(/^\d{3}/)&&s.charAt(3)===" "){let n=s.substr(4).toUpperCase();this.serverCapabilities.push(n)}}this.log(`Server capabilities: ${this.serverCapabilities.join(", ")}`)}getBestAuthMethod(){if(this.serverCapabilities.map(t=>t.split(" ")[0]).includes("AUTH")){let t=this.serverCapabilities.find(r=>r.startsWith("AUTH "));if(t){let r=t.split(" ").slice(1);if(r.includes("CRAM-MD5"))return"CRAM-MD5";if(r.includes("LOGIN"))return"LOGIN";if(r.includes("PLAIN"))return"PLAIN"}}return"PLAIN"}async authenticate(){switch(this.getBestAuthMethod()){case"CRAM-MD5":await this.authenticateCramMD5();break;case"LOGIN":await this.authenticateLogin();break;default:await this.authenticatePlain();break}}async authenticatePlain(){let e=k.from(`\0${this.config.user}\0${this.config.password}`).toString("base64");await this.sendCommand(`AUTH PLAIN ${e}`,235)}async authenticateLogin(){await this.sendCommand("AUTH LOGIN",334),await this.sendCommand(k.from(this.config.user).toString("base64"),334),await this.sendCommand(k.from(this.config.password).toString("base64"),235)}async authenticateCramMD5(){let e=await this.sendCommand("AUTH CRAM-MD5",334),t=k.from(e.substr(4),"base64").toString("utf8"),r=F.createHmac("md5",this.config.password);r.update(t);let s=r.digest("hex"),n=`${this.config.user} ${s}`,i=k.from(n).toString("base64");await this.sendCommand(i,235)}generateMessageId(){let e=F.randomBytes(16).toString("hex"),t=this.config.user.split("@")[1]||"localhost";return`<${e}@${t}>`}generateBoundary(){return`----=_NextPart_${F.randomBytes(12).toString("hex")}`}encodeHeaderValue(e){return/^[\x00-\x7F]*$/.test(e)?e:`=?UTF-8?Q?${e.replace(/[^\x00-\x7F]/g,t=>{let r=t.charCodeAt(0).toString(16).toUpperCase();return`=${r.length<2?`0${r}`:r}`})}?=`}sanitizeHeader(e){let t=e.replace(/[\r\n\t]+/g," ").replace(/\s{2,}/g," ").trim();return this.encodeHeaderValue(t)}createEmailHeaders(e){let t=this.generateMessageId(),r=new Date().toUTCString(),s=e.from||this.config.user,n=Array.isArray(e.to)?e.to.join(", "):e.to,i=[`From: ${this.sanitizeHeader(s)}`,`To: ${this.sanitizeHeader(n)}`,`Subject: ${this.sanitizeHeader(e.subject)}`,`Message-ID: ${t}`,`Date: ${r}`,"MIME-Version: 1.0"];if(e.cc){let o=Array.isArray(e.cc)?e.cc.join(", "):e.cc;i.push(`Cc: ${this.sanitizeHeader(o)}`)}if(e.replyTo&&i.push(`Reply-To: ${this.sanitizeHeader(e.replyTo)}`),e.headers)for(let[o,a]of Object.entries(e.headers))/^[a-zA-Z0-9-]+$/.test(o)&&(/^(from|to|cc|bcc|subject|date|message-id)$/i.test(o)||i.push(`${o}: ${this.sanitizeHeader(a)}`));return i}createMultipartEmail(e){let{text:t,html:r}=e,s=this.createEmailHeaders(e),n=this.generateBoundary();return r&&t?(s.push(`Content-Type: multipart/alternative; boundary="${n}"`),`${s.join(`\r `)}\r \r --${n}\r Content-Type: text/plain; charset=utf-8\r \r ${t||""}\r \r --${n}\r Content-Type: text/html; charset=utf-8\r \r ${r||""}\r \r --${n}--\r `):(s.push("Content-Type: text/html; charset=utf-8"),r?`${s.join(`\r `)}\r \r ${r}`:`${s.join(`\r `)}\r \r ${t||""}`)}async smtpHandshake(){let e=await this.sendCommand(`EHLO ${this.config.clientName}`,250);if(this.parseCapabilities(e),!this.secureMode&&this.serverCapabilities.includes("STARTTLS")){await this.sendCommand("STARTTLS",220),await this.upgradeToTLS();let t=await this.sendCommand(`EHLO ${this.config.clientName}`,250);this.parseCapabilities(t)}this.config.skipAuthentication?this.log("Authentication skipped (testing mode)"):await this.authenticate()}async sendEmail(e){let t=e.from||this.config.user,{to:r,subject:s}=e,n=e.text||"",i=e.html||"";if(!t||!r||!s||!n&&!i)return{success:!1,error:"Missing required email parameters (from, to, subject, and either text or html)"};if(!C(t))return{success:!1,error:"Invalid email address format"};let o=Array.isArray(e.to)?e.to:[e.to];for(let a of o)if(!C(a))return{success:!1,error:`Invalid recipient email address format: ${a}`};for(this.retryCount=0;this.retryCount<=this.config.maxRetries;this.retryCount++)try{this.retryCount>0&&(this.log(`Retrying email send (attempt ${this.retryCount} of ${this.config.maxRetries})...`),await new Promise(u=>setTimeout(u,this.config.retryDelay))),this.connected||(await this.connect(),await this.smtpHandshake()),await this.sendCommand(`MAIL FROM:<${t}>`,250);for(let u of o)await this.sendCommand(`RCPT TO:<${u}>`,250);if(e.cc){let u=Array.isArray(e.cc)?e.cc:[e.cc];for(let h of u)C(h)&&await this.sendCommand(`RCPT TO:<${h}>`,250)}if(e.bcc){let u=Array.isArray(e.bcc)?e.bcc:[e.bcc];for(let h of u)C(h)&&await this.sendCommand(`RCPT TO:<${h}>`,250)}await this.sendCommand("DATA",354);let a=this.createMultipartEmail(e);if(a.length>this.maxEmailSize)return{success:!1,error:"Email size exceeds maximum allowed"};await this.sendCommand(`${a}\r .`,250);let c=/Message-ID: (.*)/i.exec(a);return{success:!0,messageId:c?c[1].trim():void 0,message:"Email sent successfully"}}catch(a){let c=a.message;if(this.log(`Error sending email: ${c}`,!0),c.includes("5.")||c.includes("Authentication failed")||c.includes("certificate")||this.retryCount>=this.config.maxRetries)return{success:!1,error:c};try{this.connected&&await this.sendCommand("RSET",250)}catch{}this.socket?.end(),this.connected=!1}return{success:!1,error:"Maximum retry count exceeded"}}async close(){try{this.connected&&await this.sendCommand("QUIT",221)}catch(e){this.log(`Error during QUIT: ${e.message}`,!0)}finally{this.socket&&(this.socket.destroy(),this.socket=null,this.connected=!1)}}};var H=class{smtpClient;constructor(e){let t=new te(e).get(),r=new ie(t);this.smtpClient=r}async send(e){try{let t=Array.isArray(e.to)?e.to:[e.to];for(let s of t)await re(s)||console.error(`Warning: No MX records found for recipient domain: ${s}`);let r=await this.smtpClient.sendEmail(e);r.success?console.log(`Message ID: ${r.messageId}`):console.error(`Failed to send email: ${r.error}`),await this.smtpClient.close()}catch(t){console.error("Error in email sending process:",t.message)}}};var O=class{email;sender;constructor(e){this.sender=e.user,this.email=new H({config:e})}async sendMail(e){await this.email.send({from:this.sender,to:e.to,cc:e.cc,bcc:e.bcc,subject:e.subject,text:e.text,html:e.html})}};function ne(e){return/^(?!.*\.\.)(([a-z0-9!#$%&'*+/=?^_`{|}~-]{1,64}(\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*)|("[^"]*"))@((\[(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}\])|(\[IPv6:[a-f0-9:]+\])|([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\.([a-z]{2,})))$/i.test(e)}var oe=class{constructor(e){this.options=e}sentEmails=[];async sendMail(e){this.sentEmails.push(e),this.options?.logToConsole&&(console.log("Email sent:"),console.log(`From: ${e.from}`),console.log(`To: ${e.to}`),console.log(`Subject: ${e.subject}`),console.log(`Text: ${e.text}`)),this.options?.onSend&&this.options.onSend(e)}getSentEmails(){return[...this.sentEmails]}clearSentEmails(){this.sentEmails=[]}};import{EventEmitter as Oe}from"node:events";var ae=class{data=new Map;collections=new Map;expiryEmitter=new Oe;expiryCheckInterval;constructor(e=1e3){this.expiryCheckInterval=setInterval(()=>this.checkExpiredItems(),e)}destroy(){clearInterval(this.expiryCheckInterval),this.data.clear(),this.collections.clear(),this.expiryEmitter.removeAllListeners()}checkExpiredItems(){let e=Date.now();for(let[t,r]of this.data.entries())r.expiry&&r.expiry<e&&(this.data.delete(t),this.expiryEmitter.emit("expired",t));for(let[t,r]of this.collections.entries())r.expiry&&r.expiry<e&&(this.collections.delete(t),this.expiryEmitter.emit("expired",t))}async set(e,t,r){let s=r?Date.now()+r*1e3:null;this.data.set(e,{value:t,expiry:s})}async get(e){let t=this.data.get(e);return t?t.expiry&&t.expiry<Date.now()?(this.data.delete(e),null):t.value:null}async delete(e){this.data.delete(e),this.collections.delete(e)}async addToCollection(e,t,r){this.collections.has(e)||this.collections.set(e,{items:[],expiry:r?Date.now()+r*1e3:null});let s=this.collections.get(e);r&&(s.expiry=Date.now()+r*1e3),s.items.push(t)}async removeFromCollection(e,t){let r=this.collections.get(e);r&&(r.items=r.items.filter(s=>s!==t))}async getCollection(e){let t=this.collections.get(e);return t?[...t.items]:[]}async getCollectionSize(e){let t=this.collections.get(e);return t?t.items.length:0}async removeOldestFromCollection(e){let t=this.collections.get(e);return!t||t.items.length===0?null:t.items.shift()||null}async findKeys(e){let t=e.replace(/\*/g,".*").replace(/\?/g,"."),r=new RegExp(`^${t}$`),s=Array.from(this.data.keys()).filter(i=>r.test(i)),n=Array.from(this.collections.keys()).filter(i=>r.test(i));return[...new Set([...s,...n])]}};var w=()=>{let e=_e(process.env.DEBUG)||!1;return{auth:{jwtSecret:process.env.AUTH_JWT_SECRET||"your-jwt-secret",magicLinkExpirySeconds:900,jwtExpirySeconds:900,refreshTokenExpirySeconds:604800,maxActiveSessions:3,appUrl:process.env.APP_URL||"http://localhost:3000",debug:e},email:{emailSubject:"Your Secure Login Link",user:process.env.EMAIL_USER||"",host:process.env.EMAIL_HOST||"",password:process.env.EMAIL_PASSWORD||"",port:465,secure:!0,maxRetries:2,debug:e},storage:{databaseDirectory:"mikroauth",encryptionKey:process.env.STORAGE_KEY||"",debug:e},server:{port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,sslCert:"",sslKey:"",sslCa:"",rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"],debug:e}}};function _e(e){return e==="true"||e===!0}import We from"node:crypto";var ce=class{algorithm="HS256";secret="HS256";constructor(e){if(process.env.NODE_ENV==="production"&&(!e||e.length<32||e===w().auth.jwtSecret))throw new Error("Production environment requires a strong JWT secret (min 32 chars)");this.secret=e}sign(e,t={}){let r={alg:this.algorithm,typ:"JWT"},s=Math.floor(Date.now()/1e3),n={...e,iat:s};t.exp!==void 0&&(n.exp=s+t.exp),t.notBefore!==void 0&&(n.nbf=s+t.notBefore),t.issuer&&(n.iss=t.issuer),t.audience&&(n.aud=t.audience),t.subject&&(n.sub=t.subject),t.jwtid&&(n.jti=t.jwtid);let i=this.base64UrlEncode(JSON.stringify(r)),o=this.base64UrlEncode(JSON.stringify(n)),a=`${i}.${o}`,c=this.createSignature(a);return`${a}.${c}`}verify(e,t={}){let r=this.decode(e);if(r.header.alg!==this.algorithm)throw new Error(`Invalid algorithm. Expected ${this.algorithm}, got ${r.header.alg}`);let[s,n]=e.split("."),i=`${s}.${n}`;if(this.createSignature(i)!==r.signature)throw new Error("Invalid signature");let a=r.payload,c=Math.floor(Date.now()/1e3),l=t.clockTolerance||0;if(a.exp!==void 0&&a.exp+l<c)throw new Error("Token expired");if(a.nbf!==void 0&&a.nbf-l>c)throw new Error("Token not yet valid");if(t.issuer&&a.iss!==t.issuer)throw new Error("Invalid issuer");if(t.audience&&a.aud!==t.audience)throw new Error("Invalid audience");if(t.subject&&a.sub!==t.subject)throw new Error("Invalid subject");return a}decode(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid token format");try{let[r,s,n]=t,i=JSON.parse(this.base64UrlDecode(r)),o=JSON.parse(this.base64UrlDecode(s));return{header:i,payload:o,signature:n}}catch{throw new Error("Failed to decode token")}}createSignature(e){let t=We.createHmac("sha256",this.secret).update(e).digest();return this.base64UrlEncode(t)}base64UrlEncode(e){let t;return typeof e=="string"?t=Buffer.from(e):t=e,t.toString("base64").replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")}base64UrlDecode(e){let t=e.replace(/-/g,"+").replace(/_/g,"/");switch(t.length%4){case 0:break;case 2:t+="==";break;case 3:t+="=";break;default:throw new Error("Invalid base64 string")}return Buffer.from(t,"base64").toString()}};var _=class{static getText(e,t){return` Click this link to login: ${e} Security Information: - Expires in ${t} minutes - Can only be used once - Should only be used by you If you didn't request this link, please ignore this email. `.trim()}static getHtml(e,t){return` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Your Login Link</title> <style> body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; } .container { border: 1px solid #e1e1e1; border-radius: 5px; padding: 20px; } .button { display: inline-block; background-color: #4CAF50; color: white; text-decoration: none; padding: 10px 20px; border-radius: 5px; margin: 20px 0; } .security-info { background-color: #f8f8f8; padding: 15px; border-radius: 5px; margin-top: 20px; } .footer { margin-top: 20px; font-size: 12px; color: #888; } </style> </head> <body> <div class="container"> <h2>Your Secure Login Link</h2> <p>Click the button below to log in to your account:</p> <a href="${e}" class="button">Login to Your Account</a> <p> Hello, this is a test email! Hall\xE5, MikroMail has international support for, among others, espa\xF1ol, fran\xE7ais, portugu\xEAs, \u4E2D\u6587, \u65E5\u672C\u8A9E, and \u0420\u0443\u0441\u0441\u043A\u0438\u0439! </p> <div class="security-info"> <h3>Security Information:</h3> <ul> <li>This link expires in ${t} minutes</li> <li>Can only be used once</li> <li>Should only be used by you</li> </ul> </div> <p>If you didn't request this link, please ignore this email.</p> <div class="footer"> <p>This is an automated message, please do not reply to this email.</p> </div> </div> </body> </html> `.trim()}};import S from"node:crypto";import{URL as ze}from"node:url";var $={linkSent:"If a matching account was found, a magic link has been sent",revokedSuccess:"All other sessions revoked successfully",logoutSuccess:"Logged out successfully"},W=class{config;email;storage;jwtService;constructor(e,t,r){this.config=e,this.email=t||new oe,this.storage=r||new ae,this.jwtService=new ce(e.jwtSecret),this.checkIfUsingDefaultCredentialsInProduction()}checkIfUsingDefaultCredentialsInProduction(){process.env.NODE_ENV==="production"&&this.config.jwtSecret===w().auth.jwtSecret&&(console.error("WARNING: Using default secrets in production environment!"),process.exit(1))}generateToken(e){let t=Date.now().toString(),r=S.randomBytes(32).toString("hex");return S.createHash("sha256").update(`${e}:${t}:${r}`).digest("hex")}generateJsonWebToken(e){return this.jwtService.sign({sub:e.id,email:e.email,username:e.username,role:e.role,exp:Math.floor(Date.now()/1e3)+60*60*24})}generateRefreshToken(){return S.randomBytes(40).toString("hex")}async trackSession(e,t,r){let s=`sessions:${e}`;if(await this.storage.getCollectionSize(s)>=this.config.maxActiveSessions){let i=await this.storage.removeOldestFromCollection(s);i&&await this.storage.delete(`refresh:${i}`)}await this.storage.addToCollection(s,t,this.config.refreshTokenExpirySeconds),await this.storage.set(`refresh:${t}`,JSON.stringify(r),this.config.refreshTokenExpirySeconds)}generateMagicLinkUrl(e){let{token:t,email:r}=e;try{return new ze(this.config.appUrl),`${this.config.appUrl}/verify?token=${encodeURIComponent(t)}&email=${encodeURIComponent(r)}`}catch{throw new Error("Invalid base URL configuration")}}async createMagicLink(e){let{email:t,ip:r}=e;if(!ne(t))throw new Error("Valid email required");try{let s=this.generateToken(t),n=`magic_link:${s}`,i={email:t,ipAddress:r||"unknown",createdAt:Date.now()};await this.storage.set(n,JSON.stringify(i),this.config.magicLinkExpirySeconds);let o=await this.storage.findKeys("magic_link:*");console.log("existingTokens",o);for(let l of o){if(l===n)continue;let u=await this.storage.get(l);if(u)try{JSON.parse(u).email===t&&await this.storage.delete(l)}catch{}}let a=this.generateMagicLinkUrl({token:s,email:t}),c=Math.ceil(this.config.magicLinkExpirySeconds/60);return await this.email.sendMail({from:process.env.EMAIL_FROM||w().email.user,to:t,subject:process.env.EMAIL_SUBJECT||w().email.emailSubject,text:_.getText(a,c),html:_.getHtml(a,c)}),{message:$.linkSent}}catch(s){throw console.error(`Failed to process magic link request: ${s}`),new Error("Failed to process magic link request")}}async verifyToken(e){let{token:t,email:r}=e;try{let s=`magic_link:${t}`,n=await this.storage.get(s);if(!n)throw new Error("Invalid or expired token");let i=JSON.parse(n);if(i.email!==r)throw new Error("Email mismatch");let o=i.username,a=i.role;await this.storage.delete(s);let c=S.randomBytes(16).toString("hex"),l=this.generateRefreshToken(),u={sub:r,username:o,role:a,jti:c,lastLogin:i.createdAt,metadata:{ip:i.ipAddress},exp:Math.floor(Date.now()/1e3)+60*60*24},h=this.jwtService.sign(u,{exp:this.config.jwtExpirySeconds});return await this.trackSession(r,l,{...i,tokenId:c,createdAt:Date.now()}),{accessToken:h,refreshToken:l,exp:this.config.jwtExpirySeconds,tokenType:"Bearer"}}catch(s){throw console.error("Token verification error:",s),new Error("Verification failed")}}async refreshAccessToken(e){try{let t=await this.storage.get(`refresh:${e}`);if(!t)throw new Error("Invalid or expired refresh token");let r=JSON.parse(t),s=r.email;if(!s)throw new Error("Invalid refresh token data");let n=r.username,i=r.role,o=S.randomBytes(16).toString("hex"),a={sub:s,username:n,role:i,jti:o,lastLogin:r.lastLogin||r.createdAt,metadata:{ip:r.ipAddress}},c=this.jwtService.sign(a,{exp:this.config.jwtExpirySeconds});return r.lastUsed=Date.now(),await this.storage.set(`refresh:${e}`,JSON.stringify(r),this.config.refreshTokenExpirySeconds),{accessToken:c,refreshToken:e,exp:this.config.jwtExpirySeconds,tokenType:"Bearer"}}catch(t){throw console.error("Token refresh error:",t),new Error("Token refresh failed")}}verify(e){try{return this.jwtService.verify(e)}catch{throw new Error("Invalid token")}}async logout(e){try{if(!e||typeof e!="string")throw new Error("Refresh token is required");let t=await this.storage.get(`refresh:${e}`);if(!t)return{message:$.logoutSuccess};let s=JSON.parse(t).email;if(!s)throw new Error("Invalid refresh token data");await this.storage.delete(`refresh:${e}`);let n=`sessions:${s}`;return await this.storage.removeFromCollection(n,e),{message:$.logoutSuccess}}catch(t){throw console.error("Logout error:",t),new Error("Logout failed")}}async getSessions(e){try{if(!e.user?.email)throw new Error("User not authenticated");let t=e.user.email,r=e.body?.refreshToken,s=`sessions:${t}`,i=(await this.storage.getCollection(s)).map(async a=>{try{let c=await this.storage.get(`refresh:${a}`);if(!c)return await this.storage.removeFromCollection(s,a),null;let l=JSON.parse(c);return{id:`${a.substring(0,8)}...`,createdAt:l.createdAt||0,lastLogin:l.lastLogin||l.createdAt||0,lastUsed:l.lastUsed||l.createdAt||0,metadata:{ip:l.ipAddress},isCurrentSession:a===r}}catch{return await this.storage.removeFromCollection(s,a),null}}),o=(await Promise.all(i)).filter(Boolean);return o.sort((a,c)=>c.createdAt-a.createdAt),{sessions:o}}catch(t){throw console.error("Get sessions error:",t),new Error("Failed to fetch sessions")}}async revokeSessions(e){try{if(!e.user?.email)throw new Error("User not authenticated");let t=e.user.email,r=e.body?.refreshToken,s=`sessions:${t}`,n=await this.storage.getCollection(s);for(let i of n)r&&i===r||await this.storage.delete(`refresh:${i}`);return await this.storage.delete(s),r&&await this.storage.get(`refresh:${r}`)&&await this.storage.addToCollection(s,r,this.config.refreshTokenExpirySeconds),{message:$.revokedSuccess}}catch(t){throw console.error("Revoke sessions error:",t),new Error("Failed to revoke sessions")}}authenticate(e,t){try{let r=e.headers?.authorization;if(!r||!r.startsWith("Bearer "))throw new Error("Authentication required");let s=r.split(" ")[1];try{let n=this.verify(s);e.user={email:n.sub},t()}catch{throw new Error("Invalid or expired token")}}catch(r){t(r)}}};var z=class extends Error{constructor(e){super(e),this.name="ValidationError",this.message=e||"Validation did not pass",this.cause={statusCode:400}}};import{existsSync as Ke,readFileSync as qe}from"node:fs";var y=class{config={};options=[];validators=[];autoValidate=!0;constructor(e){let t=e?.configFilePath,r=e?.args||[],s=e?.config||{};this.options=e?.options||[],this.validators=e?.validators||[],e?.autoValidate!==void 0&&(this.autoValidate=e.autoValidate),this.config=this.createConfig(t,r,s)}deepMerge(e,t){let r={...e};for(let s in t)t[s]!==void 0&&(t[s]!==null&&typeof t[s]=="object"&&!Array.isArray(t[s])&&s in e&&e[s]!==null&&typeof e[s]=="object"&&!Array.isArray(e[s])?r[s]=this.deepMerge(e[s],t[s]):t[s]!==void 0&&(r[s]=t[s]));return r}setValueAtPath(e,t,r){let s=t.split("."),n=e;for(let o=0;o<s.length-1;o++){let a=s[o];!(a in n)||n[a]===null?n[a]={}:typeof n[a]!="object"&&(n[a]={}),n=n[a]}let i=s[s.length-1];n[i]=r}getValueAtPath(e,t){let r=t.split("."),s=e;for(let n of r){if(s==null)return;s=s[n]}return s}createConfig(e,t=[],r={}){let s={};for(let a of this.options)a.defaultValue!==void 0&&this.setValueAtPath(s,a.path,a.defaultValue);let n={};if(e&&Ke(e))try{let a=qe(e,"utf8");n=JSON.parse(a),console.log(`Loaded configuration from ${e}`)}catch(a){console.error(`Error reading config file: ${a instanceof Error?a.message:String(a)}`)}let i=this.parseCliArgs(t),o=this.deepMerge({},s);return o=this.deepMerge(o,n),o=this.deepMerge(o,r),o=this.deepMerge(o,i),o}parseCliArgs(e){let t={},r=e[0]?.endsWith("node")||e[0]?.endsWith("node.exe")?2:0;for(;r<e.length;){let s=e[r++],n=this.options.find(i=>i.flag===s);if(n)if(n.isFlag)this.setValueAtPath(t,n.path,!0);else if(r<e.length&&!e[r].startsWith("-")){let i=e[r++];if(n.parser)try{i=n.parser(i)}catch(o){console.error(`Error parsing value for ${n.flag}: ${o instanceof Error?o.message:String(o)}`);continue}if(n.validator){let o=n.validator(i);if(o!==!0&&typeof o=="string"){console.error(`Invalid value for ${n.flag}: ${o}`);continue}if(o===!1){console.error(`Invalid value for ${n.flag}`);continue}}this.setValueAtPath(t,n.path,i)}else console.error(`Missing value for option ${s}`)}return t}validate(){for(let e of this.validators){let t=this.getValueAtPath(this.config,e.path),r=e.validator(t,this.config);if(r===!1)throw new z(e.message);if(typeof r=="string")throw new z(r)}}get(){return this.autoValidate&&this.validate(),this.config}getValue(e,t){let r=this.getValueAtPath(this.config,e);return r!==void 0?r:t}setValue(e,t){if(typeof t=="object"&&t!==null&&!Array.isArray(t)){let r=this.getValueAtPath(this.config,e)||{};if(typeof r=="object"&&!Array.isArray(r)){let s=this.deepMerge(r,t);this.setValueAtPath(this.config,e,s);return}}this.setValueAtPath(this.config,e,t)}getHelpText(){let e=`Available configuration options: `;for(let t of this.options)e+=`${t.flag}${t.isFlag?"":" <value>"} `,t.description&&(e+=` ${t.description} `),t.defaultValue!==void 0&&(e+=` Default: ${JSON.stringify(t.defaultValue)} `),e+=` `;return e}};var T={int:e=>{let t=e.trim();if(!/^[+-]?\d+$/.test(t))throw new Error(`Cannot parse "${e}" as an integer`);let r=Number.parseInt(t,10);if(Number.isNaN(r))throw new Error(`Cannot parse "${e}" as an integer`);return r},float:e=>{let t=e.trim();if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(t)){if(t==="Infinity"||t==="-Infinity")return t==="Infinity"?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY;throw new Error(`Cannot parse "${e}" as a number`)}let r=Number.parseFloat(t);if(Number.isNaN(r))throw new Error(`Cannot parse "${e}" as a number`);return r},boolean:e=>{let t=e.trim().toLowerCase();if(["true","yes","1","y"].includes(t))return!0;if(["false","no","0","n"].includes(t))return!1;throw new Error(`Cannot parse "${e}" as a boolean`)},array:e=>e.split(",").map(t=>t.trim()),json:e=>{try{return JSON.parse(e)}catch{throw new Error(`Cannot parse "${e}" as JSON`)}}};var p=()=>Date.now(),le=(e,t)=>t==="D"||e.length===0||e.join(" ")==="null"?null:ue(e),ue=e=>{try{return JSON.parse(e.join(" "))}catch{return}};function he(e){return e==="true"||e===!0}function de(e){let t=(o,a)=>{if(o){if(a==="json")return ue(o)||o;if(a==="number")return Number.parseInt(o,10)}},r=t(e?.filter,"json"),s=t(e?.sort,"json"),n=t(e?.limit,"number"),i=t(e?.offset,"number");if(!(!r&&!s&&!n&&!i))return{filter:r,sort:s,limit:n,offset:i}}var fe=()=>({port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,sslCert:"",sslKey:"",sslCa:"",debug:he(process.env.DEBUG)||!1,rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"]});var L=class extends Error{constructor(e){super(),this.name="NotFoundError",this.message=e||"Resource not found",this.cause={statusCode:404}}},b=class extends Error{constructor(e){super(),this.name="CheckpointError",this.message=e,this.cause={statusCode:500}}};import{existsSync as K}from"node:fs";import{readFile as me,unlink as Ge,writeFile as pe}from"node:fs/promises";var ge=class{checkpointInterval;defaultCheckpointIntervalMs=10*1e3;isCheckpointing=!1;lastCheckpointTime=0;walFile;checkpointTimer=null;wal;table;constructor(e){let{table:t,wal:r,walFile:s,checkpointIntervalMs:n}=e;this.defaultCheckpointIntervalMs=n||this.defaultCheckpointIntervalMs,this.table=t,this.wal=r,this.walFile=s,this.checkpointInterval=n,this.lastCheckpointTime=p()}async start(){let e=`${this.walFile}.checkpoint`;if(K(e)){console.log("Incomplete checkpoint detected, running recovery...");try{let t=await me(e,"utf8");console.log(`Incomplete checkpoint from: ${new Date(Number.parseInt(t))}`),await this.checkpoint(!0)}catch(t){throw new L(`Error reading checkpoint file: ${t}`)}}this.checkpointTimer=setInterval(async()=>{try{await this.checkpoint()}catch(t){throw new b(`Checkpoint interval failed: ${t}`)}},this.checkpointInterval)}stop(){this.checkpointTimer&&(clearInterval(this.checkpointTimer),this.checkpointTimer=null)}async checkpoint(e=!1){if(this.isCheckpointing)return;let t=p();if(!(!e&&t-this.lastCheckpointTime<this.checkpointInterval)){this.isCheckpointing=!0;try{await this.wal.flushWAL();let r=`${this.walFile}.checkpoint`;await pe(r,t.toString(),"utf8");let s=await this.getTablesFromWAL();await this.persistTables(s);try{await pe(this.walFile,"","utf8"),process.env.MIKRODB_DEBUG==="true"&&console.log("WAL truncated successfully")}catch(n){throw new b(`Failed to truncate WAL: ${n}`)}K(r)&&await Ge(r),this.wal.clearPositions(),this.lastCheckpointTime=t,process.env.MIKRODB_DEBUG==="true"&&console.log("Checkpoint complete")}catch(r){throw new b(`Checkpoint failed: ${r}`)}finally{this.isCheckpointing=!1}}}async getTablesFromWAL(){let e=new Set;if(!K(this.walFile))return e;try{let t=await me(this.walFile,"utf8");if(!t.trim())return e;let r=t.trim().split(` `);for(let s of r){if(!s.trim())continue;let n=s.split(" ");n.length>=3&&e.add(n[2])}}catch(t){throw new b(`Error reading WAL file: ${t}`)}return e}async persistTables(e){let t=Array.from(e).map(async r=>{try{await this.table.flushTableToDisk(r),console.log(`Checkpointed table "${r}"`)}catch(s){throw new b(`Failed to checkpoint table "${r}": ${s.message}`)}});await Promise.all(t)}};import{existsSync as ye,statSync as q,writeFileSync as Je}from"node:fs";import{appendFile as Xe,readFile as we}from"node:fs/promises";var be=class{walFile;walInterval;walBuffer=[];maxWalBufferEntries=Number.parseInt(process.env.MAX_WAL_BUFFER_ENTRIES||"100");maxWalBufferSize=Number.parseInt(process.env.MAX_WAL_BUFFER_SIZE||(1024*1024*.01).toString());maxWalSizeBeforeCheckpoint=Number.parseInt(process.env.MAX_WAL_BUFFER_SIZE||(1024*1024*.01).toString());lastProcessedEntryCount=new Map;checkpointCallback=null;constructor(e,t){this.walFile=e,this.walInterval=t,this.start()}setCheckpointCallback(e){this.checkpointCallback=e}start(){ye(this.walFile)||Je(this.walFile,"","utf-8"),setInterval(async()=>{try{this.walBuffer.length>0&&await this.flushWAL()}catch(e){console.error("WAL flush interval failed:",e)}},this.walInterval)}checkWalFileExists(){if(!ye(this.walFile))throw new L(`WAL file "${this.walFile}" does not exist`)}async loadWAL(e){this.checkWalFileExists();let t=[];if((q(this.walFile)?.size||0)===0)return t;try{let n=(await we(this.walFile,"utf8")).trim().split(` `),i=p();for(let o=0;o<n.length;o++){if(!n[o].trim())continue;let c=this.lastProcessedEntryCount.get(e||"")||0;if(e&&o<c)continue;let[l,u,h,d,m,M,...Pe]=n[o].split(" ");if(e&&h!==e)continue;let De=Number(d.split(":")[1]),R=m==="0"?null:Number(m.split(":")[1]);if(R&&R<i)continue;let Q=le(Pe,u);Q!==void 0&&(e&&this.lastProcessedEntryCount.set(e,o+1),u==="W"?t.push({operation:"W",tableName:h,key:M,data:{value:Q,version:De,timestamp:i,expiration:R}}):u==="D"&&t.push({operation:"D",tableName:h,key:M}))}return t}catch(s){return console.error(e?`Failed to replay WAL for table "${e}": ${s.message}`:`Failed to replay WAL: ${s.message}`),t}}async hasNewWALEntriesForTable(e){this.checkWalFileExists();try{let t=await we(this.walFile,"utf8");if(!t.trim())return!1;let r=t.trim().split(` `),s=this.lastProcessedEntryCount.get(e)||0;if(s>=r.length)return!1;for(let n=s;n<r.length;n++){let i=r[n];if(!i.trim())continue;let o=i.split(" ");if(o.length>=3&&o[2]===e)return!0}return!1}catch(t){return console.error(`Error checking WAL for ${e}:`,t),!0}}async flushWAL(){if(this.checkWalFileExists(),this.walBuffer.length===0)return;let e=[...this.walBuffer];this.walBuffer=[];try{await Xe(this.walFile,e.join(""),"utf8");let t=q(this.walFile);t.size>this.maxWalSizeBeforeCheckpoint&&(process.env.MIKRODB_DEBUG==="true"&&console.log(`WAL size (${t.size}) exceeds limit (${this.maxWalSizeBeforeCheckpoint}), triggering checkpoint`),this.checkpointCallback&&setImmediate(async()=>{try{await this.checkpointCallback()}catch(r){console.error("Error during automatic checkpoint:",r)}}))}catch(t){throw console.error(`Failed to flush WAL: ${t.message}`),this.walBuffer=[...e,...this.walBuffer],t}}async appendToWAL(e,t,r,s,n,i=0){this.checkWalFileExists();let a=`${p()} ${t} ${e} v:${n} x:${i} ${r} ${JSON.stringify(s)} `;this.walBuffer.push(a),this.walBuffer.length>=this.maxWalBufferEntries&&await this.flushWAL(),this.walBuffer.reduce((u,h)=>u+h.length,0)>=this.maxWalBufferSize&&await this.flushWAL();let l=q(this.walFile);l.size>this.maxWalSizeBeforeCheckpoint&&(process.env.MIKRODB_DEBUG==="true"&&console.log(`WAL size (${l.size}) exceeds limit (${this.maxWalSizeBeforeCheckpoint}), triggering checkpoint`),this.checkpointCallback&&setImmediate(async()=>{try{await this.checkpointCallback()}catch(u){console.error("Error during automatic checkpoint:",u)}}))}clearPositions(){this.lastProcessedEntryCount.clear()}};import{createCipheriv as Ye,createDecipheriv as Ze,randomBytes as Qe,scryptSync as et}from"node:crypto";var U=class{algo="aes-256-gcm";KEY_LENGTH=32;IV_LENGTH=12;generateKey(e,t){return et(`${t}#${e}`,t,this.KEY_LENGTH)}generateIV(){return Qe(this.IV_LENGTH)}encrypt(e,t,r){if(t.length!==this.KEY_LENGTH)throw new Error(`Invalid key length: ${t.length} bytes. Expected: ${this.KEY_LENGTH} bytes`);if(r.length!==this.IV_LENGTH)throw new Error(`Invalid IV length: ${r.length} bytes. Expected: ${this.IV_LENGTH} bytes`);let s=Ye(this.algo,t,r),n=Buffer.concat([s.update(e,"utf8"),s.final()]),i=s.getAuthTag();return{iv:r,encrypted:n,authTag:i}}decrypt(e,t){let{iv:r,encrypted:s,authTag:n}=e;if(t.length!==this.KEY_LENGTH)throw new Error(`Invalid key length: ${t.length} bytes. Expected: ${this.KEY_LENGTH} bytes`);if(r.length!==this.IV_LENGTH)throw new Error(`Invalid IV length: ${r.length} bytes. Expected: ${this.IV_LENGTH} bytes`);let i=Ze(this.algo,t,r);return i.setAuthTag(n),Buffer.concat([i.update(s),i.final()]).toString("utf8")}serialize(e){return Buffer.concat([Buffer.from([1]),Buffer.from([e.iv.length]),e.iv,Buffer.from([e.authTag.length]),e.authTag,e.encrypted])}deserialize(e){let t=0,r=e[t++];if(r!==1)throw new Error(`Unsupported encryption format version: ${r}`);let s=e[t++],n=e.subarray(t,t+s);t+=s;let i=e[t++],o=e.subarray(t,t+i);t+=i;let a=e.subarray(t);return{iv:n,authTag:o,encrypted:a}}toHex(e){return e.toString("hex")}toUtf8(e){return e.toString("utf8")}toBuffer(e){return Buffer.from(e,"hex")}};var B=class{readTableFromBinaryBuffer(e){if(e.length<8||e[0]!==77||e[1]!==68||e[2]!==66||e[3]!==1)throw new Error("Invalid table file format");let t=e.readUInt32LE(4),r=new Map,s=8,n=p();for(let i=0;i<t&&s+26<=e.length;i++){let o=e.readUInt16LE(s);s+=2;let a=e.readUInt32LE(s);s+=4;let c=e.readUInt32LE(s);s+=4;let l=Number(e.readBigUInt64LE(s));s+=8;let u=Number(e.readBigUInt64LE(s));if(s+=8,s+o+a>e.length)break;if(u&&u<=n){s+=o+a;continue}let h=e.toString("utf8",s,s+o);s+=o;let d=e.slice(s,s+a);s+=a;let m=this.decodeValue(d);r.set(h,{value:m,version:c,timestamp:l,expiration:u||null})}return r}toBinaryBuffer(e){let t=[],r=Buffer.from([77,68,66,1]);t.push(r);let s=Array.from(e.entries()).filter(([i])=>typeof i=="string"),n=Buffer.alloc(4);n.writeUInt32LE(s.length,0),t.push(n);for(let[i,o]of s){if(i===null||typeof i!="string")continue;let a=Buffer.from(i),c=this.encodeValue(o.value),l=Buffer.alloc(2);l.writeUInt16LE(a.length,0),t.push(l);let u=Buffer.alloc(4);u.writeUInt32LE(c.length,0),t.push(u);let h=Buffer.alloc(4);h.writeUInt32LE(o.version||0,0),t.push(h);let d=Buffer.alloc(8);d.writeBigUInt64LE(BigInt(o.timestamp||0),0),t.push(d);let m=Buffer.alloc(8);m.writeBigUInt64LE(BigInt(o.expiration||0),0),t.push(m),t.push(a),t.push(c)}return Buffer.concat(t)}encodeValue(e){if(e==null)return Buffer.from([0]);if(typeof e=="boolean")return Buffer.from([1,e?1:0]);if(typeof e=="number"){if(Number.isInteger(e)&&e>=-2147483648&&e<=2147483647){let r=Buffer.alloc(5);return r[0]=2,r.writeInt32LE(e,1),r}let t=Buffer.alloc(9);return t[0]=3,t.writeDoubleLE(e,1),t}if(typeof e=="string"){let t=Buffer.from(e,"utf8"),r=Buffer.alloc(5+t.length);return r[0]=4,r.writeUInt32LE(t.length,1),t.copy(r,5),r}if(Array.isArray(e)){let t=[],r=Buffer.alloc(5);r[0]=5,r.writeUInt32LE(e.length,1),t.push(r);for(let s of e)t.push(this.encodeValue(s));return Buffer.concat(t)}if(typeof e=="object"){let t=Object.keys(e),r=[],s=Buffer.alloc(5);s[0]=6,s.writeUInt32LE(t.length,1),r.push(s);for(let n of t){let i=Buffer.from(n,"utf8"),o=Buffer.alloc(2);o.writeUInt16LE(i.length,0),r.push(o),r.push(i),r.push(this.encodeValue(e[n]))}return Buffer.concat(r)}return this.encodeValue(String(e))}decodeValue(e){if(e.length===0)return null;let t=e[0];switch(t){case 0:return null;case 1:return e[1]===1;case 2:return e.readInt32LE(1);case 3:return e.readDoubleLE(1);case 4:{let r=e.readUInt32LE(1);return e.toString("utf8",5,5+r)}case 5:{let r=e.readUInt32LE(1),s=new Array(r),n=5;for(let i=0;i<r;i++){let{value:o,bytesRead:a}=this.decodeValueWithSize(e,n);s[i]=o,n+=a}return s}case 6:{let r=e.readUInt32LE(1),s={},n=5;for(let i=0;i<r;i++){let o=e.readUInt16LE(n);n+=2;let a=e.toString("utf8",n,n+o);n+=o;let{value:c,bytesRead:l}=this.decodeValueWithSize(e,n);s[a]=c,n+=l}return s}default:return console.warn(`Unknown type byte: ${t}`),null}}decodeValueWithSize(e,t=0){if(t>=e.length)return{value:null,bytesRead:0};let r=e[t];switch(r){case 0:return{value:null,bytesRead:1};case 1:return{value:e[t+1]===1,bytesRead:2};case 2:return{value:e.readInt32LE(t+1),bytesRead:5};case 3:return{value:e.readDoubleLE(t+1),bytesRead:9};case 4:{let s=e.readUInt32LE(t+1);return{value:e.toString("utf8",t+5,t+5+s),bytesRead:5+s}}case 5:{let s=e.readUInt32LE(t+1),n=new Array(s),i=t+5;for(let o=0;o<s;o++){let a=this.decodeValueWithSize(e,i);n[o]=a.value,i+=a.bytesRead}return{value:n,bytesRead:i-t}}case 6:{let s=e.readUInt32LE(t+1),n={},i=t+5;for(let o=0;o<s;o++){let a=e.readUInt16LE(i);i+=2;let c=e.toString("utf8",i,i+a);i+=a;let l=this.decodeValueWithSize(e,i);n[c]=l.value,i+=l.bytesRead}return{value:n,bytesRead:i-t}}default:return console.warn(`Unknown type byte: ${r} at offset ${t}`),{value:null,bytesRead:1}}}};import{writeFile as tt}from"node:fs/promises";async function Ee(e,t,r){let s=new U,i=new B().toBinaryBuffer(t);if(!i){console.log("Buffer is empty, skipping...");return}if(r)try{let o=s.generateKey(r,"salt"),a=i.toString("binary"),c=s.generateIV(),l=s.encrypt(a,o,c);i=s.serialize(l)}catch(o){console.error("Encryption failed:",o)}await tt(e,i)}var ve=class{cacheLimit;tableAccessTimes=new Map;constructor(e={}){this.cacheLimit=e.cacheLimit??20}trackTableAccess(e){this.tableAccessTimes.set(e,p())}findTablesForEviction(e){if(e<=this.cacheLimit)return[];let t=e-this.cacheLimit,r=Array.from(this.tableAccessTimes.entries()).sort((s,n)=>s[1]-n[1]).slice(0,t).map(([s])=>s);for(let s of r)this.tableAccessTimes.delete(s);return r}removeTable(e){this.tableAccessTimes.delete(e)}findExpiredItems(e){let t=p(),r=[];for(let[s,n]of e.entries())n.x&&n.x<t&&r.push([s,n]);return r}clear(){this.tableAccessTimes.clear()}};var Ce=class{async query(e,t,r=50){let s=new Map;for(let[n,i]of e.entries())if((!t||(typeof t=="function"?t(i.value):this.evaluateFilter(i.value,t)))&&(s.set(n,i.value),r&&s.size>=r))break;return Array.from(s.values())}evaluateCondition(e,t){if(!t||typeof t!="object"||!t.operator)return e===t;let{operator:r,value:s}=t;switch(r){case"eq":return e===s;case"neq":return e!==s;case"gt":return e>s;case"gte":return e>=s;case"lt":return e<s;case"lte":return e<=s;case"in":return Array.isArray(s)&&s.includes(e);case"nin":return Array.isArray(s)&&!s.includes(e);case"like":return typeof e=="string"&&typeof s=="string"&&e.toLowerCase().includes(s.toLowerCase());case"between":return Array.isArray(s)&&s.length===2&&e>=s[0]&&e<=s[1];case"regex":try{let n=new RegExp(s);return typeof e=="string"&&n.test(e)}catch(n){return console.error("Invalid regex pattern:",n),!1}case"contains":return Array.isArray(e)&&e.includes(s);case"containsAll":return Array.isArray(e)&&Array.isArray(s)&&s.every(n=>e.includes(n));case"containsAny":return Array.isArray(e)&&Array.isArray(s)&&s.some(n=>e.includes(n));case"size":return Array.isArray(e)&&e.length===s;default:return!1}}evaluateFilter(e,t){if(e==null)return!1;if("$or"in t)return t.$or.some(r=>this.evaluateFilter(e,r));for(let[r,s]of Object.entries(t))if(!r.startsWith("$"))if(r.includes(".")){let n=r.split("."),i=e;for(let o of n)if(i=i?.[o],i==null)return!1;if(!this.evaluateCondition(i,s))return!1}else if(s&&typeof s=="object"&&!("operator"in s)){let n=e[r];if(n==null||!this.evaluateFilter(n,s))return!1}else{let n=e[r];if(!this.evaluateCondition(n,s))return!1}return!0}};import{existsSync as ke,mkdirSync as st}from"node:fs";import{readFile as it,writeFile as nt}from"node:fs/promises";import{join as J}from"node:path";import{EventEmitter as rt}from"node:events";var G=class{emitter;targets={};options;constructor(e){this.emitter=new rt,this.options={errorHandler:e?.errorHandler||(t=>console.error(t))}}addTarget(e){return(Array.isArray(e)?e:[e]).map(s=>this.targets[s.name]?(console.error(`Target with name '${s.name}' already exists.`),!1):(this.targets[s.name]={name:s.name,url:s.url,headers:s.headers||{},events:s.events||[]},!0)).every(s=>s===!0)}updateTarget(e,t){if(!this.targets[e])return console.error(`Target with name '${e}' does not exist.`),!1;let r=this.targets[e];return t.url!==void 0&&(r.url=t.url),t.headers&&(r.headers={...r.headers,...t.headers}),t.events&&(r.events=t.events),!0}removeTarget(e){return this.targets[e]?(delete this.targets[e],!0):(console.error(`Target with name '${e}' does not exist.`),!1)}addEventToTarget(e,t){if(!this.targets[e])return console.error(`Target with name '${e}' does not exist.`),!1;let r=Array.isArray(t)?t:[t],s=this.targets[e];return r.forEach(n=>{s.events.includes(n)||s.events.push(n)}),!0}on(e,t){return this.emitter.on(e,t),this}off(e,t){return this.emitter.off(e,t),this}once(e,t){return this.emitter.once(e,t),this}async emit(e,t){let r={success:!0,errors:[]},s=(o,a,c)=>({target:o,event:a,error:c}),n=Object.values(this.targets).filter(o=>o.events.includes(e)||o.events.includes("*"));n.filter(o=>!o.url).forEach(o=>{try{this.emitter.emit(e,t)}catch(a){let c=a instanceof Error?a:new Error(String(a));r.errors.push({target:o.name,event:e,error:c}),this.options.errorHandler(c,e,t),r.success=!1}});let i=n.filter(o=>o.url);if(i.length>0){let o=i.map(async a=>{try{let c=await fetch(a.url,{method:"POST",headers:{"Content-Type":"application/json",...a.headers},body:JSON.stringify({eventName:e,data:t})});if(!c.ok){let l=`HTTP error! Status: ${c.status}: ${c.statusText}`,u=new Error(l);r.errors.push(s(a.name,e,u)),this.options.errorHandler(u,e,t),r.success=!1}}catch(c){let l=c instanceof Error?c:new Error(String(c));r.errors.push(s(a.name,e,l)),this.options.errorHandler(l,e,t),r.success=!1}});await Promise.allSettled(o)}return r}async handleIncomingEvent(e){try{let{eventName:t,data:r}=typeof e=="string"?JSON.parse(e):e;process.nextTick(()=>{try{this.emitter.emit(t,r)}catch(s){this.options.errorHandler(s instanceof Error?s:new Error(String(s)),t,r)}})}catch(t){throw this.options.errorHandler(t instanceof Error?t:new Error(String(t)),"parse_event"),t}}createMiddleware(){return async(e,t,r)=>{if(e.method!=="POST"){r&&r();return}if(e.body)try{await this.handleIncomingEvent(e.body),t.statusCode=202,t.end()}catch(s){t.statusCode=400,t.end(JSON.stringify({error:"Invalid event format"})),r&&r(s)}else{let s="";e.on("data",n=>s+=n.toString()),e.on("end",async()=>{try{await this.handleIncomingEvent(s),t.statusCode=202,t.end()}catch(n){t.statusCode=400,t.end(JSON.stringify({error:"Invalid event format"})),r&&r(n)}})}}}};var ot=new G,Se=class{databaseDirectory;walFile;activeTable=null;data=new Map;writeBuffer=[];maxWriteOpsBeforeFlush=Number.parseInt(process.env.MAX_WRITE_OPS_BEFORE_FLUSH||"100");encryptionKey;cache;encryption;persistence;query;wal;constructor(e){let{databaseDirectory:t,walFileName:r,walInterval:s}=e;this.databaseDirectory=t,this.walFile=J(this.databaseDirectory,r),this.encryptionKey=e.encryptionKey?e.encryptionKey:null,ke(this.databaseDirectory)||st(this.databaseDirectory),this.cache=new ve,this.encryption=new U,this.persistence=new B,this.query=new Ce,this.wal=new be(this.walFile,s)}async start(){await this.applyWALEntries()}async setActiveTable(e){this.activeTable!==e&&(this.hasTable(e)||await this.loadTable(e),await this.applyWALEntries(e),await this.evictTablesIfNeeded(),this.activeTable=e)}async applyWALEntries(e){let t=await this.wal.loadWAL(e);if(t.length===0)return;let r=e?[e]:[...new Set(t.map(s=>s.tableName))];for(let s of r){let n=t.filter(i=>i.tableName===s);e&&!this.hasTable(s)?await this.loadTable(s):this.createTable(s);for(let i of n)i.operation==="W"&&i.data?this.setItem(s,i.key,i.data):i.operation==="D"&&this.deleteItem(s,i.key)}}async loadTable(e){let t=J(this.databaseDirectory,e);if(this.hasTable(e))return;if(!ke(t)){this.createTable(e);return}let r=await it(t),s=r;if(this.encryptionKey&&r.length>0&&r[0]===1)try{let i=this.encryption.deserialize(r),o=this.encryption.generateKey(this.encryptionKey,"salt"),a=this.encryption.decrypt(i,o);s=Buffer.from(a,"binary")}catch(i){console.error(`Failed to decrypt ${e}:`,i)}let n=this.persistence.readTableFromBinaryBuffer(s);this.data.set(e,n),this.data.size>this.cache.cacheLimit&&setImmediate(()=>this.evictTablesIfNeeded())}async get(e){let{tableName:t}=e,r=e.key,s=e.options;if(await this.setActiveTable(t),!s)return r?this.getItem(t,r)?.value:[...this.getAll(t)];let n=this.getTable(t),i=await this.query.query(n,s.filter,s.limit);if(s.sort&&(i=i.sort(s.sort)),s.offset!=null||s.limit!=null){let o=s.offset||0,a=s.limit?o+s.limit:void 0;i=i.slice(o,a)}return i}async write(e,t={}){let{concurrencyLimit:r=10,flushImmediately:s=!1}=t,n=Array.isArray(e)?e:[e],i=n.length,o=0;for(;o<i;){let a=n.slice(o,o+r),c=a.map(u=>this.writeItem(u,!1));if((await Promise.all(c)).includes(!1))return!1;o+=a.length,this.writeBuffer.length>=this.maxWriteOpsBeforeFlush&&await this.flushWrites()}return(s||i>0)&&await this.flush(),!0}async writeItem(e,t=!1){let{tableName:r,key:s,value:n,expectedVersion:i=null,expiration:o=0}=e;await this.setActiveTable(r);let{success:a,newVersion:c}=this.getItemVersion(r,s,i);return a?(await this.wal.appendToWAL(r,"W",s,n,c,o),this.setItem(r,s,{value:n,v:c,t:p(),x:o}),this.addToWriteBuffer(r,s),t&&await this.flush(),!0):!1}async delete(e,t,r=null,s=!1){if(await this.setActiveTable(e),!this.hasTable(e)||!this.hasKey(e,t))return console.log(`Key ${t} not found in table ${e}`),!1;let{success:n,currentVersion:i,expiration:o}=this.getItemVersion(e,t,r);return n?(await this.wal.appendToWAL(e,"D",t,null,i,o),this.deleteItem(e,t),s&&await this.flush(),!0):!1}getItemVersion(e,t,r){let s=this.getItem(e,t),n=s?s.version:0,i=n+1,o=s&&s.expiration||0,a=!0;return r!==null&&n!==r&&(console.log(`Version mismatch for ${e}:${t}. Expected ${r}, found ${n}`),a=!1),{success:a,currentRecord:s,currentVersion:n,newVersion:i,expiration:o}}createTable(e){this.trackTableAccess(e),this.hasTable(e)||this.data.set(e,new Map)}getTable(e){return this.trackTableAccess(e),this.hasTable(e)?this.data.get(e):new Map}async getTableSize(e){return await this.setActiveTable(e),this.trackTableAccess(e),this.data.get(e)?.size}deleteTable(e){this.trackTableAccess(e),this.data.delete(e)}hasTable(e){return this.trackTableAccess(e),this.data.has(e)}hasKey(e,t){return this.trackTableAccess(e),this.data.get(e)?.has(t)}getItem(e,t){this.trackTableAccess(e);let r=this.data.get(e)?.get(t);if(r){if(r?.x!==0&&Date.now()>r?.x){this.deleteItem(e,t);return}return{value:r.value,version:r.v,timestamp:r.t,expiration:r.x}}}getAll(e){this.trackTableAccess(e);let t=this.data.get(e);return t?Array.from(t):[]}setItem(e,t,r){this.trackTableAccess(e),this.createTable(e),this.data.get(e)?.set(t,r)}deleteItem(e,t){this.data.get(e)?.delete(t)}addToWriteBuffer(e,t){let r=this.getItem(e,t);this.writeBuffer.push(JSON.stringify({tableName:e,key:t,record:r}))}trackTableAccess(e){this.cache.trackTableAccess(e)}async flush(){await this.flushWAL(),await this.flushWrites()}async flushWAL(){await this.wal.flushWAL()}async flushWrites(){if(this.writeBuffer.length!==0)try{let e=new Map,t=[...this.writeBuffer];for(let s of t){let n=JSON.parse(s);e.has(n.tableName)||e.set(n.tableName,new Map),e.get(n.tableName).set(n.key,n.record);let{success:o,errors:a}=await ot.emit("write.flushed",{operation:n.key,record:n.record});o||console.error(a)}let r=Array.from(e.entries()).map(async([s])=>{let n=this.getTable(s),i=J(this.databaseDirectory,s);await Ee(i,n,this.encryptionKey)});await Promise.all(r),this.writeBuffer=this.writeBuffer.slice(t.length)}catch(e){console.error(`Failed to flush writes: ${e.message}`)}}async flushTableToDisk(e){await this.setActiveTable(e);let t=this.getTable(e);if(t.size!==0){for(let[r,s]of t.entries())this.addToWriteBuffer(e,r);await this.flushWrites()}}async evictTablesIfNeeded(){let e=this.cache.findTablesForEviction(this.data.size);for(let t of e)await this.flushTableToDisk(t),this.data.delete(t)}async cleanupExpiredItems(){for(let[e,t]of this.data.entries()){let r=this.cache.findExpiredItems(t);for(let[s,n]of r)await this.wal.appendToWAL(e,"D",s,null,n.v,n.x),t.delete(s)}}async dump(e){e&&await this.setActiveTable(e);let t=this.getAll(this.activeTable);await nt(`${this.databaseDirectory}/${this.activeTable}_dump.json`,JSON.stringify(t),"utf8")}getWAL(){return this.wal}getPersistence(){return this.persistence}};import{join as at}from"node:path";var x=class{table;checkpoint;constructor(e){let t=fe(),r=new y({configFilePath:"mikrodb.config.json",args:process.argv,options:[{flag:"--port",path:"port",defaultValue:t.port},{flag:"--host",path:"host",defaultValue:t.host},{flag:"--db",path:"dbName",defaultValue:"mikrodb"},{flag:"--https",path:"useHttps",isFlag:!0,defaultValue:t.useHttps},{flag:"--cert",path:"sslCert",defaultValue:t.sslCert},{flag:"--key",path:"sslKey",defaultValue:t.sslKey},{flag:"--ca",path:"sslCa",defaultValue:t.sslCa},{flag:"--debug",path:"debug",isFlag:!0,defaultValue:t.debug}]}).get();r.debug&&console.log("Using configuration:",r);let{databaseDirectory:s="mikrodb",walFileName:n="wal.log",walInterval:i=1e3,encryptionKey:o}=e;e.debug&&(process.env.MIKRODB_DEBUG="true"),this.table=new Se({databaseDirectory:s,walFileName:n,walInterval:i,encryptionKey:o});let a=this.table.getWAL();this.table.getWAL().setCheckpointCallback(()=>this.checkpoint.checkpoint(!0)),this.checkpoint=new ge({table:this.table,wal:a,walFile:at(s,n),checkpointIntervalMs:2e3}),this.checkpoint.start().catch(c=>console.error("Failed to start checkpoint service:",c))}async start(){await this.table.start(),await this.checkpoint.start()}async get(e){return await this.table.get(e)}async getTableSize(e){return await this.table.getTableSize(e)}async write(e,t){return await this.table.write(e,t)}async delete(e){let{tableName:t,key:r}=e;return await this.table.delete(t,r)}async close(){await this.flush()}async flush(){await this.table.flush()}async flushWAL(){await this.table.flushWAL()}async dump(e){await this.table.dump(e)}async cleanupExpiredItems(){await this.table.cleanupExpiredItems()}};var Te=class{requests=new Map;limit;windowMs;constructor(e=100,t=60){this.limit=e,this.windowMs=t*1e3,setInterval(()=>this.cleanup(),this.windowMs)}getLimit(){return this.limit}isAllowed(e){let t=Date.now(),r=e||"unknown",s=this.requests.get(r);return(!s||s.resetTime<t)&&(s={count:0,resetTime:t+this.windowMs},this.requests.set(r,s)),s.count++,s.count<=this.limit}getRemainingRequests(e){let t=Date.now(),r=e||"unknown",s=this.requests.get(r);return!s||s.resetTime<t?this.limit:Math.max(0,this.limit-s.count)}getResetTime(e){let t=Date.now(),r=e||"unknown",s=this.requests.get(r);return!s||s.resetTime<t?Math.floor((t+this.windowMs)/1e3):Math.floor(s.resetTime/1e3)}cleanup(){let e=Date.now();for(let[t,r]of this.requests.entries())r.resetTime<e&&this.requests.delete(t)}};import{URL as ct}from"node:url";var xe=class{routes=[];globalMiddlewares=[];pathPatterns=new Map;use(e){return this.globalMiddlewares.push(e),this}register(e,t,r,s=[]){return this.routes.push({method:e,path:t,handler:r,middlewares:s}),this.pathPatterns.set(t,this.createPathPattern(t)),this}get(e,...t){let r=t.pop();return this.register("GET",e,r,t)}post(e,...t){let r=t.pop();return this.register("POST",e,r,t)}put(e,...t){let r=t.pop();return this.register("PUT",e,r,t)}delete(e,...t){let r=t.pop();return this.register("DELETE",e,r,t)}patch(e,...t){let r=t.pop();return this.register("PATCH",e,r,t)}options(e,...t){let r=t.pop();return this.register("OPTIONS",e,r,t)}match(e,t){for(let r of this.routes){if(r.method!==e)continue;let s=this.pathPatterns.get(r.path);if(!s)continue;let n=s.pattern.exec(t);if(!n)continue;let i={};return s.paramNames.forEach((o,a)=>{i[o]=n[a+1]||""}),{route:r,params:i}}return null}createPathPattern(e){let t=[],r=e.replace(/\/:[^/]+/g,s=>{let n=s.slice(2);return t.push(n),"/([^/]+)"}).replace(/\/$/,"/?");return{pattern:new RegExp(`^${r}$`),paramNames:t}}async handle(e,t){let r=e.method||"GET",s=new ct(e.url||"/",`http://${e.headers.host}`),n=s.pathname,i=this.match(r,n);if(!i)return null;let{route:o,params:a}=i,c={};s.searchParams.forEach((h,d)=>{c[d]=h});let l={req:e,res:t,params:a,query:c,body:e.body||{},headers:e.headers,path:n,state:{},raw:()=>t,binary:(h,d="application/octet-stream",m=200)=>({statusCode:m,body:h,headers:{"Content-Type":d,"Content-Length":h.length.toString()},isRaw:!0}),text:(h,d=200)=>({statusCode:d,body:h,headers:{"Content-Type":"text/plain"}}),form:(h,d=200)=>({statusCode:d,body:h,headers:{"Content-Type":"application/x-www-form-urlencoded"}}),json:(h,d=200)=>({statusCode:d,body:h,headers:{"Content-Type":"application/json"}}),html:(h,d=200)=>({statusCode:d,body:h,headers:{"Content-Type":"text/html"}}),redirect:(h,d=302)=>({statusCode:d,body:null,headers:{Location:h}}),status:function(h){return{raw:()=>t,binary:(d,m="application/octet-stream")=>({statusCode:h,body:d,headers:{"Content-Type":m,"Content-Length":d.length.toString()},isRaw:!0}),text:d=>({statusCode:h,body:d,headers:{"Content-Type":"text/plain"}}),json:d=>({statusCode:h,body:d,headers:{"Content-Type":"application/json"}}),html:d=>({statusCode:h,body:d,headers:{"Content-Type":"text/html"}}),form:d=>({statusCode:h,body:d,headers:{"Content-Type":"application/x-www-form-urlencoded"}}),redirect:(d,m=302)=>({statusCode:m,body:null,headers:{Location:d}}),status:d=>this.status(d)}}},u=[...this.globalMiddlewares,...o.middlewares];return this.executeMiddlewareChain(l,u,o.handler)}async executeMiddlewareChain(e,t,r){let s=0,n=async()=>{if(s<t.length){let i=t[s++];return i(e,n)}return r(e)};return n()}};var P=()=>({port:Number(process.env.PORT)||3e3,host:process.env.HOST||"0.0.0.0",useHttps:!1,useHttp2:!1,sslCert:"",sslKey:"",sslCa:"",debug:lt(process.env.DEBUG)||!1,rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["*"]});function lt(e){return e==="true"||e===!0}var g=P(),Ae=e=>({configFilePath:"mikroserve.config.json",args:process.argv,options:[{flag:"--port",path:"port",defaultValue:g.port},{flag:"--host",path:"host",defaultValue:g.host},{flag:"--https",path:"useHttps",defaultValue:g.useHttps,isFlag:!0},{flag:"--http2",path:"useHttp2",defaultValue:g.useHttp2,isFlag:!0},{flag:"--cert",path:"sslCert",defaultValue:g.sslCert},{flag:"--key",path:"sslKey",defaultValue:g.sslKey},{flag:"--ca",path:"sslCa",defaultValue:g.sslCa},{flag:"--ratelimit",path:"rateLimit.enabled",defaultValue:g.rateLimit.enabled,isFlag:!0},{flag:"--rps",path:"rateLimit.requestsPerMinute",defaultValue:g.rateLimit.requestsPerMinute},{flag:"--allowed",path:"allowedDomains",defaultValue:g.allowedDomains,parser:T.array},{flag:"--debug",path:"debug",defaultValue:g.debug,isFlag:!0}],config:e});import{readFileSync as E}from"node:fs";import X from"node:http";import ut from"node:http2";import ht from"node:https";var A=class{config;rateLimiter;router;constructor(e){let t=new y(Ae(e||{})).get();t.debug&&console.log("Using configuration:",t),this.config=t,this.router=new xe;let r=t.rateLimit.requestsPerMinute||P().rateLimit.requestsPerMinute;this.rateLimiter=new Te(r,60),t.rateLimit.enabled===!0&&this.use(this.rateLimitMiddleware.bind(this))}use(e){return this.router.use(e),this}get(e,...t){return this.router.get(e,...t),this}post(e,...t){return this.router.post(e,...t),this}put(e,...t){return this.router.put(e,...t),this}delete(e,...t){return this.router.delete(e,...t),this}patch(e,...t){return this.router.patch(e,...t),this}options(e,...t){return this.router.options(e,...t),this}start(){let e=this.createServer(),{port:t,host:r}=this.config;return this.setupGracefulShutdown(e),e.listen(t,r,()=>{let s=e.address(),n=this.config.useHttps?"https":"http";console.log(`MikroServe running at ${n}://${s.address!=="::"?s.address:"localhost"}:${s.port}`)}),e}createServer(){let e=this.requestHandler.bind(this);if(this.config.useHttp2){if(!this.config.sslCert||!this.config.sslKey)throw new Error("SSL certificate and key paths are required when useHttp2 is true");try{let t={key:E(this.config.sslKey),cert:E(this.config.sslCert),...this.config.sslCa?{ca:E(this.config.sslCa)}:{}};return ut.createSecureServer(t,e)}catch(t){throw t.message.includes("key values mismatch")?new Error(`SSL certificate and key do not match: ${t.message}`):t}}else if(this.config.useHttps){if(!this.config.sslCert||!this.config.sslKey)throw new Error("SSL certificate and key paths are required when useHttps is true");try{let t={key:E(this.config.sslKey),cert:E(this.config.sslCert),...this.config.sslCa?{ca:E(this.config.sslCa)}:{}};return ht.createServer(t,e)}catch(t){throw t.message.includes("key values mismatch")?new Error(`SSL certificate and key do not match: ${t.message}`):t}}return X.createServer(e)}async rateLimitMiddleware(e,t){let r=e.req.socket.remoteAddress||"unknown";return e.res.setHeader("X-RateLimit-Limit",this.rateLimiter.getLimit().toString()),e.res.setHeader("X-RateLimit-Remaining",this.rateLimiter.getRemainingRequests(r).toString()),e.res.setHeader("X-RateLimit-Reset",this.rateLimiter.getResetTime(r).toString()),this.rateLimiter.isAllowed(r)?t():{statusCode:429,body:{error:"Too Many Requests",message:"Rate limit exceeded, please try again later"},headers:{"Content-Type":"application/json"}}}async requestHandler(e,t){let r=Date.now(),s=e.method||"UNKNOWN",n=e.url||"/unknown",i=this.config.debug;try{if(this.setCorsHeaders(t,e),this.setSecurityHeaders(t,this.config.useHttps),i&&console.log(`${s} ${n}`),e.method==="OPTIONS"){if(t instanceof X.ServerResponse)t.statusCode=204,t.end();else{let a=t;a.writeHead(204),a.end()}return}try{e.body=await this.parseBody(e)}catch(a){return i&&console.error("Body parsing error:",a.message),this.respond(t,{statusCode:400,body:{error:"Bad Request",message:a.message}})}let o=await this.router.handle(e,t);return o?o._handled?void 0:this.respond(t,o):this.respond(t,{statusCode:404,body:{error:"Not Found",message:"The requested endpoint does not exist"}})}catch(o){return console.error("Server error:",o),this.respond(t,{statusCode:500,body:{error:"Internal Server Error",message:i?o.message:"An unexpected error occurred"}})}finally{i&&this.logDuration(r,s,n)}}logDuration(e,t,r){let s=Date.now()-e;console.log(`${t} ${r} completed in ${s}ms`)}async parseBody(e){return new Promise((t,r)=>{let s=[],n=0,i=1024*1024,o=!1,a=this.config.debug,c=e.headers["content-type"]||"";a&&console.log("Content-Type:",c),e.on("data",l=>{if(n+=l.length,a&&console.log(`Received chunk: ${l.length} bytes, total size: ${n}`),n>i&&!o){o=!0,a&&console.log(`Body size exceeded limit: ${n} > ${i}`),r(new Error("Request body too large"));return}o||s.push(l)}),e.on("end",()=>{if(!o){a&&console.log(`Request body complete: ${n} bytes`);try{if(s.length>0){let l=Buffer.concat(s).toString("utf8");if(c.includes("application/json"))try{t(JSON.parse(l))}catch(u){r(new Error(`Invalid JSON in request body: ${u.message}`))}else if(c.includes("application/x-www-form-urlencoded")){let u={};new URLSearchParams(l).forEach((h,d)=>{u[d]=h}),t(u)}else t(l)}else t({})}catch(l){r(new Error(`Invalid request body: ${l}`))}}}),e.on("error",l=>{o||r(new Error(`Error reading request body: ${l.message}`))})})}setCorsHeaders(e,t){let r=t.headers.origin,{allowedDomains:s=["*"]}=this.config;!r||s.length===0||s.includes("*")?e.setHeader("Access-Control-Allow-Origin","*"):s.includes(r)&&(e.setHeader("Access-Control-Allow-Origin",r),e.setHeader("Vary","Origin")),e.setHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, PATCH, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type, Authorization"),e.setHeader("Access-Control-Max-Age","86400")}setSecurityHeaders(e,t=!1){let r={"X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Content-Security-Policy":"default-src 'self'; script-src 'self'; object-src 'none'","X-XSS-Protection":"1; mode=block"};if((t||this.config.useHttp2)&&(r["Strict-Transport-Security"]="max-age=31536000; includeSubDomains"),e instanceof X.ServerResponse)Object.entries(r).forEach(([s,n])=>{e.setHeader(s,n)});else{let s=e;Object.entries(r).forEach(([n,i])=>{s.setHeader(n,i)})}}respond(e,t){let r={...t.headers||{}};(n=>typeof n.writeHead=="function"&&typeof n.end=="function")(e)?(e.writeHead(t.statusCode,r),t.body===null||t.body===void 0?e.end():t.isRaw||typeof t.body=="string"?e.end(t.body):e.end(JSON.stringify(t.body))):(console.warn("Unexpected response object type without writeHead/end methods"),e.writeHead?.(t.statusCode,r),t.body===null||t.body===void 0?e.end?.():t.isRaw||typeof t.body=="string"?e.end?.(t.body):e.end?.(JSON.stringify(t.body)))}setupGracefulShutdown(e){let t=r=>{console.log("Shutting down MikroServe server..."),r&&console.error("Error:",r),e.close(()=>{console.log("Server closed successfully"),setImmediate(()=>process.exit(r?1:0))})};process.on("SIGINT",()=>t()),process.on("SIGTERM",()=>t()),process.on("uncaughtException",t),process.on("unhandledRejection",t)}};async function Ie(){let e=new x({databaseDirectory:"mikrodb-test"});await e.start();let t=new A;return t.get("/table",async r=>{let s=r.req.body;if(!s.tableName)return r.json({statusCode:400,body:"tableName is required"});let n=await e.getTableSize(s.tableName);return n||r.json({statusCode:404,body:null}),r.json({statusCode:200,body:n})}),t.post("/get",async r=>{let s=r.req.body;if(!s.tableName)return r.json({statusCode:400,body:"tableName is required"});let n={tableName:s.tableName,key:s.key},i=de(s?.options);i&&(n.options=i);let o=await e.get(n);return o||r.json({statusCode:404,body:null}),r.json({statusCode:200,body:o})}),t.post("/write",async r=>{let s=r.req.body;if(!s.tableName||s.value===void 0)return r.json({statusCode:400,body:"tableName and value are required is required"});let n={tableName:s.tableName,key:s.key,value:s.value,expectedVersion:s.expectedVersion,expiration:s.expiration},i={concurrencyLimit:s.concurrencyLimit,flushImmediately:s.flushImmediately},o=await e.write(n,i);return r.json({statusCode:200,body:o})}),t.delete("/delete",async r=>{let s=r.req.query;if(!s.tableName||!s.key)return r.json({statusCode:400,body:"tableName and key are required"});let n={tableName:s.tableName,key:s.key},i=await e.delete(n);return r.json({statusCode:200,body:i})}),t.start(),t}async function dt(){let t=process.argv[1]?.includes("node_modules/.bin/mikrodb"),r=process.argv[1]?.includes("mikrodb/src/index.ts")&&(process.argv[2]||"")==="--force";if(t||r){console.log("\u{1F5C2}\uFE0F Welcome to MikroDB! \u2728");try{Ie()}catch(s){console.error(s)}}}dt();import I from"node:crypto";import{EventEmitter as ft}from"node:events";var v=class{async getUserById(t){return this.db.get(`user:${t}`)}async getUserByUsername(t){return(await this.db.list("user:")).find(s=>s.userName===t)||null}async createUser(t){await this.db.set(`user:${t.id}`,t)}async deleteUser(t){await this.db.delete(`user:${t}`)}async listUsers(){return this.db.list("user:")}async getUserByEmail(t){return(await this.db.list("user:")).find(s=>s.email===t)||null}async getChannelById(t){return this.db.get(`channel:${t}`)}async getChannelByName(t){return(await this.db.list("channel:")).find(s=>s.name===t)||null}async createChannel(t){await this.db.set(`channel:${t.id}`,t)}async updateChannel(t){await this.db.set(`channel:${t.id}`,t)}async deleteChannel(t){await this.db.delete(`channel:${t}`)}async listChannels(){return this.db.list("channel:")}async getMessageById(t){return await this.db.get(`message:${t}`)}async listMessagesByChannel(t){return(await this.db.list("message:")).filter(s=>s.channelId===t).sort((s,n)=>s.createdAt-n.createdAt)}async createMessage(t){await this.db.set(`message:${t.id}`,t)}async updateMessage(t){await this.db.set(`message:${t.id}`,t)}async deleteMessage(t){await this.db.delete(`message:${t}`)}async addReaction(t,r,s){let n=await this.getMessageById(t);if(!n)return null;n.reactions||(n.reactions={});let i=n.reactions[r]||[];return i.includes(s)||(n.reactions[r]=[...i,s],await this.updateMessage(n)),n}async removeReaction(t,r,s){let n=await this.getMessageById(t);return n?(!n.reactions||!n.reactions[r]||(n.reactions[r]=n.reactions[r].filter(i=>i!==s),await this.updateMessage(n)),n):null}async getServerSettings(){return this.db.get("server:settings")}async updateServerSettings(t){await this.db.set("server:settings",t)}};var D=class extends v{db;constructor(){super(),this.db=new Y}},Y=class{data={};async get(t){return this.data[t]||null}async set(t,r){this.data[t]=r}async delete(t){delete this.data[t]}async list(t){let r=[];for(let s in this.data)s.startsWith(t)&&r.push(this.data[s]);return r}};var N=class{config;db;eventEmitter;generalChannelName="General";constructor(t,r){this.config=t,this.db=r||new D,this.eventEmitter=new ft,this.initialize()}async initialize(){let t=this.config.initialUser.id,r=this.config.initialUser.userName,s=this.config.initialUser.email;await this.db.getUserById(t)||await this.db.createUser({id:t||I.randomUUID(),userName:r||s.split("@")[0],email:s,isAdmin:!0,createdAt:Date.now()}),await this.db.getChannelByName(this.generalChannelName)||await this.db.createChannel({id:I.randomUUID(),name:this.generalChannelName,createdAt:Date.now(),createdBy:this.config.initialUser.id}),this.scheduleMessageCleanup()}scheduleMessageCleanup(){setInterval(async()=>{let s=await this.db.listChannels(),n=24*60*60*1e3,i=Date.now()-this.config.messageRetentionDays*n;for(let o of s){let a=await this.db.listMessagesByChannel(o.id);for(let c of a)c.createdAt<i&&(await this.db.deleteMessage(c.id),this.emitEvent({type:"DELETE_MESSAGE",payload:{id:c.id,channelId:o.id}}))}},36e5)}async getServerSettings(){return await this.db.getServerSettings()}async updateServerSettings(t){return await this.db.updateServerSettings(t)}async listUsers(){return await this.db.listUsers()}async getUserById(t){return await this.db.getUserById(t)}async getUserByEmail(t){return await this.db.getUserByEmail(t)}async createUser(t){return await this.db.createUser(t)}async deleteUser(t){await this.db.deleteUser(t)}async addUser(t,r,s=!1){let n=await this.db.getUserById(r);if(!n)throw new Error("User not found");if(await this.db.getUserByEmail(t))throw new Error("User with this email already exists");if(s&&!n.isAdmin)throw new Error("Only administrators can add admin users");let o={id:I.randomUUID(),userName:t.split("@")[0],email:t,isAdmin:s,createdAt:Date.now(),addedBy:r};return await this.db.createUser(o),this.emitEvent({type:"NEW_USER",payload:{id:o.id,userName:o.userName,email:o.email}}),o.id}async removeUser(t,r){let s=await this.db.getUserById(t);if(!s)throw new Error("User not found");let n=await this.db.getUserById(r);if(!n)throw new Error("Requester not found");if(!n.isAdmin)throw new Error("Only administrators can remove users");if(s.isAdmin&&(await this.db.listUsers()).filter(o=>o.isAdmin).length<=1)throw new Error("Cannot remove the last administrator");await this.db.deleteUser(t),this.emitEvent({type:"REMOVE_USER",payload:{id:t,email:s.email}})}async exitUser(t){let r=await this.db.getUserById(t);if(!r)throw new Error("User not found");await this.db.deleteUser(t),this.emitEvent({type:"USER_EXIT",payload:{id:t,userName:r.userName}})}async createChannel(t,r){if(await this.db.getChannelByName(t))throw new Error(`Channel with name "${t}" already exists`);let n={id:I.randomUUID(),name:t,createdAt:Date.now(),createdBy:r};return await this.db.createChannel(n),this.emitEvent({type:"NEW_CHANNEL",payload:n}),n}async updateChannel(t,r,s){let n=await this.db.getChannelById(t);if(!n)throw new Error("Channel not found");let i=await this.db.getUserById(s);if(!i)throw new Error("User not found");if(n.createdBy!==s&&!i.isAdmin)throw new Error("You can only edit channels you created");let o=await this.db.getChannelById(t);if(o&&o?.name===this.generalChannelName)throw new Error(`The ${this.generalChannelName} channel cannot be renamed`);if(o&&o?.id!==t)throw new Error(`Channel with name "${r}" already exists`);return n.name=r,n.updatedAt=Date.now(),await this.db.updateChannel(n),this.emitEvent({type:"UPDATE_CHANNEL",payload:n}),n}async deleteChannel(t,r){let s=await this.db.getChannelById(t);if(!s)throw new Error("Channel not found");let n=await this.db.getUserById(r);if(!n)throw new Error("User not found");if(s.createdBy!==r&&!n.isAdmin)throw new Error("You can only delete channels you created");if(s.name.toLowerCase()===this.generalChannelName.toLowerCase())throw new Error("The General channel cannot be deleted");let i=await this.db.listMessagesByChannel(t);for(let o of i)await this.db.deleteMessage(o.id);await this.db.deleteChannel(t),this.emitEvent({type:"DELETE_CHANNEL",payload:{id:t,name:s.name}})}async listChannels(){return await this.db.listChannels()}async createMessage(t,r,s,n=[]){let i=await this.db.getUserById(r);if(!i)throw new Error("Author not found");if(!await this.db.getChannelById(s))throw new Error("Channel not found");let a=Date.now(),c={id:I.randomUUID(),author:{id:r,userName:i.userName},images:n,content:t,channelId:s,createdAt:a,updatedAt:a,reactions:{}};await this.db.createMessage(c);let l=await this.db.listMessagesByChannel(s);if(l.length>this.config.maxMessagesPerChannel){let u=l[0];await this.db.deleteMessage(u.id),this.emitEvent({type:"DELETE_MESSAGE",payload:{id:u.id,channelId:s}})}return this.emitEvent({type:"NEW_MESSAGE",payload:c}),c}async updateMessage(t,r,s,n){let i=await this.db.getMessageById(t);if(!i)throw new Error("Message not found");if(i.author.id!==r)throw new Error("You can only edit your own messages");return i.content=s,n&&(i.images=n),i.updatedAt=Date.now(),await this.db.updateMessage(i),this.emitEvent({type:"UPDATE_MESSAGE",payload:i}),i}async deleteMessage(t,r){let s=await this.db.getMessageById(t);if(!s)throw new Error("Message not found");let n=await this.db.getUserById(r);if(!n)throw new Error("User not found");if(s.author.id!==r&&!n.isAdmin)throw new Error("You can only delete your own messages");await this.db.deleteMessage(t),this.emitEvent({type:"DELETE_MESSAGE",payload:{id:t,channelId:s.channelId}})}async getMessagesByChannel(t){return await this.db.listMessagesByChannel(t)}async addReaction(t,r,s){if(!await this.db.getUserById(r))throw new Error("User not found");if(!await this.db.getMessageById(t))throw new Error("Message not found");let o=await this.db.addReaction(t,r,s);if(!o)throw new Error("Failed to add reaction");return this.emitEvent({type:"NEW_REACTION",payload:{messageId:t,userId:r,reaction:s}}),o}async removeReaction(t,r,s){if(!await this.db.getUserById(r))throw new Error("User not found");if(!await this.db.getMessageById(t))throw new Error("Message not found");let o=await this.db.removeReaction(t,r,s);if(!o)throw new Error("Failed to remove reaction");return this.emitEvent({type:"DELETE_REACTION",payload:{messageId:t,userId:r,reaction:s}}),o}emitEvent(t){console.log(`Emitting event ${t.type}`,t.payload),this.eventEmitter.emit("sse",t)}subscribeToEvents(t){let r=s=>t(s);return this.eventEmitter.on("sse",r),()=>this.eventEmitter.off("sse",r)}};import{randomBytes as mt}from"node:crypto";import{existsSync as Me,mkdirSync as pt,readFileSync as gt,writeFileSync as yt}from"node:fs";import{join as $e}from"node:path";var wt=2,bt=["jpg","jpeg","png","webp","svg"];async function Le(e,t,r){let s=new A(e.server);console.log("Dev mode?",e.devMode),e.devMode&&s.post("/auth/dev-login",async i=>{if(!i.body.email)return i.json({error:"Email is required"},400);let{email:o}=i.body;try{let a=await r.listUsers(),c=a.find(u=>u.email===o);c||(c={id:crypto.randomUUID(),userName:o.split("@")[0],email:o,isAdmin:a.length===0,createdAt:Date.now()},await r.createUser(c));let l=t.generateJsonWebToken(c);return i.json({user:c,token:l},200)}catch(a){return console.error("Dev login error:",a),i.json({error:a.message},400)}}),s.post("/auth/login",async i=>{if(!i.body.email)return i.json({error:"Email is required"},400);let{email:o}=i.body,a=await t.createMagicLink({email:o,ip:i.req.socket.remoteAddress});return a?i.json({success:!0,message:a.message},200):i.json({error:"Failed to create magic link"},400)}),s.post("/auth/logout",n,async i=>{let a=i.body.refreshToken;if(!a)return i.json({error:"Missing refresh token"},400);let c=await t.logout(a);return i.json(c,200)}),s.get("/auth/me",n,async i=>i.json({user:i.state.user},200)),s.post("/auth/verify",async i=>{let o=i.body,c=(i.headers.authorization||"").split(" ")[1];if(!c)return i.json({error:"Token is required"},400);let l;try{if(l=await t.verifyToken({email:o.email,token:c}),!l)return i.json({error:"Invalid token"},400)}catch{return i.json({error:"Invalid token"},400)}return i.json(l,200)}),s.post("/auth/refresh",async i=>{let a=i.body.refreshToken,c=await t.refreshAccessToken(a);return i.json(c,200)}),s.get("/auth/sessions",n,async i=>{let o=i.headers.authorization;if(!o||!o.startsWith("Bearer "))return i.json(null,401);let a=i.body,c=o.split(" ")[1],u={email:t.verify(c).sub},h=await t.getSessions({body:a,user:u});return i.json(h,200)}),s.delete("/auth/sessions",n,async i=>{let o=i.headers.authorization;if(!o||!o.startsWith("Bearer "))return i.json(null,401);let a=i.body,c=o.split(" ")[1],u={email:t.verify(c).sub},h=await t.revokeSessions({body:a,user:u});return i.json(h,200)}),s.post("/users/add",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let{email:a,role:c}=i.body;if(!a)return i.json({error:"Email is required"},400);try{if(c==="admin"&&!o.isAdmin)return i.json({error:"Only administrators can add admin users"},403);let l=await r.addUser(a,o.id,c==="admin");return i.json({success:!0,userId:l},200)}catch(l){return i.json({error:l.message},400)}}),s.get("/users",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);try{let a=await r.listUsers();return i.json({users:a},200)}catch(a){return i.json({error:a.message},400)}}),s.delete("/users/:id",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.id;if(a===o.id)return i.json({error:"You cannot remove your own account"},400);try{return await r.removeUser(a,o.id),i.json({success:!0},200)}catch(c){return i.json({error:c.message},400)}}),s.post("/users/exit",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);try{return o.isAdmin&&(await r.listUsers()).filter(c=>c.isAdmin).length<=1?i.json({error:"Cannot exit as the last administrator"},400):(await r.exitUser(o.id),i.json({success:!0,message:"You have exited the server"},200))}catch(a){return i.json({error:a.message},400)}}),s.get("/channels",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);let a=await r.listChannels();return i.json({channels:a},200)}),s.post("/channels",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let{name:a}=i.body;if(!a)return i.json({error:"Channel name is required"},400);try{let c=await r.createChannel(a,o.id);return i.json({channel:c},200)}catch(c){return i.json({error:c.message},400)}}),s.get("/channels/:channelId/messages",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);let a=i.params.channelId;try{let c=await r.getMessagesByChannel(a),l=await Promise.all(c.map(async u=>{let h=await r.getUserById(u.author.id);return{...u,author:{id:h?.id,userName:h?.userName||"Unknown User"}}}));return i.json({messages:l},200)}catch(c){return i.json({error:c.message},400)}}),s.post("/channels/:channelId/messages",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.channelId,{content:c}=i.body;if(!c)return i.json({error:"Message content is required"},400);try{let l=await r.createMessage(c,o.id,a);return i.json({message:l},200)}catch(l){return i.json({error:l.message},400)}}),s.put("/channels/:channelId",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.channelId,{name:c}=i.body;if(!c)return i.json({error:"Channel name is required"},400);try{let l=await r.updateChannel(a,c,o.id);return i.json({channel:l},200)}catch(l){return i.json({error:l.message},400)}}),s.delete("/channels/:channelId",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.channelId;try{return await r.deleteChannel(a,o.id),i.json({success:!0},200)}catch(c){return i.json({error:c.message},400)}}),s.put("/messages/:messageId",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.messageId,c=i.body?.content||"TODO",l=i.body?.images;if(!c)return i.json({error:"Message content is required"},400);try{let u=await r.updateMessage(a,o.id,c,l);return i.json({message:u},200)}catch(u){return i.json({error:u.message},400)}}),s.delete("/messages/:messageId",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.messageId;try{return await r.deleteMessage(a,o.id),i.json({success:!0},200)}catch(c){return i.json({error:c.message},400)}}),s.post("/messages/:messageId/reactions",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.messageId,{reaction:c}=i.body;if(!c)return i.json({error:"Reaction is required"},400);try{let l=await r.addReaction(a,o.id,c);return l?i.json({message:l},200):i.json({error:`Message with ID ${a} not found`},401)}catch(l){return console.error(`Error adding reaction to message ${a}:`,l),i.json({error:l.message},400)}}),s.delete("/messages/:messageId/reactions",n,async i=>{let o=i.state.user;if(!o)return i.json({error:"Unauthorized"},401);let a=i.params.messageId,{reaction:c}=i.body;if(!c)return i.json({error:"Reaction is required"},400);try{let l=await r.removeReaction(a,o.id,c);return l?i.json({message:l},200):i.json({error:`Message with ID ${a} not found`},404)}catch(l){return console.error(`Error removing reaction from message ${a}:`,l),i.json({error:l.message},400)}}),s.post("/channels/:channelId/messages/image",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);try{let{filename:a,image:c}=await i.body;if(!c)return i.json({error:"No image provided"},400);let l=a.split(".").pop();if(!l)return i.json({error:"Missing file extension"},400);if(!bt.includes(l))return i.json({error:"Unsupported file format"},400);let u=Buffer.from(c,"base64"),h=wt*1024*1024;if(u.length>h)return i.json({error:"Image too large"},400);let d=`${process.cwd()}/uploads`;Me(d)||pt(d);let m=`${Date.now()}-${mt(1).toString("hex")}.${l}`,M=$e(d,m);return yt(M,u),i.json({success:!0,filename:m})}catch(a){return console.error("Image upload error:",a),i.json({success:!1,error:a instanceof Error?a.message:"Upload failed"},500)}}),s.get("/channels/:channelId/messages/image/:filename",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);try{let{filename:a}=i.params,c=`${process.cwd()}/uploads`,l=$e(c,a);if(!Me(l))return i.json({error:"Image not found"},404);let u=gt(l),h=a.split(".").pop()?.toLowerCase(),d="application/octet-stream";return h==="jpg"||h==="jpeg"?d="image/jpeg":h==="png"?d="image/png":h==="webp"?d="image/webp":h==="svg"&&(d="image/svg+xml"),i.binary(u,d)}catch(a){return i.json({success:!1,error:a instanceof Error?a.message:"Image fetch failed"},500)}}),s.get("/server/settings",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);try{let a=await r.getServerSettings();return i.json(a||{name:"MikroChat"},200)}catch(a){return i.json({error:a.message},400)}}),s.put("/server/settings",n,async i=>{if(!i.state.user)return i.json({error:"Unauthorized"},401);let{name:a}=i.body;if(!a)return i.json({error:"Server name is required"},400);try{return await r.updateServerSettings({name:a}),i.json({name:a},200)}catch(c){return i.json({error:c.message},400)}}),s.get("/events",async i=>{let o=null,a=i.query.token;if(a)try{let u=t.verify(a);o=await r.getUserByEmail(u.email||u.sub)}catch(u){console.error("SSE token validation error:",u)}if(!o&&i.headers.authorization?.startsWith("Bearer ")){let u=i.headers.authorization.substring(7);try{let h=t.verify(u);o=await r.getUserByEmail(h.email||h.sub)}catch(h){console.error("SSE header validation error:",h)}}if(!o)return console.log("SSE unauthorized access attempt"),{statusCode:401,body:{error:"Unauthorized"},headers:{"Content-Type":"application/json"}};console.log(`SSE connection established for user ${o.id}`),i.res.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"});let c=setInterval(()=>{if(!i.res.writable){clearInterval(c);return}i.res.write(`: ping `)},3e4);i.res.write(`: `),i.res.write(`data: ${JSON.stringify({type:"CONNECTED",payload:{message:"SSE connection established",timestamp:new Date().toISOString(),userId:o.id}})} `);let l=r.subscribeToEvents(u=>{if(!i.res.writable){l();return}try{console.log(`Sending SSE event to user ${o.id}: ${u.type}`),i.res.write(`data: ${JSON.stringify(u)} `)}catch(h){console.error("Error sending SSE event:",h),i.res.writable&&i.res.end(),l(),clearInterval(c)}});return i.req.on("close",()=>{console.log(`SSE connection closed for user ${o.id}`),l(),clearInterval(c)}),i.req.on("error",u=>{console.error(`SSE connection error for user ${o.id}:`,u),l(),clearInterval(c),i.res.writable&&i.res.end()}),i.res.on("error",u=>{console.error(`SSE response error for user ${o.id}:`,u),l(),clearInterval(c)}),{statusCode:200,_handled:!0,body:null}});async function n(i,o){let a={error:"Unauthorized",message:"Authentication required"},c=i.headers.authorization;if(!c||!c.startsWith("Bearer "))return i.status(401).json(a);let l=c.split(" ")[1];if(!l)return i.status(401).json(a);let u=t.verify(l),h=await r.getUserByEmail(u.email||u.sub);return i.state.user=h,o()}s.start()}var j=class extends v{db;mikroDb;constructor(t){super(),this.mikroDb=t,this.db=new Z(t)}async start(){await this.mikroDb.start()}async close(){await this.mikroDb.close()}},Z=class{db;tableName;constructor(t,r="mikrochat_db"){this.db=t,this.tableName=r}async get(t){try{let r=await this.db.get({tableName:this.tableName,key:t});return r??null}catch(r){return console.error(`Error getting key ${t}:`,r),null}}async set(t,r,s){let n=s?Date.now()+s*1e3:0;await this.db.write({tableName:this.tableName,key:t,value:r,expiration:n})}async delete(t){await this.db.delete({tableName:this.tableName,key:t})}async list(t){try{return(await this.db.get({tableName:this.tableName})||[]).filter(n=>Array.isArray(n)&&typeof n[0]=="string"&&n[0].startsWith(t)).map(n=>n[1].value)}catch(r){return console.error(`Error listing with prefix ${t}:`,r),[]}}};var Ue=()=>{let e=Et(process.env.DEBUG)||!1;return{auth:{jwtSecret:process.env.AUTH_JWT_SECRET||"your-jwt-secret",magicLinkExpirySeconds:900,jwtExpirySeconds:900,refreshTokenExpirySeconds:604800,maxActiveSessions:3,appUrl:process.env.APP_URL||"http://127.0.0.1:3000",debug:e},email:{emailSubject:"Your Secure Login Link",user:process.env.EMAIL_USER||"",host:process.env.EMAIL_HOST||"",password:process.env.EMAIL_PASSWORD||"",port:465,secure:!0,maxRetries:2,debug:e},storage:{databaseDirectory:"mikrochat_db",encryptionKey:process.env.STORAGE_KEY||"",debug:e},server:{port:Number(process.env.PORT)||3e3,host:process.env.HOST||"localhost",useHttps:!1,sslCert:"",sslKey:"",sslCa:"",rateLimit:{enabled:!0,requestsPerMinute:100},allowedDomains:["http://127.0.0.1:8080"],devMode:!1,debug:e},chat:{initialUser:{id:process.env.INITIAL_USER_ID||crypto.randomUUID(),userName:process.env.INITIAL_USER_NAME||"",email:process.env.INITIAL_USER_EMAIL||""},messageRetentionDays:30,maxMessagesPerChannel:100}}};function Et(e){return e==="true"||e===!0}var f=Ue(),Be={configFilePath:"mikroauth.config.json",args:process.argv,options:[{flag:"--jwtSecret",path:"auth.jwtSecret",defaultValue:f.auth.jwtSecret},{flag:"--magicLinkExpirySeconds",path:"auth.magicLinkExpirySeconds",defaultValue:f.auth.magicLinkExpirySeconds},{flag:"--jwtExpirySeconds",path:"auth.jwtExpirySeconds",defaultValue:f.auth.jwtExpirySeconds},{flag:"--refreshTokenExpirySeconds",path:"auth.refreshTokenExpirySeconds",defaultValue:f.auth.refreshTokenExpirySeconds},{flag:"--maxActiveSessions",path:"auth.maxActiveSessions",defaultValue:f.auth.maxActiveSessions},{flag:"--appUrl",path:"auth.appUrl",defaultValue:f.auth.appUrl},{flag:"--debug",path:"auth.debug",isFlag:!0,defaultValue:f.auth.debug},{flag:"--emailSubject",path:"email.emailSubject",defaultValue:"Your Secure Login Link"},{flag:"--emailHost",path:"email.host",defaultValue:f.email.host},{flag:"--emailUser",path:"email.user",defaultValue:f.email.user},{flag:"--emailPassword",path:"email.password",defaultValue:f.email.password},{flag:"--emailPort",path:"email.port",defaultValue:f.email.host},{flag:"--emailSecure",path:"email.secure",isFlag:!0,defaultValue:f.email.secure},{flag:"--emailMaxRetries",path:"email.maxRetries",defaultValue:f.email.maxRetries},{flag:"--debug",path:"email.debug",isFlag:!0,defaultValue:f.email.debug},{flag:"--db",path:"storage.databaseDirectory",defaultValue:f.storage.databaseDirectory},{flag:"--encryptionKey",path:"storage.encryptionKey",defaultValue:f.storage.encryptionKey},{flag:"--debug",path:"storage.debug",defaultValue:f.storage.debug},{flag:"--port",path:"server.port",defaultValue:f.server.port},{flag:"--host",path:"server.host",defaultValue:f.server.host},{flag:"--https",path:"server.useHttps",isFlag:!0,defaultValue:f.server.useHttps},{flag:"--cert",path:"server.sslCert",defaultValue:f.server.sslCert},{flag:"--key",path:"server.sslKey",defaultValue:f.server.sslKey},{flag:"--ca",path:"server.sslCa",defaultValue:f.server.sslCa},{flag:"--ratelimit",path:"server.rateLimit.enabled",defaultValue:f.server.rateLimit.enabled,isFlag:!0},{flag:"--rps",path:"server.rateLimit.requestsPerMinute",defaultValue:f.server.rateLimit.requestsPerMinute},{flag:"--allowed",path:"server.allowedDomains",defaultValue:f.server.allowedDomains,parser:T.array},{flag:"--dev",path:"server.devMode",defaultValue:f.server.devMode,isFlag:!0},{flag:"--debug",path:"server.debug",isFlag:!0,defaultValue:f.server.debug},{flag:"--initialUserId",path:"chat.initialUser.id",defaultValue:f.chat.initialUser.id},{flag:"--initialUserUsername",path:"chat.initialUser.userName",defaultValue:f.chat.initialUser.userName},{flag:"--initialUserEmail",path:"chat.initialUser.email",defaultValue:f.chat.initialUser.email},{flag:"--messageRetentionDays",path:"chat.messageRetentionDays",defaultValue:f.chat.messageRetentionDays},{flag:"--maxMessagesPerChannel",path:"chat.maxMessagesPerChannel",defaultValue:f.chat.maxMessagesPerChannel}]};async function vt(){let e=Ct(),t=new x(e.storage);await t.start();let r=new V(t),s=new j(t),n=new O(e.email),i=new W(e.auth,n,r),o=new N(e.chat,s);await Le(e.server,i,o)}function Ct(){let e=new y({...Be,config:{email:{user:"test@starlite.systems",password:"77FUX6F2GSJSF93W",host:"smtp.protonmail.ch",port:465,secure:!0}}}).get();return e.auth.debug&&console.log("Using configuration:",e),e}vt();