UNPKG

mikroauth

Version:

Dead-simple magic link authentication that is useful, lightweight, and uncluttered.

81 lines (75 loc) 21.3 kB
#!/usr/bin/env node 'use strict';var mikroconf=require('mikroconf'),mikrodb=require('mikrodb'),mikroserve=require('mikroserve'),y=require('crypto'),url=require('url'),events=require('events'),mikromail=require('mikromail');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var y__default=/*#__PURE__*/_interopDefault(y);var m=()=>{let o=A(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 A(o){return o==="true"||o===true}var v=class{algorithm="HS256";secret="HS256";constructor(e){if(process.env.NODE_ENV==="production"&&(!e||e.length<32||e===m().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),i={...e,iat:s};t.exp!==void 0&&(i.exp=s+t.exp),t.notBefore!==void 0&&(i.nbf=s+t.notBefore),t.issuer&&(i.iss=t.issuer),t.audience&&(i.aud=t.audience),t.subject&&(i.sub=t.subject),t.jwtid&&(i.jti=t.jwtid);let a=this.base64UrlEncode(JSON.stringify(r)),n=this.base64UrlEncode(JSON.stringify(i)),l=`${a}.${n}`,c=this.createSignature(l);return `${l}.${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,i]=e.split("."),a=`${s}.${i}`;if(this.createSignature(a)!==r.signature)throw new Error("Invalid signature");let l=r.payload,c=Math.floor(Date.now()/1e3),d=t.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(t.issuer&&l.iss!==t.issuer)throw new Error("Invalid issuer");if(t.audience&&l.aud!==t.audience)throw new Error("Invalid audience");if(t.subject&&l.sub!==t.subject)throw new Error("Invalid subject");return l}decode(e){let t=e.split(".");if(t.length!==3)throw new Error("Invalid token format");try{let[r,s,i]=t,a=JSON.parse(this.base64UrlDecode(r)),n=JSON.parse(this.base64UrlDecode(s));return {header:a,payload:n,signature:i}}catch{throw new Error("Failed to decode token")}}createSignature(e){let t=y__default.default.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 b=class{templates;constructor(e){e?this.templates=e:this.templates=M;}getText(e,t){return this.templates.textVersion(e,t).trim()}getHtml(e,t){return this.templates.htmlVersion(e,t).trim()}},M={textVersion:(o,e)=>` Click this link to login: ${o} Security Information: - Expires in ${e} minutes - Can only be used once - Should only be used by you If you didn't request this link, please ignore this email. `,htmlVersion:(o,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="${o}" 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 ${e} 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 f=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=[];}};var p=class{data=new Map;collections=new Map;expiryEmitter=new events.EventEmitter;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(a=>r.test(a)),i=Array.from(this.collections.keys()).filter(a=>r.test(a));return [...new Set([...s,...i])]}};function T(o){if(!o||o.trim()===""||(o.match(/@/g)||[]).length!==1)return false;let[t,r]=o.split("@");return !(!t||!r||o.includes("..")||!$(t)||!j(r))}function $(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 j(o){if(o.startsWith("[")&&o.endsWith("]")){let t=o.slice(1,-1);return t.startsWith("IPv6:")?N(t.slice(5)):R(t)}let e=o.split(".");if(e.length===0)return false;for(let 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){let t=e[e.length-1];if(!/^[a-zA-Z]{2,}$/.test(t))return false}return true}function R(o){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(o)}function N(o){if(!/^[a-fA-F0-9:]+$/.test(o))return false;let t=o.split(":");return !(t.length<2||t.length>8)}var u=m(),E=o=>{let e={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 o&&(e.config=o),e};var k={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(e,t,r){let s=new mikroconf.MikroConf(E({auth:e.auth,email:e.email})).get();s.auth.debug&&console.log("Using configuration:",s),this.config=s,this.email=t||new f,this.storage=r||new p,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===m().auth.jwtSecret&&(console.error("WARNING: Using default secrets in production environment!"),process.exit(1));}generateToken(e){let t=Date.now().toString(),r=y__default.default.randomBytes(32).toString("hex");return y__default.default.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 y__default.default.randomBytes(40).toString("hex")}async trackSession(e,t,r){let s=`sessions:${e}`;if(await this.storage.getCollectionSize(s)>=this.config.auth.maxActiveSessions){let a=await this.storage.removeOldestFromCollection(s);a&&await this.storage.delete(`refresh:${a}`);}await this.storage.addToCollection(s,t,this.config.auth.refreshTokenExpirySeconds),await this.storage.set(`refresh:${t}`,JSON.stringify(r),this.config.auth.refreshTokenExpirySeconds);}generateMagicLinkUrl(e){let{token:t,email:r}=e;try{return new url.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){let{email:t,ip:r}=e;if(!T(t))throw new Error("Valid email required");try{let s=this.generateToken(t),i=`magic_link:${s}`,a={email:t,ipAddress:r||"unknown",createdAt:Date.now()};await this.storage.set(i,JSON.stringify(a),this.config.auth.magicLinkExpirySeconds);let n=await this.storage.findKeys("magic_link:*");for(let d of n){if(d===i)continue;let h=await this.storage.get(d);if(h)try{JSON.parse(h).email===t&&await this.storage.delete(d);}catch{}}let l=this.generateMagicLinkUrl({token:s,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:k.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}`,i=await this.storage.get(s);if(!i)throw new Error("Invalid or expired token");let a=JSON.parse(i);if(a.email!==r)throw new Error("Email mismatch");let n=a.username,l=a.role;await this.storage.delete(s);let c=y__default.default.randomBytes(16).toString("hex"),d=this.generateRefreshToken(),h={sub:r,username:n,role:l,jti:c,lastLogin:a.createdAt,metadata:{ip:a.ipAddress},exp:Math.floor(Date.now()/1e3)+60*60*24},g=this.jwtService.sign(h,{exp:this.config.auth.jwtExpirySeconds});return await this.trackSession(r,d,{...a,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(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 i=r.username,a=r.role,n=y__default.default.randomBytes(16).toString("hex"),l={sub:s,username:i,role:a,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:${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")}}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:k.logoutSuccess};let s=JSON.parse(t).email;if(!s)throw new Error("Invalid refresh token data");await this.storage.delete(`refresh:${e}`);let i=`sessions:${s}`;return await this.storage.removeFromCollection(i,e),{message:k.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}`,a=(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(a)).filter(Boolean);return n.sort((l,c)=>c.createdAt-l.createdAt),{sessions:n}}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}`,i=await this.storage.getCollection(s);for(let a of i)r&&a===r||await this.storage.delete(`refresh:${a}`);return await this.storage.delete(s),r&&await this.storage.get(`refresh:${r}`)&&await this.storage.addToCollection(s,r,this.config.auth.refreshTokenExpirySeconds),{message:k.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 i=this.verify(s);e.user={email:i.sub},t();}catch{throw new Error("Invalid or expired token")}}catch(r){t(r);}}};var S=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}`,i=r?Date.now()+r*1e3:void 0;await this.db.write({tableName:this.TABLE_NAME,key:s,value:t,expiration:i});}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}`,i=await this.db.get({tableName:this.TABLE_NAME,key:s}),a=[];i&&(a=JSON.parse(i)),a.includes(t)||a.push(t);let n=r?Date.now()+r*1e3:void 0;await this.db.write({tableName:this.TABLE_NAME,key:s,value:JSON.stringify(a),expiration:n});}async removeFromCollection(e,t){let r=`${this.PREFIX_COLLECTION}${e}`,s=await this.db.get({tableName:this.TABLE_NAME,key:r});if(!s)return;let i=JSON.parse(s);i=i.filter(a=>a!==t),await this.db.write({tableName:this.TABLE_NAME,key:r,value:JSON.stringify(i)});}async getCollection(e){let 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){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 i=s.shift();return await this.db.write({tableName:this.TABLE_NAME,key:t,value:JSON.stringify(s)}),i}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(i=>{let a=i[0];return typeof a=="string"&&a.startsWith(this.PREFIX_KV)}).map(i=>i[0].substring(this.PREFIX_KV.length)).filter(i=>r.test(i))}};var P=class{email;sender;constructor(e){this.sender=e.user,this.email=new mikromail.MikroMail({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});}};async function C(){let o=new mikroconf.MikroConf(E()).get(),e=new P(o.email),t=new S(new mikrodb.MikroDB(o.storage));await t.start(),await F(o,e,t);}async function F(o,e,t){let r=t||new p,s=e||new f,i=new x(o,s,r),a=new mikroserve.MikroServe(o.server);return a.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)}),a.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)}),a.post("/refresh",async n=>{let c=n.req.body.refreshToken,d=await i.refreshAccessToken(c);return n.json(d,200)}),a.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},w=await i.getSessions({body:c,user:g});return n.json(w,200)}),a.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},w=await i.revokeSessions({body:c,user:g});return n.json(w,200)}),a.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)}),a.start(),a}async function K(){let e=process.argv[1]?.includes("node_modules/.bin/mikroauth"),t=(process.argv[2]||"")==="--forceAuth";if(e||t){console.log("\u{1F510} Welcome to MikroAuth! \u2728");try{await C();}catch(r){console.error(r);}}}K();