From d601d0431d6a35ce55680af9987c2ecfee719870 Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Thu, 3 Feb 2022 10:13:46 +0100 Subject: timetrack: Initial --- modules/timetrack/timetrack.js | 228 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 modules/timetrack/timetrack.js (limited to 'modules/timetrack/timetrack.js') diff --git a/modules/timetrack/timetrack.js b/modules/timetrack/timetrack.js new file mode 100644 index 0000000..886ea1e --- /dev/null +++ b/modules/timetrack/timetrack.js @@ -0,0 +1,228 @@ +"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(); + }); +}; -- cgit v1.2.3-54-g00ecf