diff options
Diffstat (limited to 'modules/todo')
-rw-r--r-- | modules/todo/todo.html | 28 | ||||
-rw-r--r-- | modules/todo/todo.js | 121 | ||||
-rw-r--r-- | modules/todo/unknownuser.html | 87 |
3 files changed, 217 insertions, 19 deletions
diff --git a/modules/todo/todo.html b/modules/todo/todo.html index b196682..1f2a060 100644 --- a/modules/todo/todo.html +++ b/modules/todo/todo.html @@ -6,18 +6,26 @@ <script> "use strict"; -function fetch(method,url,data/*?*/,cb){ - if(!cb){ +function fetch(method,url,data/*?*/,creds/*?*/,cb){ + if(!creds){ cb=data; data=undefined; - if(!cb)throw new Error("No callback passed to fetch"); + creds=undefined; + } else if(!cb){ + cb=creds; + creds=undefined; } + if(!cb)throw new Error("No callback passed to fetch"); var xhr=new XMLHttpRequest(); xhr.onreadystatechange=function(ev){ if(xhr.readyState<4)return; cb(xhr.status,xhr.responseText); }; - xhr.open(method,url); + if(creds){ + xhr.open(method,url,true,creds[0],creds[1]); + } else { + xhr.open(method,url); + } xhr.send(data); } @@ -187,6 +195,12 @@ function doAddTask(ev){ }); } +function logoutReload(){ + fetch("GET","/todo/authfail",undefined,["baduser","badpass"],function(status,body){ + location.href=location.href; + }); +} + window.addEventListener("load",getlist); </script> <style> @@ -238,9 +252,15 @@ body{ #addtaskform.invisible{ display:none; } +#logoutwrapper{ + float:right; +} </style> </head> <body> +<div id="logoutwrapper"> + <input type="button" onclick="logoutReload();" value="Logout"> +</div> <h1>TODO</h1> <div id="todolist"></div> <br><br> diff --git a/modules/todo/todo.js b/modules/todo/todo.js index 9e53c76..58322f2 100644 --- a/modules/todo/todo.js +++ b/modules/todo/todo.js @@ -3,7 +3,11 @@ // {"key":"tasks","value":[{"id":1,"subject":"kaas rep","repweeks":0,"date":"2016-10-25T07:46:54.493Z"},{"id":2,"subject":"kaas","repweeks":0,"date":"2016-10-27T07:46:54.493Z"}]} var cmn=require("../$common.js"), - persist=require("node-persist"); + persist=require("node-persist"), + bcrypt=require("bcrypt"), + basicAuth=require("basic-auth"); + +var bcryptHashRounds=10; var moddir=null; @@ -14,45 +18,132 @@ persist=persist.create({ }); persist.initSync(); -//tasks: [{id: Int,subject: String,repweeks: Int,date: Date}] +//tasks: {"user": [{id: Int,subject: String,repweeks: Int,date: Date}]} +//accounts: {"user": hash (String)} //repweeks==0 implies no repetition var nextid=persist.getItemSync("nextid"); -if(nextid==null)nextid=1; +if(nextid==null){ + nextid=1; + persist.setItemSync("nextid",nextid); +} var tasks=persist.getItemSync("tasks"); (function(){ - if(!tasks){ - tasks=[]; + if(tasks==null){ + tasks={}; persist.setItemSync("tasks",tasks); } else { - for(var task of tasks){ - task.date=new Date(task.date); - if(nextid<=task.id)nextid=task.id+1; + for(var user in tasks){ + for(var task of tasks[user]){ + task.date=new Date(task.date); + if(nextid<=task.id)nextid=task.id+1; + } } persist.setItemSync("nextid",nextid); } })(); +var accounts=persist.getItemSync("accounts"); +if(accounts==null){ + accounts={}; + persist.setItemSync("accounts",accounts); +} + + +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]){ + bcrypt.compare(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<str.length;i++){ + c=str.charCodeAt(i); + if(c<32||c>=127)return false; + } + return true; +} module.exports=function(app,io,_moddir){ moddir=_moddir; - app.all("/todo/task",cmn.authgen()); + + //first the endpoints that need to bypass authMiddleware + app.get("/todo/authfail",function(req,res){ + sendUnauth(res); + }); + app.post("/todo/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; + } + bcrypt.hash(user.pass,bcryptHashRounds,function(err,hash){ + if(!hash){ + res.status(500).send("Something went wrong..."); + console.log(err); + return; + } + accounts[user.name]=hash; + tasks[user.name]=[]; + persist.setItemSync("accounts",accounts); + persist.setItemSync("tasks",tasks); + res.status(200).end(); + }); + }); + + app.all(["/todo","/todo/*"],authMiddleware); //for all the other endpoints + app.get("/todo",function(req,res){ res.sendFile(moddir+"/todo.html"); }); app.get("/todo/list",function(req,res){ - res.json(tasks); + res.json(tasks[req.authuser]); }); app.delete("/todo/task",function(req,res){ var id=+req.body; var i; var fail=false; - if(id<0||~~id!=id||isNaN(id)){ + var usertasks=tasks[req.authuser]; + if(id<0||~~id!=id||isNaN(id)||!usertasks){ fail=true; } else { - for(i=0;i<tasks.length;i++)if(tasks[i].id==id)break; - if(i==tasks.length)fail=true; + for(i=0;i<usertasks.length;i++)if(usertasks[i].id==id)break; + if(i==usertasks.length)fail=true; else { - tasks.splice(i,1); + usertasks.splice(i,1); persist.setItemSync("tasks",tasks); } } @@ -74,7 +165,7 @@ module.exports=function(app,io,_moddir){ res.status(400).send("Invalid data"); return; } - tasks.push({ + tasks[req.authuser].push({ id:nextid++, subject:subject, repweeks:repweeks, diff --git a/modules/todo/unknownuser.html b/modules/todo/unknownuser.html new file mode 100644 index 0000000..c8590a3 --- /dev/null +++ b/modules/todo/unknownuser.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>TODO: Unknown user</title> +<script> +"use strict"; + +function fetch(method,url,data/*?*/,creds/*?*/,cb){ + if(!creds){ + cb=data; + data=undefined; + creds=undefined; + } else if(!cb){ + cb=creds; + creds=undefined; + } + if(!cb)throw new Error("No callback passed to fetch"); + var xhr=new XMLHttpRequest(); + xhr.onreadystatechange=function(ev){ + if(xhr.readyState<4)return; + cb(xhr.status,xhr.responseText); + }; + if(creds){ + xhr.open(method,url,true,creds[0],creds[1]); + } else { + xhr.open(method,url); + } + xhr.send(data); +} + +function asciiValid(str){ + var i,c; + for(i=0;i<str.length;i++){ + c=str.charCodeAt(i); + if(c<32||c>=127)return false; + } + return true; +} + +function logoutReload(){ + fetch("GET","/todo/authfail",undefined,["baduser","badpass"],function(status,body){ + location.href=location.href; + }); +} + +function doCreateUser(){ + var username=document.getElementById("username").value; + var password=document.getElementById("password").value; + var fail; + [["Username",username],["Password",password]].forEach(function(name,value){ + fail=true; + if(value.length<3)alert(name+" too short!"); + else if(value.length>32)alert(name+" too long!"); + else if(!asciiValid(value))alert("Invalid "+name.toLowerCase()+"! Please use only ASCII characters."); + else fail=false; + }); + if(fail)return; + + fetch("POST","/todo/createuser",undefined,[username,password],function(status,body){ + if(status==200){ + alert("User \""+username+"\" created successfully. Please login."); + logoutReload(); + } else { + alert("Error: "+body); + } + }); +} +</script> +<style> +body{ + font-family:Georgia,Times,serif; + font-size:14px; +} +</style> +</head> +<body> +<h1>TODO: Unknown user</h1> +<p>The user you entered is not known in the system. You can use the below form to create a new user. +Be aware: this system is not secure.</p> +Username: <input type="text" id="username" placeholder="username"><br> +Password: <input type="password" id="password" placeholder="password"><br> +<input type="button" value="Create user" onclick="doCreateUser();"> +<br><br> +<p>You can also <input type="button" onclick="logoutReload();" value="log out and try again"> if you just can't type.</p> +</body> +</html> |