mikroauth
Version:
Dead-simple magic link authentication that is useful, lightweight, and uncluttered.
81 lines (75 loc) • 23 kB
JavaScript
;var mikroconf=require('mikroconf'),pikodb=require('pikodb'),mikroserve=require('mikroserve'),w=require('crypto'),url=require('url'),events=require('events'),mikromail=require('mikromail');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var w__default=/*#__PURE__*/_interopDefault(w);var f=()=>{let a=j(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:a},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:a},storage:{databaseDirectory:"mikroauth",encryptionKey:process.env.STORAGE_KEY||"",debug:a},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:a}}};function j(a){return a==="true"||a===true}var v=class{algorithm="HS256";secret="HS256";constructor(t){if(process.env.NODE_ENV==="production"&&(!t||t.length<32||t===f().auth.jwtSecret))throw new Error("Production environment requires a strong JWT secret (min 32 chars)");this.secret=t;}sign(t,e={}){let r={alg:this.algorithm,typ:"JWT"},s=Math.floor(Date.now()/1e3),i={...t,iat:s};e.exp!==void 0&&(i.exp=s+e.exp),e.notBefore!==void 0&&(i.nbf=s+e.notBefore),e.issuer&&(i.iss=e.issuer),e.audience&&(i.aud=e.audience),e.subject&&(i.sub=e.subject),e.jwtid&&(i.jti=e.jwtid);let o=this.base64UrlEncode(JSON.stringify(r)),n=this.base64UrlEncode(JSON.stringify(i)),l=`${o}.${n}`,c=this.createSignature(l);return `${l}.${c}`}verify(t,e={}){let r=this.decode(t);if(r.header.alg!==this.algorithm)throw new Error(`Invalid algorithm. Expected ${this.algorithm}, got ${r.header.alg}`);let[s,i]=t.split("."),o=`${s}.${i}`;if(this.createSignature(o)!==r.signature)throw new Error("Invalid signature");let l=r.payload,c=Math.floor(Date.now()/1e3),d=e.clockTolerance||0;if(l.exp!==void 0&&l.exp+d<c)throw new Error("Token expired");if(l.nbf!==void 0&&l.nbf-d>c)throw new Error("Token not yet valid");if(e.issuer&&l.iss!==e.issuer)throw new Error("Invalid issuer");if(e.audience&&l.aud!==e.audience)throw new Error("Invalid audience");if(e.subject&&l.sub!==e.subject)throw new Error("Invalid subject");return l}decode(t){let e=t.split(".");if(e.length!==3)throw new Error("Invalid token format");try{let[r,s,i]=e,o=JSON.parse(this.base64UrlDecode(r)),n=JSON.parse(this.base64UrlDecode(s));return {header:o,payload:n,signature:i}}catch{throw new Error("Failed to decode token")}}createSignature(t){let e=w__default.default.createHmac("sha256",this.secret).update(t).digest();return this.base64UrlEncode(e)}base64UrlEncode(t){let e;return typeof t=="string"?e=Buffer.from(t):e=t,e.toString("base64").replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")}base64UrlDecode(t){let e=t.replace(/-/g,"+").replace(/_/g,"/");switch(e.length%4){case 0:break;case 2:e+="==";break;case 3:e+="=";break;default:throw new Error("Invalid base64 string")}return Buffer.from(e,"base64").toString()}};var b=class{templates;constructor(t){t?this.templates=t:this.templates=$;}getText(t,e,r){return this.templates.textVersion(t,e,r).trim()}getHtml(t,e,r){return this.templates.htmlVersion(t,e,r).trim()}},$={textVersion:(a,t,e)=>`
Click this link to login: ${a}
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.
`,htmlVersion:(a,t,e)=>`
<!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="${a}" 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>
`};var m=class{constructor(t){this.options=t;}sentEmails=[];async sendMail(t){this.sentEmails.push(t),this.options?.logToConsole&&(console.log("Email sent:"),console.log(`From: ${t.from}`),console.log(`To: ${t.to}`),console.log(`Subject: ${t.subject}`),console.log(`Text: ${t.text}`)),this.options?.onSend&&this.options.onSend(t);}getSentEmails(){return [...this.sentEmails]}clearSentEmails(){this.sentEmails=[];}};var y=class{data=new Map;collections=new Map;expiryEmitter=new events.EventEmitter;expiryCheckInterval;constructor(t=1e3){this.expiryCheckInterval=setInterval(()=>this.checkExpiredItems(),t);}destroy(){clearInterval(this.expiryCheckInterval),this.data.clear(),this.collections.clear(),this.expiryEmitter.removeAllListeners();}checkExpiredItems(){let t=Date.now();for(let[e,r]of this.data.entries())r.expiry&&r.expiry<t&&(this.data.delete(e),this.expiryEmitter.emit("expired",e));for(let[e,r]of this.collections.entries())r.expiry&&r.expiry<t&&(this.collections.delete(e),this.expiryEmitter.emit("expired",e));}async set(t,e,r){let s=r?Date.now()+r*1e3:null;this.data.set(t,{value:e,expiry:s});}async get(t){let e=this.data.get(t);return e?e.expiry&&e.expiry<Date.now()?(this.data.delete(t),null):e.value:null}async delete(t){this.data.delete(t),this.collections.delete(t);}async addToCollection(t,e,r){this.collections.has(t)||this.collections.set(t,{items:[],expiry:r?Date.now()+r*1e3:null});let s=this.collections.get(t);s&&(r&&(s.expiry=Date.now()+r*1e3),s.items.push(e));}async removeFromCollection(t,e){let r=this.collections.get(t);r&&(r.items=r.items.filter(s=>s!==e));}async getCollection(t){let e=this.collections.get(t);return e?[...e.items]:[]}async getCollectionSize(t){let e=this.collections.get(t);return e?e.items.length:0}async removeOldestFromCollection(t){let e=this.collections.get(t);return !e||e.items.length===0?null:e.items.shift()||null}async findKeys(t){let e=t.replace(/\*/g,".*").replace(/\?/g,"."),r=new RegExp(`^${e}$`),s=Array.from(this.data.keys()).filter(o=>r.test(o)),i=Array.from(this.collections.keys()).filter(o=>r.test(o));return [...new Set([...s,...i])]}};function C(a){if(!a||a.trim()===""||(a.match(/@/g)||[]).length!==1)return false;let[e,r]=a.split("@");return !(!e||!r||a.includes("..")||!V(e)||!D(r))}function V(a){return a.startsWith('"')&&a.endsWith('"')?!a.slice(1,-1).includes('"'):a.length>64||a.startsWith(".")||a.endsWith(".")?false:/^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$/.test(a)}function D(a){if(a.startsWith("[")&&a.endsWith("]")){let e=a.slice(1,-1);return e.startsWith("IPv6:")?B(e.slice(5)):O(e)}let t=a.split(".");if(t.length===0)return false;for(let e of t)if(!e||e.length>63||!/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(e))return false;if(t.length>1){let e=t[t.length-1];if(!/^[a-zA-Z]{2,}$/.test(e))return false}return true}function O(a){return /^(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}$/.test(a)}function B(a){if(!/^[a-fA-F0-9:]+$/.test(a))return false;let e=a.split(":");return !(e.length<2||e.length>8)}var u=f(),k=a=>{let t={configFilePath:"mikroauth.config.json",args:process.argv,options:[{flag:"--jwtSecret",path:"auth.jwtSecret",defaultValue:u.auth.jwtSecret},{flag:"--magicLinkExpirySeconds",path:"auth.magicLinkExpirySeconds",defaultValue:u.auth.magicLinkExpirySeconds},{flag:"--jwtExpirySeconds",path:"auth.jwtExpirySeconds",defaultValue:u.auth.jwtExpirySeconds},{flag:"--refreshTokenExpirySeconds",path:"auth.refreshTokenExpirySeconds",defaultValue:u.auth.refreshTokenExpirySeconds},{flag:"--maxActiveSessions",path:"auth.maxActiveSessions",defaultValue:u.auth.maxActiveSessions},{flag:"--appUrl",path:"auth.appUrl",defaultValue:u.auth.appUrl},{flag:"--debug",path:"auth.debug",isFlag:true,defaultValue:u.auth.debug},{flag:"--emailSubject",path:"email.emailSubject",defaultValue:"Your Secure Login Link"},{flag:"--emailHost",path:"email.host",defaultValue:u.email.host},{flag:"--emailUser",path:"email.user",defaultValue:u.email.user},{flag:"--emailPassword",path:"email.password",defaultValue:u.email.password},{flag:"--emailPort",path:"email.port",defaultValue:u.email.port},{flag:"--emailSecure",path:"email.secure",isFlag:true,defaultValue:u.email.secure},{flag:"--emailMaxRetries",path:"email.maxRetries",defaultValue:u.email.maxRetries},{flag:"--debug",path:"email.debug",isFlag:true,defaultValue:u.email.debug},{flag:"--dir",path:"storage.databaseDirectory",defaultValue:u.storage.databaseDirectory},{flag:"--encryptionKey",path:"storage.encryptionKey",defaultValue:u.storage.encryptionKey},{flag:"--debug",path:"storage.debug",defaultValue:u.storage.debug},{flag:"--port",path:"server.port",defaultValue:u.server.port},{flag:"--host",path:"server.host",defaultValue:u.server.host},{flag:"--https",path:"server.useHttps",isFlag:true,defaultValue:u.server.useHttps},{flag:"--https",path:"server.useHttp2",isFlag:true,defaultValue:u.server.useHttp2},{flag:"--cert",path:"server.sslCert",defaultValue:u.server.sslCert},{flag:"--key",path:"server.sslKey",defaultValue:u.server.sslKey},{flag:"--ca",path:"server.sslCa",defaultValue:u.server.sslCa},{flag:"--ratelimit",path:"server.rateLimit.enabled",defaultValue:u.server.rateLimit.enabled,isFlag:true},{flag:"--rps",path:"server.rateLimit.requestsPerMinute",defaultValue:u.server.rateLimit.requestsPerMinute},{flag:"--allowed",path:"server.allowedDomains",defaultValue:u.server.allowedDomains,parser:mikroconf.parsers.array},{flag:"--debug",path:"server.debug",isFlag:true,defaultValue:u.server.debug}]};return a&&(t.config=a),t};var E={linkSent:"If a matching account was found, a magic link has been sent.",revokedSuccess:"All other sessions revoked successfully.",logoutSuccess:"Logged out successfully."},x=class{config;email;storage;jwtService;templates;constructor(t,e,r){let s=new mikroconf.MikroConf(k({auth:t.auth,email:t.email})).get();s.auth.debug&&console.log("Using configuration:",s),this.config=s,this.email=e||new m,this.storage=r||new y,this.jwtService=new v(s.auth.jwtSecret),this.templates=new b(s?.auth.templates),this.checkIfUsingDefaultCredentialsInProduction();}checkIfUsingDefaultCredentialsInProduction(){process.env.NODE_ENV==="production"&&this.config.auth.jwtSecret===f().auth.jwtSecret&&(console.error("WARNING: Using default secrets in production environment!"),process.exit(1));}generateToken(t){let e=Date.now().toString(),r=w__default.default.randomBytes(32).toString("hex");return w__default.default.createHash("sha256").update(`${t}:${e}:${r}`).digest("hex")}generateJsonWebToken(t){return this.jwtService.sign({sub:t.id,email:t.email,username:t.username,role:t.role,exp:Math.floor(Date.now()/1e3)+3600*24})}generateRefreshToken(){return w__default.default.randomBytes(40).toString("hex")}async trackSession(t,e,r){let s=`sessions:${t}`;if(await this.storage.getCollectionSize(s)>=this.config.auth.maxActiveSessions){let o=await this.storage.removeOldestFromCollection(s);o&&await this.storage.delete(`refresh:${o}`);}await this.storage.addToCollection(s,e,this.config.auth.refreshTokenExpirySeconds),await this.storage.set(`refresh:${e}`,JSON.stringify(r),this.config.auth.refreshTokenExpirySeconds);}generateMagicLinkUrl(t){let{token:e,email:r,appUrl:s}=t,i=s||this.config.auth.appUrl;try{return new url.URL(i),`${i}?token=${encodeURIComponent(e)}&email=${encodeURIComponent(r)}`}catch{throw new Error("Invalid base URL configuration")}}async createMagicLink(t){let{email:e,ip:r,metadata:s,appUrl:i,subject:o}=t;if(!C(e))throw new Error("Valid email required");try{let n=this.generateToken(e),l=`magic_link:${n}`,c={email:e,ipAddress:r||"unknown",createdAt:Date.now()};await this.storage.set(l,JSON.stringify(c),this.config.auth.magicLinkExpirySeconds);let d=await this.storage.findKeys("magic_link:*");for(let p of d){if(p===l)continue;let A=await this.storage.get(p);if(A)try{JSON.parse(A).email===e&&await this.storage.delete(p);}catch{}}let h=this.generateMagicLinkUrl({token:n,email:e,appUrl:i}),g=Math.ceil(this.config.auth.magicLinkExpirySeconds/60);return await this.email.sendMail({from:this.config.email.user,to:e,subject:o||this.config.email.emailSubject,text:this.templates.getText(h,g,s),html:this.templates.getHtml(h,g,s)}),{message:E.linkSent}}catch(n){throw console.error(`Failed to process magic link request: ${n}`),new Error("Failed to process magic link request")}}async createToken(t){let{email:e,username:r,role:s,ip:i}=t;if(!C(e))throw new Error("Valid email required");try{let o=w__default.default.randomBytes(16).toString("hex"),n=this.generateRefreshToken(),l=Date.now(),c={sub:e,username:r,role:s,jti:o,lastLogin:l,metadata:{ip:i||"unknown"},exp:Math.floor(Date.now()/1e3)+3600*24},d=this.jwtService.sign(c,{exp:this.config.auth.jwtExpirySeconds}),h={email:e,username:r,role:s,ipAddress:i||"unknown",tokenId:o,createdAt:l,lastLogin:l};return await this.trackSession(e,n,h),{accessToken:d,refreshToken:n,exp:this.config.auth.jwtExpirySeconds,tokenType:"Bearer"}}catch(o){throw console.error("Token creation error:",o),new Error("Token creation failed")}}async verifyToken(t){let{token:e,email:r}=t;try{let s=`magic_link:${e}`,i=await this.storage.get(s);if(!i)throw new Error("Invalid or expired token");let o=JSON.parse(i);if(o.email!==r)throw new Error("Email mismatch");let n=o.username,l=o.role;await this.storage.delete(s);let c=w__default.default.randomBytes(16).toString("hex"),d=this.generateRefreshToken(),h={sub:r,username:n,role:l,jti:c,lastLogin:o.createdAt,metadata:{ip:o.ipAddress},exp:Math.floor(Date.now()/1e3)+3600*24},g=this.jwtService.sign(h,{exp:this.config.auth.jwtExpirySeconds});return await this.trackSession(r,d,{...o,tokenId:c,createdAt:Date.now()}),{accessToken:g,refreshToken:d,exp:this.config.auth.jwtExpirySeconds,tokenType:"Bearer"}}catch(s){throw console.error("Token verification error:",s),new Error("Verification failed")}}async refreshAccessToken(t){try{let e=await this.storage.get(`refresh:${t}`);if(!e)throw new Error("Invalid or expired refresh token");let r=JSON.parse(e),s=r.email;if(!s)throw new Error("Invalid refresh token data");let i=r.username,o=r.role,n=w__default.default.randomBytes(16).toString("hex"),l={sub:s,username:i,role:o,jti:n,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:${t}`,JSON.stringify(r),this.config.auth.refreshTokenExpirySeconds),{accessToken:c,refreshToken:t,exp:this.config.auth.jwtExpirySeconds,tokenType:"Bearer"}}catch(e){throw console.error("Token refresh error:",e),new Error("Token refresh failed")}}verify(t){try{return this.jwtService.verify(t)}catch{throw new Error("Invalid token")}}async logout(t){try{if(!t||typeof t!="string")throw new Error("Refresh token is required");let e=await this.storage.get(`refresh:${t}`);if(!e)return {message:E.logoutSuccess};let s=JSON.parse(e).email;if(!s)throw new Error("Invalid refresh token data");await this.storage.delete(`refresh:${t}`);let i=`sessions:${s}`;return await this.storage.removeFromCollection(i,t),{message:E.logoutSuccess}}catch(e){throw console.error("Logout error:",e),new Error("Logout failed")}}async getSessions(t){try{if(!t.user?.email)throw new Error("User not authenticated");let e=t.user.email,r=t.body?.refreshToken,s=`sessions:${e}`,o=(await this.storage.getCollection(s)).map(async l=>{try{let c=await this.storage.get(`refresh:${l}`);if(!c)return await this.storage.removeFromCollection(s,l),null;let d=JSON.parse(c);return {id:`${l.substring(0,8)}...`,createdAt:d.createdAt||0,lastLogin:d.lastLogin||d.createdAt||0,lastUsed:d.lastUsed||d.createdAt||0,metadata:{ip:d.ipAddress},isCurrentSession:l===r}}catch{return await this.storage.removeFromCollection(s,l),null}}),n=(await Promise.all(o)).filter(Boolean);return n.sort((l,c)=>c.createdAt-l.createdAt),{sessions:n}}catch(e){throw console.error("Get sessions error:",e),new Error("Failed to fetch sessions")}}async revokeSessions(t){try{if(!t.user?.email)throw new Error("User not authenticated");let e=t.user.email,r=t.body?.refreshToken,s=`sessions:${e}`,i=await this.storage.getCollection(s);for(let o of i)r&&o===r||await this.storage.delete(`refresh:${o}`);return await this.storage.delete(s),r&&await this.storage.get(`refresh:${r}`)&&await this.storage.addToCollection(s,r,this.config.auth.refreshTokenExpirySeconds),{message:E.revokedSuccess}}catch(e){throw console.error("Revoke sessions error:",e),new Error("Failed to revoke sessions")}}authenticate(t,e){try{let r=t.headers?.authorization;if(!r||!r.startsWith("Bearer "))throw new Error("Authentication required");let s=r.split(" ")[1];try{let i=this.verify(s);t.user={email:i.sub},e();}catch{throw new Error("Invalid or expired token")}}catch(r){e(r);}}};var S=class{key;algorithm="aes-256-gcm";keyLength=32;constructor(t){this.key=w.scryptSync(t,"mikroauth-salt",this.keyLength);}encrypt(t){let e=w.randomBytes(12),r=w.createCipheriv(this.algorithm,this.key,e),s=Buffer.concat([r.update(t,"utf8"),r.final()]),i=r.getAuthTag();return `${e.toString("hex")}:${i.toString("hex")}:${s.toString("hex")}`}decrypt(t){let e=t.split(":");if(e.length!==3)throw new Error("Invalid encrypted data format");let[r,s,i]=e,o=Buffer.from(r,"hex"),n=Buffer.from(s,"hex"),l=Buffer.from(i,"hex"),c=w.createDecipheriv(this.algorithm,this.key,o);return c.setAuthTag(n),Buffer.concat([c.update(l),c.final()]).toString("utf8")}};var T=class{db;encryption;PREFIX_KV="kv:";PREFIX_COLLECTION="coll:";TABLE_NAME="mikroauth";constructor(t,e){this.db=t,e&&(this.encryption=new S(e));}async start(){await this.db.start();}async close(){await this.db.close();}async set(t,e,r){let s=`${this.PREFIX_KV}${t}`,i=this.encryption?this.encryption.encrypt(e):e,o=r?Date.now()+r*1e3:void 0;await this.db.write(this.TABLE_NAME,s,i,o);}async get(t){let e=`${this.PREFIX_KV}${t}`,r=await this.db.get(this.TABLE_NAME,e);return r?this.encryption?this.encryption.decrypt(r):r:null}async delete(t){let e=`${this.PREFIX_KV}${t}`;await this.db.delete(this.TABLE_NAME,e);}async addToCollection(t,e,r){let s=`${this.PREFIX_COLLECTION}${t}`,i=await this.db.get(this.TABLE_NAME,s),o=[];if(i){let d=this.encryption?this.encryption.decrypt(i):i;o=JSON.parse(d);}o.includes(e)||o.push(e);let n=JSON.stringify(o),l=this.encryption?this.encryption.encrypt(n):n,c=r?Date.now()+r*1e3:void 0;await this.db.write(this.TABLE_NAME,s,l,c);}async removeFromCollection(t,e){let r=`${this.PREFIX_COLLECTION}${t}`,s=await this.db.get(this.TABLE_NAME,r);if(!s)return;let i=this.encryption?this.encryption.decrypt(s):s,o=JSON.parse(i);o=o.filter(c=>c!==e);let n=JSON.stringify(o),l=this.encryption?this.encryption.encrypt(n):n;await this.db.write(this.TABLE_NAME,r,l);}async getCollection(t){let e=`${this.PREFIX_COLLECTION}${t}`,r=await this.db.get(this.TABLE_NAME,e);if(!r)return [];let s=this.encryption?this.encryption.decrypt(r):r;return JSON.parse(s)}async getCollectionSize(t){return (await this.getCollection(t)).length}async removeOldestFromCollection(t){let e=`${this.PREFIX_COLLECTION}${t}`,r=await this.db.get(this.TABLE_NAME,e);if(!r)return null;let s=this.encryption?this.encryption.decrypt(r):r,i=JSON.parse(s);if(i.length===0)return null;let o=i.shift(),n=JSON.stringify(i),l=this.encryption?this.encryption.encrypt(n):n;return await this.db.write(this.TABLE_NAME,e,l),o}async findKeys(t){let e=t.replace(/\./g,"\\.").replace(/\*/g,".*").replace(/\?/g,"."),r=new RegExp(`^${e}$`),s=await this.db.get(this.TABLE_NAME);return Array.isArray(s)?s.filter(i=>{let o=i[0];return typeof o=="string"&&o.startsWith(this.PREFIX_KV)}).map(i=>i[0].substring(this.PREFIX_KV.length)).filter(i=>r.test(i)):[]}};var P=class{email;sender;constructor(t){this.sender=t.user,this.email=new mikromail.MikroMail({config:t});}async sendMail(t){await this.email.send({from:this.sender,to:t.to,cc:t.cc,bcc:t.bcc,subject:t.subject,text:t.text,html:t.html});}};async function L(){let a=new mikroconf.MikroConf(k()).get(),t=new P(a.email),e=new T(new pikodb.PikoDB({databaseDirectory:a.storage.databaseDirectory}),a.storage.encryptionKey);await e.start(),await Y(a,t,e);}async function Y(a,t,e){let r=e||new y,s=t||new m,i=new x(a,s,r),o=new mikroserve.MikroServe(a.server);return o.post("/login",async n=>{let l=n.req.body,c=await i.createMagicLink({email:l.email,ip:n.req.socket.remoteAddress});return c||n.json(null,404),n.json(c,200)}),o.post("/verify",async n=>{let l=n.req.body,d=(n.req.headers.authorization||"").split(" ")[1],h=await i.verifyToken({email:l.email,token:d});return h?n.json(h,200):n.json(null,404)}),o.post("/refresh",async n=>{let c=n.req.body.refreshToken,d=await i.refreshAccessToken(c);return n.json(d,200)}),o.get("/sessions",async n=>{let l=n.req.headers.authorization;if(!l||!l.startsWith("Bearer "))return n.json(null,401);let c=n.req.body,d=l.split(" ")[1],g={email:i.verify(d).sub},p=await i.getSessions({body:c,user:g});return n.json(p,200)}),o.delete("/sessions",async n=>{let l=n.req.headers.authorization;if(!l||!l.startsWith("Bearer "))return n.json(null,401);let c=n.req.body,d=l.split(" ")[1],g={email:i.verify(d).sub},p=await i.revokeSessions({body:c,user:g});return n.json(p,200)}),o.post("/logout",async n=>{let l=n.req.headers.authorization;if(!l||!l.startsWith("Bearer "))return n.json(null,401);let d=n.req.body.refreshToken,h=await i.logout(d);return n.json(h,200)}),o.start(),o}async function Z(){let t=process.argv[1]?.includes("node_modules/.bin/mikroauth"),e=(process.argv[2]||"")==="--forceAuth";if(t||e){console.log("\u{1F510} Welcome to MikroAuth! \u2728");try{await L();}catch(r){console.error(r);}}}Z();