UNPKG

authrix

Version:

Lightweight, flexible authentication library for Node.js and TypeScript.

7 lines 8.19 kB
'use strict';var mongodb=require('mongodb');var r=class t{constructor(){this.client=null;this.db=null;this.usersCollection=null;this.twoFactorCollection=null;this.config=null;this.connectionPromise=null;this.isConnected=false;}static getInstance(){return this.instance||(this.instance=new t),this.instance}loadConfig(){if(this.config)return this.config;let e=process.env.MONGO_URI,i=process.env.DB_NAME,n=process.env.AUTH_COLLECTION||"users",o=process.env.TWO_FACTOR_COLLECTION||"two_factor_codes";if(!e||!i)throw new Error(`Missing required MongoDB environment variables: - MONGO_URI: MongoDB connection string - DB_NAME: Database name Optional: - AUTH_COLLECTION: Users collection name (default: "users") - TWO_FACTOR_COLLECTION: 2FA codes collection name (default: "two_factor_codes")`);return e.startsWith("mongodb+srv://")&&(process.env.MONGO_MAX_POOL_SIZE=process.env.MONGO_MAX_POOL_SIZE||"10",process.env.MONGO_MIN_POOL_SIZE=process.env.MONGO_MIN_POOL_SIZE||"2",process.env.MONGO_SOCKET_TIMEOUT=process.env.MONGO_SOCKET_TIMEOUT||"30000",process.env.MONGO_SERVER_SELECTION_TIMEOUT=process.env.MONGO_SERVER_SELECTION_TIMEOUT||"5000"),this.config={uri:e,dbName:i,authCollection:n,twoFactorCollection:o,options:{maxPoolSize:parseInt(process.env.MONGO_MAX_POOL_SIZE||"10"),minPoolSize:parseInt(process.env.MONGO_MIN_POOL_SIZE||"2"),socketTimeoutMS:parseInt(process.env.MONGO_SOCKET_TIMEOUT||"30000"),serverSelectionTimeoutMS:parseInt(process.env.MONGO_SERVER_SELECTION_TIMEOUT||"5000")}},this.config}async connect(){if(this.connectionPromise)return this.connectionPromise;if(!(this.isConnected&&this.client))return this.connectionPromise=this.performConnection(),this.connectionPromise}async performConnection(){try{let e=this.loadConfig(),i={};e.uri.startsWith("mongodb+srv://")&&(i.retryWrites=!0),this.client=new mongodb.MongoClient(e.uri,{maxPoolSize:e.options?.maxPoolSize,minPoolSize:e.options?.minPoolSize,socketTimeoutMS:e.options?.socketTimeoutMS,serverSelectionTimeoutMS:e.options?.serverSelectionTimeoutMS,...i}),await this.client.connect(),this.db=this.client.db(e.dbName),this.usersCollection=this.db.collection(e.authCollection),this.twoFactorCollection=this.db.collection(e.twoFactorCollection),await this.createIndexes(),this.isConnected=!0;}catch(e){throw this.connectionPromise=null,new Error(`MongoDB connection failed: ${e instanceof Error?e.message:"Unknown error"}`)}}async createIndexes(){if(!this.usersCollection||!this.twoFactorCollection)return;let e=[[{email:1},{unique:true,background:true}],[{username:1},{unique:true,sparse:true,background:true}],[{createdAt:-1},{background:true}],[{emailVerified:1,createdAt:-1},{background:true}]],i=[[{id:1},{unique:true,background:true}],[{userId:1,type:1,isUsed:1},{background:true}],[{expiresAt:1},{expireAfterSeconds:0,background:true}],[{createdAt:-1},{background:true}]];await Promise.all([...e.map(([n,o])=>this.usersCollection.createIndex(n,o).catch(()=>{})),...i.map(([n,o])=>this.twoFactorCollection.createIndex(n,o).catch(()=>{}))]);}async getUsers(){return this.usersCollection||await this.connect(),this.usersCollection}async getTwoFactorCodes(){return this.twoFactorCollection||await this.connect(),this.twoFactorCollection}async disconnect(){this.client&&(await this.client.close(),this.client=null,this.db=null,this.usersCollection=null,this.twoFactorCollection=null,this.isConnected=false,this.connectionPromise=null);}reset(){this.disconnect(),this.config=null;}};function g(t){return t.toLowerCase().trim()}function p(t){return t?t.toLowerCase().trim():void 0}function m(t){return {id:t.e.toString(),email:t.email,password:t.password,createdAt:t.createdAt,emailVerified:t.emailVerified||false,emailVerifiedAt:t.emailVerifiedAt,twoFactorEnabled:t.twoFactorEnabled||false,username:t.username,firstName:t.firstName,lastName:t.lastName,fullName:t.fullName,profilePicture:t.profilePicture,authMethod:t.authMethod,authProvider:t.authProvider}}var b={async findUserByEmail(t){let n=await(await r.getInstance().getUsers()).findOne({email:g(t)},{projection:{e:1,email:1,password:1,createdAt:1,emailVerified:1,emailVerifiedAt:1,twoFactorEnabled:1,username:1,firstName:1,lastName:1,fullName:1,profilePicture:1,authMethod:1,authProvider:1}});return n&&n.e?m(n):null},async getUserByEmail(t){return this.findUserByEmail(t)},async findUserById(t){if(!mongodb.ObjectId.isValid(t))return null;let n=await(await r.getInstance().getUsers()).findOne({e:new mongodb.ObjectId(t)},{projection:{e:1,email:1,password:1,createdAt:1,emailVerified:1,emailVerifiedAt:1,twoFactorEnabled:1,username:1,firstName:1,lastName:1,fullName:1,profilePicture:1,authMethod:1,authProvider:1}});return n&&n.e?m(n):null},async createUser({email:t,password:e,username:i,firstName:n,lastName:o,fullName:s,profilePicture:a,authMethod:d,authProvider:h}){let I=await r.getInstance().getUsers(),C=g(t),f=p(i),O=new Date,l={email:C,password:e,createdAt:O,emailVerified:false,twoFactorEnabled:false,loginCount:0,authMethod:d,authProvider:h};f&&(l.username=f),n&&(l.firstName=n.trim()),o&&(l.lastName=o.trim()),s&&(l.fullName=s.trim()),a&&(l.profilePicture=a);try{let c=await I.insertOne(l);if(!c.insertedId)throw new Error("Failed to create user: No ID generated");return {id:c.insertedId.toString(),email:C,password:e,createdAt:O,emailVerified:!1,twoFactorEnabled:!1,username:f,firstName:n?.trim(),lastName:o?.trim(),fullName:s?.trim(),profilePicture:a,authMethod:d,authProvider:h}}catch(c){if(c.code===11e3){let w=c.keyPattern?.email?"email":c.keyPattern?.username?"username":"field",P=w==="email"?t:i;throw new Error(`${w.charAt(0).toUpperCase()+w.slice(1)} "${P}" is already in use`)}throw new Error(`Failed to create user: ${c.message}`)}},async updateUser(t,e){if(!mongodb.ObjectId.isValid(t))throw new Error("Invalid user ID");let n=await r.getInstance().getUsers(),o={};e.email!==void 0&&(o.email=g(e.email)),e.password!==void 0&&(o.password=e.password),e.emailVerified!==void 0&&(o.emailVerified=e.emailVerified),e.emailVerifiedAt!==void 0&&(o.emailVerifiedAt=e.emailVerifiedAt),e.twoFactorEnabled!==void 0&&(o.twoFactorEnabled=e.twoFactorEnabled),e.username!==void 0&&(o.username=p(e.username)),e.firstName!==void 0&&(o.firstName=e.firstName?.trim()),e.lastName!==void 0&&(o.lastName=e.lastName?.trim()),e.fullName!==void 0&&(o.fullName=e.fullName?.trim()),e.profilePicture!==void 0&&(o.profilePicture=e.profilePicture);try{let s=await n.findOneAndUpdate({e:new mongodb.ObjectId(t)},{$set:o},{returnDocument:"after",projection:{e:1,email:1,password:1,createdAt:1,emailVerified:1,emailVerifiedAt:1,twoFactorEnabled:1,username:1,firstName:1,lastName:1,fullName:1,profilePicture:1}});if(!s||!s.e)throw new Error("User not found");return m(s)}catch(s){if(s.code===11e3){let a=s.keyPattern?.email?"email":s.keyPattern?.username?"username":"field",d=a==="email"?e.email:e.username;throw new Error(`${a.charAt(0).toUpperCase()+a.slice(1)} "${d}" is already in use`)}throw s}},async findUserByUsername(t){let n=await(await r.getInstance().getUsers()).findOne({username:p(t)},{projection:{e:1,email:1,password:1,createdAt:1,emailVerified:1,emailVerifiedAt:1,twoFactorEnabled:1,username:1,firstName:1,lastName:1,fullName:1,profilePicture:1}});return n&&n.e?m(n):null},async storeTwoFactorCode(t){await(await r.getInstance().getTwoFactorCodes()).insertOne(t);},async getTwoFactorCode(t){return await(await r.getInstance().getTwoFactorCodes()).findOne({id:t})},async updateTwoFactorCode(t,e){await(await r.getInstance().getTwoFactorCodes()).updateOne({id:t},{$set:e});},async getUserTwoFactorCodes(t,e){let n=await r.getInstance().getTwoFactorCodes(),o={userId:t,isUsed:false,expiresAt:{$gt:new Date}};return e&&(o.type=e),await n.find(o).sort({createdAt:-1}).limit(10).toArray()},async cleanupExpiredTwoFactorCodes(){return (await(await r.getInstance().getTwoFactorCodes()).deleteMany({$or:[{expiresAt:{$lt:new Date}},{isUsed:true,createdAt:{$lt:new Date(Date.now()-24*60*60*1e3)}}]})).deletedCount||0}},_={async disconnect(){await r.getInstance().disconnect();},reset(){r.getInstance().reset();},async healthCheck(){try{return await(await r.getInstance().getUsers()).findOne({},{projection:{e:1}}),!0}catch{return false}}}; exports.a=b;exports.b=_;