"use strict"; var cmn=require("../$common.js"), persist=require("node-persist"), crypto=require("crypto"), basicAuth=require("basic-auth"), fs=require("fs"); var moddir=null; persist=persist.create({ dir:cmn.persistdir+"/timetrack", continuous:false, interval:false }); persist.initSync(); //events: {"user": [{id: Int,sheet: String,text: String,date: Date,type: String}]} // type: "in" / "out" //accounts: {"user": hash (String)} var nextid=persist.getItemSync("nextid"); if(nextid==null){ nextid=1; persist.setItemSync("nextid",nextid); } var events=persist.getItemSync("events"); (function(){ if(events==null){ events={}; persist.setItemSync("events",events); } else { for(var user in events){ for(var ev of events[user]){ ev.date=new Date(ev.date); if(nextid<=ev.id)nextid=ev.id+1; } } persist.setItemSync("nextid",nextid); } })(); var accounts=persist.getItemSync("accounts"); if(accounts==null){ accounts={}; persist.setItemSync("accounts",accounts); } var naccounts=0; (function(){ var user; for(user in accounts)naccounts++; })(); function scryptHash(password,cb){ crypto.randomBytes(16,function(err,salt){ if(err){ cb(err,null); return; } crypto.scrypt(password,salt,32,function(err,key){ if(err)cb(err,null); else cb(null,salt.toString("hex")+"$"+key.toString("hex")); }); }); } function scryptCompare(password,hash,cb){ hash=hash.split("$"); if(hash.length!=2){ cb(new Error("Invalid hash in database"),null); return; } var salt=Buffer.from(hash[0],"hex"),shash=hash[1]; crypto.scrypt(password,salt,32,function(err,key){ if(err)cb(err,null); else if(key.toString("hex")==shash)cb(null,true); else cb(null,false); }); } function sendUnauth(res){ res.set("WWW-Authenticate","Basic realm=Authorization required"); return res.sendStatus(401); } function unknownUserHandler(req,res,next){ res.sendFile(moddir+"/unknownuser.html"); } function authMiddleware(req,res,next){ var user=basicAuth(req); req.authuser=null; if(!user||!user.name){ sendUnauth(res); return; } req.authuser=user.name; if(accounts[req.authuser]){ scryptCompare(user.pass,accounts[req.authuser],function(err,ok){ if(ok)next(); else sendUnauth(res); }); } else { unknownUserHandler(req,res,next); } } function asciiValid(str){ var i,c; for(i=0;i=127)return false; } return true; } module.exports=function(app,io,_moddir){ moddir=_moddir; //first the endpoints that need to bypass authMiddleware app.get("/timetrack/authfail",function(req,res){ sendUnauth(res); }); app.post("/timetrack/createuser",function(req,res){ var user=basicAuth(req); if(!user||!user.name){ res.status(400).send("No credentials sent"); return; } if(user.name.length<3||user.name.length>32||!asciiValid(user.name)){ res.status(400).send("Invalid username"); return; } if(user.pass.length<3||user.pass.length>32||!asciiValid(user.pass)){ res.status(400).send("Invalid password"); return; } if(accounts[user.name]){ res.status(400).send("User already exists"); return; } if(naccounts>=20){ res.status(500).send("Too many accounts created, please contact Tom..."); return; } scryptHash(user.pass,function(err,hash){ if(!hash){ res.status(500).send("Something went wrong..."); console.log(err); return; } accounts[user.name]=hash; events[user.name]=[]; naccounts++; persist.setItemSync("accounts",accounts); persist.setItemSync("events",events); res.status(200).end(); }); }); app.all(["/timetrack","/timetrack/*"],authMiddleware); //for all the other endpoints app.get("/timetrack",function(req,res){ res.sendFile(moddir+"/timetrack.html"); }); app.get("/timetrack/list",function(req,res){ res.json(events[req.authuser]); }); app.get("/timetrack/sheets",function(req,res){ var seen=new Map(); var list=[]; for (var i=0;i=4000){ res.status(400).send("Isn't 4000 events enough for you?"); return; } events[req.authuser].push({ id:nextid++, sheet:sheet, text:text, date:date, type:type }); events[req.authuser].sort(function(a, b) { return a.date - b.date; }); persist.setItemSync("events",events); persist.setItemSync("nextid",nextid); res.status(200).end(); }); };