summaryrefslogtreecommitdiff
path: root/modules/todo
diff options
context:
space:
mode:
Diffstat (limited to 'modules/todo')
-rw-r--r--modules/todo/todo.html28
-rw-r--r--modules/todo/todo.js121
-rw-r--r--modules/todo/unknownuser.html87
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>