Skip to content

Repository files, collaboration, releases and project activity.

ARCHFORGE HISTORY

Blame · src/security.js

Branch main · the Commit author is not necessarily a verified ArchForge account.

e59b7f7cc6archadmin2026-10-111 'use strict';
e59b7f7cc6archadmin2026-10-112 const crypto=require('node:crypto');
e59b7f7cc6archadmin2026-10-113 const {URL}=require('node:url');
e59b7f7cc6archadmin2026-10-114 const base64url=b=>Buffer.from(b).toString('base64url');
e59b7f7cc6archadmin2026-10-115 const randomToken=(bytes=32)=>base64url(crypto.randomBytes(bytes));
e59b7f7cc6archadmin2026-10-116 const sha=s=>crypto.createHash('sha256').update(s).digest('hex');
e59b7f7cc6archadmin2026-10-117 function safeEqual(a,b){const x=Buffer.from(String(a)),y=Buffer.from(String(b));return x.length===y.length&&crypto.timingSafeEqual(x,y);}
e59b7f7cc6archadmin2026-10-118 function passwordHash(pass,salt=crypto.randomBytes(16).toString('hex')){if(pass.length<12||Buffer.byteLength(pass)>1024)throw Error('Use a password between 12 and 1024 bytes');return `scrypt$${salt}$${crypto.scryptSync(pass,Buffer.from(salt,'hex'),64,{N:16384,r:8,p:1}).toString('hex')}`;}
e59b7f7cc6archadmin2026-10-119 function verifyPassword(pass,stored){try{const [v,s,h]=stored.split('$');if(v!=='scrypt'||!s||!h)return false;return safeEqual(crypto.scryptSync(pass,Buffer.from(s,'hex'),64,{N:16384,r:8,p:1}).toString('hex'),h);}catch{return false;}}
e59b7f7cc6archadmin2026-10-1110 const alphabet='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
e59b7f7cc6archadmin2026-10-1111 function base32Encode(bytes){let bits=0,v=0,out='';for(const b of bytes){v=(v<<8)|b;bits+=8;while(bits>=5){out+=alphabet[(v>>>(bits-=5))&31];}}if(bits)out+=alphabet[(v<<(5-bits))&31];return out;}
e59b7f7cc6archadmin2026-10-1112 function base32Decode(input){let bits=0,v=0,out=[];for(const c of input.replace(/=|\s/g,'').toUpperCase()){let n=alphabet.indexOf(c);if(n<0)throw Error('Invalid base32');v=(v<<5)|n;bits+=5;if(bits>=8){out.push((v>>>(bits-=8))&255);}}return Buffer.from(out);}
e59b7f7cc6archadmin2026-10-1113 function totp(secret,when=Date.now()){const step=Math.floor(when/30000);const buf=Buffer.alloc(8);buf.writeBigUInt64BE(BigInt(step));const digest=crypto.createHmac('sha1',base32Decode(secret)).update(buf).digest();const off=digest[19]&15;return String((digest.readUInt32BE(off)&0x7fffffff)%1000000).padStart(6,'0');}
e59b7f7cc6archadmin2026-10-1114 function verifyTotp(secret,input,when=Date.now()){if(!/^\d{6}$/.test(String(input)))return false;for(let offset=-1;offset<=1;offset++)if(safeEqual(totp(secret,when+offset*30000),input))return true;return false;}
e59b7f7cc6archadmin2026-10-1115 function encrypt(key,text){const nonce=crypto.randomBytes(12),cipher=crypto.createCipheriv('aes-256-gcm',Buffer.from(key,'hex'),nonce);const ciphertext=Buffer.concat([cipher.update(text,'utf8'),cipher.final()]);return [base64url(nonce),base64url(ciphertext),base64url(cipher.getAuthTag())].join('.');}
e59b7f7cc6archadmin2026-10-1116 function decrypt(key,encoded){const [n,c,t]=encoded.split('.');const dec=crypto.createDecipheriv('aes-256-gcm',Buffer.from(key,'hex'),Buffer.from(n,'base64url'));dec.setAuthTag(Buffer.from(t,'base64url'));return Buffer.concat([dec.update(Buffer.from(c,'base64url')),dec.final()]).toString('utf8');}
e59b7f7cc6archadmin2026-10-1117 const esc=value=>String(value??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
e59b7f7cc6archadmin2026-10-1118 function cookies(req){return Object.fromEntries((req.headers.cookie||'').split(';').map(p=>p.trim().split('=')).filter(x=>x.length===2));}
e59b7f7cc6archadmin2026-10-1119 function rateLimiter(windowMs,maximum){const map=new Map();return (key)=>{const now=Date.now();if(map.size>10000){for(const [k,v] of map)if(v.until<now)map.delete(k);}let x=map.get(key);if(!x||x.until<now){x={count:0,until:now+windowMs};map.set(key,x);}x.count++;return x.count<=maximum;};}
e59b7f7cc6archadmin2026-10-1120 function validEmail(v){return typeof v==='string'&&v.length<=254&&/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(v);}
e59b7f7cc6archadmin2026-10-1121 function isAllowedRedirect(candidate,origin){try{const u=new URL(candidate,origin);return u.protocol==='https:'&&u.origin===new URL(origin).origin;}catch{return false;}}
e59b7f7cc6archadmin2026-10-1122 module.exports={randomToken,sha,safeEqual,passwordHash,verifyPassword,base32Encode,totp,verifyTotp,encrypt,decrypt,esc,cookies,rateLimiter,validEmail,isAllowedRedirect};