UNPKG

authrix

Version:

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

1 lines 12.8 kB
'use strict';var chunk5ZKHY3X3_cjs=require('./chunk-5ZKHY3X3.cjs'),E=require('bcryptjs'),m=require('argon2'),crypto=require('crypto');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var E__namespace=/*#__PURE__*/_interopNamespace(E);var m__namespace=/*#__PURE__*/_interopNamespace(m);var b=class{constructor(){this.MAX_PASSWORD_LENGTH=256;this.MAX_ATTEMPTS_PER_MINUTE=5;this.MAX_ATTEMPTS_PER_HOUR=20;this.BLOCK_DURATION=15*60*1e3;this.RATE_LIMIT_WINDOW=60*1e3;this.DEV_GENERATED=false;this.DEV_DERIVED=false;this.STRICT_MODE=(process.env.AUTHRIX_STRICT_PASSWORD_POLICY||"").toLowerCase()==="true";let e=parseInt(process.env.AUTHRIX_BCRYPT_ROUNDS||"",10);this.BCRYPT_ROUNDS=this.validateBcryptRounds(e);let s=parseInt(process.env.AUTHRIX_ARGON2_TIME_COST||"",10),t=parseInt(process.env.AUTHRIX_ARGON2_MEMORY_COST||"",10),r=parseInt(process.env.AUTHRIX_ARGON2_PARALLELISM||"",10),n=!!process.env.JEST_WORKER_ID||process.env.NODE_ENV==="test";this.ARGON2_TIME_COST=!isNaN(s)&&s>=2&&s<=6?s:n&&!this.STRICT_MODE?2:3,this.STRICT_MODE?this.ARGON2_MEMORY_COST=!isNaN(t)&&t>=32768?t:65536:this.ARGON2_MEMORY_COST=isNaN(t)?n?2048:32768:Math.max(n?1024:4096,t),this.ARGON2_PARALLELISM=!isNaN(r)&&r>=1&&r<=8?r:this.STRICT_MODE?4:n?2:3,this.STRICT_MODE?(this.MIN_PASSWORD_LENGTH=12,this.MIN_ENTROPY=50):(this.MIN_PASSWORD_LENGTH=8,this.MIN_ENTROPY=30);let i=process.env.AUTHRIX_PASSWORD_PEPPER,l=process.env.NODE_ENV==="production";if(!i&&l)throw new Error("AUTHRIX_PASSWORD_PEPPER must be configured in production");if(i)this.PEPPER=i;else {let u=chunk5ZKHY3X3_cjs.b&&typeof chunk5ZKHY3X3_cjs.b.jwtSecret=="string"?chunk5ZKHY3X3_cjs.b.jwtSecret:"";u&&u.length>=12?(this.PEPPER=crypto.createHash("sha256").update(`authrix-pepper:${u}`).digest("hex"),this.DEV_DERIVED=true,process.env.AUTHRIX_SUPPRESS_DEV_PEPPER_WARNING||(process.env.AUTHRIX_SUPPRESS_DEV_PEPPER_WARNING="1")):(this.PEPPER=this.generateDefaultPepper(),this.DEV_GENERATED=true);}this.validateConfiguration();}validateBcryptRounds(e){if(!isNaN(e)){if(this.STRICT_MODE){if(e>=12&&e<=20)return e}else if(e>=6&&e<=20)return e}return 14}generateDefaultPepper(){if(process.env.NODE_ENV==="production")throw new Error("Password pepper must be configured in production");return crypto.randomBytes(32).toString("hex")}validateConfiguration(){if(this.STRICT_MODE&&this.BCRYPT_ROUNDS<12)throw new Error("Bcrypt rounds must be at least 12 for production security");if(!this.PEPPER||this.PEPPER.length<32)throw new Error("Password pepper must be at least 32 characters")}getPepper(){if(!process.env.AUTHRIX_PASSWORD_PEPPER&&this.DEV_GENERATED){let e=chunk5ZKHY3X3_cjs.b&&typeof chunk5ZKHY3X3_cjs.b.jwtSecret=="string"?chunk5ZKHY3X3_cjs.b.jwtSecret:"";e&&e.length>=12&&(this.PEPPER=crypto.createHash("sha256").update(`authrix-pepper:${e}`).digest("hex"),this.DEV_GENERATED=false,this.DEV_DERIVED=true);}return this.PEPPER}},o=new b,_=class{constructor(){this.store=new Map;this.cleanupInterval=setInterval(()=>this.cleanup(),5*60*1e3),this.cleanupInterval.unref&&this.cleanupInterval.unref();}checkLimit(e){let s=Date.now(),t=this.store.get(e)||{attempts:[],blocked:false};return t.blocked&&t.blockUntil&&t.blockUntil>s?{allowed:false,retryAfter:Math.ceil((t.blockUntil-s)/1e3)}:(t.blocked&&t.blockUntil&&t.blockUntil<=s&&(t.blocked=false,t.blockUntil=void 0,t.attempts=[]),t.attempts=t.attempts.filter(r=>s-r<o.RATE_LIMIT_WINDOW),t.attempts.length>=o.MAX_ATTEMPTS_PER_MINUTE?(t.blocked=true,t.blockUntil=s+o.BLOCK_DURATION,this.store.set(e,t),{allowed:false,retryAfter:Math.ceil(o.BLOCK_DURATION/1e3)}):(t.attempts.push(s),this.store.set(e,t),{allowed:true}))}cleanup(){let e=Date.now();for(let[s,t]of this.store.entries()){let r=t.attempts.some(i=>e-i<o.RATE_LIMIT_WINDOW*2),n=t.blocked&&t.blockUntil&&t.blockUntil>e;!r&&!n&&this.store.delete(s);}if(this.store.size>1e4){let s=Array.from(this.store.entries());s.sort((t,r)=>{let n=Math.max(...t[1].attempts,0),i=Math.max(...r[1].attempts,0);return n-i}),this.store.clear(),s.slice(-5e3).forEach(([t,r])=>this.store.set(t,r));}}destroy(){this.cleanupInterval&&clearInterval(this.cleanupInterval);}},v=new _,T=class a{static{this.COMMON_PASSWORDS=new Set(["password","123456","password123","admin","letmein","welcome","monkey","1234567890","qwerty","abc123","Password1","password1"]);}static{this.KEYBOARD_PATTERNS=[/qwerty/i,/asdfgh/i,/zxcvbn/i,/qwertyuiop/i,/\d{6,}/,/(.)\1{5,}/];}validate(e,s,t){let r=[],i={...{minLength:o.MIN_PASSWORD_LENGTH,maxLength:o.MAX_PASSWORD_LENGTH,requireLowercase:true,requireUppercase:true,requireNumbers:true,requireSymbols:true,minEntropy:o.MIN_ENTROPY,preventCommonPasswords:true,preventUserInfo:true},...s};(!e||e.length<i.minLength)&&r.push(`Password must be at least ${i.minLength} characters`),e.length>i.maxLength&&r.push(`Password must not exceed ${i.maxLength} characters`),i.requireLowercase&&!/[a-z]/.test(e)&&r.push("Password must contain lowercase letters"),i.requireUppercase&&!/[A-Z]/.test(e)&&r.push("Password must contain uppercase letters"),i.requireNumbers&&!/\d/.test(e)&&r.push("Password must contain numbers"),i.requireSymbols&&!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/.test(e)&&r.push("Password must contain special characters");let l=this.calculateEntropy(e);if(l<i.minEntropy){let p=o.STRICT_MODE?0:5;l<i.minEntropy-p&&r.push(`Password is too predictable (entropy: ${l.toFixed(1)} bits, required: ${i.minEntropy})`);}let u=false;for(let p of a.KEYBOARD_PATTERNS)if(p.test(e)){this.shouldFlagPattern(e)&&r.push("Password contains predictable patterns"),u=true;break}if(!u&&this.hasSequentialRun(e)&&this.shouldFlagPattern(e)&&r.push("Password contains predictable patterns"),i.preventCommonPasswords){let p=e.toLowerCase();a.COMMON_PASSWORDS.has(p)&&r.push("Password is too common");}if(i.preventUserInfo&&t&&t.length>0){let p=e.toLowerCase();for(let R of t)if(R&&p.includes(R.toLowerCase())){r.push("Password must not contain personal information");break}}let c=this.calculateStrength(e,l,r.length),d={isValid:r.length===0,errors:r,strength:c,entropy:l};return process.env.AUTHRIX_DEBUG_PASSWORDS&&d.isValid,d}calculateEntropy(e){if(!e)return 0;let s=e.match(/^(.)\1+$/),t={lowercase:26,uppercase:26,numbers:10,symbols:32,extended:128},r=0;return /[a-z]/.test(e)&&(r+=t.lowercase),/[A-Z]/.test(e)&&(r+=t.uppercase),/\d/.test(e)&&(r+=t.numbers),/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/.test(e)&&(r+=t.symbols),/[^\x00-\x7F]/.test(e)&&(r+=t.extended),r===0?0:s?Math.log2(r):e.length*Math.log2(r)}calculateStrength(e,s,t){let r=Math.min(100,s/100*100);e.length>16&&(r+=10),e.length>20&&(r+=10),r-=t*15,s<5&&(r=Math.min(r,5));let n=this.getCharacterVariety(e);return r+=n*5,Math.max(0,Math.min(100,Math.round(r)))}getCharacterVariety(e){return [/[a-z]/,/[A-Z]/,/\d/,/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/,/[^\x00-\x7F]/].filter(t=>t.test(e)).length}hasSequentialRun(e){if(!e||e.length<6)return false;let s=e,t=1,r=1;for(let n=1;n<s.length;n++){let i=s.charCodeAt(n-1),l=s.charCodeAt(n);if(l===i+1?(t+=1,r=1):l===i-1?(r+=1,t=1):(t=1,r=1),t>=6||r>=6)return true}return false}shouldFlagPattern(e){let s=this.calculateEntropy(e),t=/[a-z]/.test(e),r=/[A-Z]/.test(e),n=/\d/.test(e),i=/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?`~]/.test(e),l=[t,r,n,i].filter(Boolean).length;return !(!o.STRICT_MODE&&l>=3&&s>=o.MIN_ENTROPY+20)}},A=new T,S=class{constructor(){this.argon2HashCount=0;}async hash(e,s={}){if(typeof e!="string")throw new TypeError("Password must be a string");if(s.identifier){let{allowed:r,retryAfter:n}=v.checkLimit(s.identifier);if(!r)throw new Error(`Rate limit exceeded. Retry after ${n} seconds`)}if(!s.skipValidation){let r=A.validate(e);if(!r.isValid)throw new Error(`Invalid password: ${r.errors[0]}`)}let t=this.applyPepper(e,s.pepper||o.getPepper());try{let n=(!!process.env.JEST_WORKER_ID||process.env.NODE_ENV==="test")&&!o.STRICT_MODE,i=s.algorithm||"argon2id";return n&&this.argon2HashCount>=4&&!s.algorithm&&(i="bcrypt"),i==="argon2id"?(this.argon2HashCount+=1,await this.hashWithArgon2(t)):await this.hashWithBcrypt(t)}finally{this.clearString(e),this.clearString(t);}}async verify(e,s,t={}){if(typeof e!="string"||typeof s!="string")return {valid:false,needsRehash:false};if(!e||!s)return await this.dummyVerify(),{valid:false,needsRehash:false};if(t.identifier){let{allowed:r,retryAfter:n}=v.checkLimit(t.identifier);if(!r)throw new Error(`Rate limit exceeded. Retry after ${n} seconds`)}try{let r=this.applyPepper(e,o.getPepper()),n=!1,i=!1;if(s.startsWith("$argon2"))n=await m__namespace.verify(s,r),i=this.needsArgon2Rehash(s);else if(s.startsWith("$2"))n=await E__namespace.compare(r,s),i=this.needsBcryptRehash(s);else return await this.dummyVerify(),{valid:!1,needsRehash:!0};return {valid:n,needsRehash:i}}catch{return await this.dummyVerify(),{valid:false,needsRehash:false}}finally{this.clearString(e);}}async hashWithArgon2(e){return C(()=>m__namespace.hash(e,{type:m__namespace.argon2id,timeCost:o.ARGON2_TIME_COST,memoryCost:o.ARGON2_MEMORY_COST,parallelism:o.ARGON2_PARALLELISM}))}async hashWithBcrypt(e){return E__namespace.hash(e,o.BCRYPT_ROUNDS)}applyPepper(e,s){if(!s)return e;let t=crypto.createHash("sha256");return t.update(e+s),t.digest("base64")}async dummyVerify(){if((!!process.env.JEST_WORKER_ID||process.env.NODE_ENV==="test")&&!o.STRICT_MODE){let t=await E__namespace.hash("dummy",6);await E__namespace.compare("dummy",t);return}let s="$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG";try{await m__namespace.verify(s,"dummy");}catch{}}needsBcryptRehash(e){try{let s=e.match(/^\$2[aby]?\$(\d+)\$/);return s?parseInt(s[1],10)<o.BCRYPT_ROUNDS:!0}catch{return true}}needsArgon2Rehash(e){try{let s=e.match(/m=(\d+),t=(\d+),p=(\d+)/);if(!s)return !0;let t=parseInt(s[1],10),r=parseInt(s[2],10),n=parseInt(s[3],10);return t<o.ARGON2_MEMORY_COST||r<o.ARGON2_TIME_COST||n<o.ARGON2_PARALLELISM}catch{return true}}clearString(e){if(typeof e=="string"&&e.length>0)try{let s=Buffer.from(e);crypto.randomBytes(s.length).copy(s);}catch{}}},g=new S,M=!!process.env.JEST_WORKER_ID||process.env.NODE_ENV==="test",D=o.STRICT_MODE||!M?1/0:2,P=0,I=[];async function C(a){P>=D&&await new Promise(e=>I.push(e)),P+=1;try{return await a()}finally{P-=1;let e=I.shift();e&&e();}}var O=class{constructor(){this.charsets={lowercase:"abcdefghjkmnpqrstuvwxyz",uppercase:"ABCDEFGHJKMNPQRSTUVWXYZ",numbers:"23456789",symbols:"!@#$%^&*()_+-=[]{}|;:,.<>?",allLowercase:"abcdefghijklmnopqrstuvwxyz",allUppercase:"ABCDEFGHIJKLMNOPQRSTUVWXYZ",allNumbers:"0123456789",similar:/[lI1O0o]/g};}generate(e=16,s={}){let{includeLowercase:t=true,includeUppercase:r=true,includeNumbers:n=true,includeSymbols:i=true,excludeSimilar:l=true,minEntropy:u=50}=s;if(e<8||e>256)throw new Error("Password length must be between 8 and 256 characters");let c="",d=[];if(t){let h=l?this.charsets.lowercase:this.charsets.allLowercase;c+=h,d.push(this.secureRandomChar(h));}if(r){let h=l?this.charsets.uppercase:this.charsets.allUppercase;c+=h,d.push(this.secureRandomChar(h));}if(n){let h=l?this.charsets.numbers:this.charsets.allNumbers;c+=h,d.push(this.secureRandomChar(h));}if(i){let h=this.charsets.symbols;c+=h,d.push(this.secureRandomChar(h));}if(!c||d.length>e)throw new Error("Invalid password generation options");l&&(c=c.replace(this.charsets.similar,""));let p="",R=0,N=100;for(;R<N&&(p=this.generatePassword(e,c,d),!(A.validate(p,{minEntropy:u,preventCommonPasswords:false,preventUserInfo:false}).entropy>=u));)R++;if(R>=N)throw new Error("Failed to generate password with sufficient entropy");return p}generatePassword(e,s,t){let r=[...t];for(let n=t.length;n<e;n++)r.push(this.secureRandomChar(s));for(let n=r.length-1;n>0;n--){let i=this.secureRandomInt(n+1);[r[n],r[i]]=[r[i],r[n]];}return r.join("")}secureRandomChar(e){return e[this.secureRandomInt(e.length)]}secureRandomInt(e){let s=e,t=Math.ceil(Math.log2(s)/8),r=Math.floor(256**t/s)*s,n;do n=crypto.randomBytes(t).reduce((l,u,c)=>l+u*256**c,0);while(n>=r);return n%s}},U=new O;async function W(a,e={}){return g.hash(a,e)}async function H(a,e,s={}){return (await g.verify(a,e,s)).valid}async function k(a,e,s={}){let t=await g.verify(a,e,s);if(t.valid&&t.needsRehash&&s.updateHash){let r=await g.hash(a,{skipValidation:true});return {...t,newHash:r}}return t}function G(a,e,s){return A.validate(a,e,s)}function B(a,e){return U.generate(a,e)}function $(a){return a?a.startsWith("$argon2")?g.needsArgon2Rehash(a):a.startsWith("$2")?g.needsBcryptRehash(a):true:true}process.on("exit",()=>{v.destroy();});exports.a=W;exports.b=H;exports.c=k;exports.d=G;exports.e=B;exports.f=$;