# HG changeset patch
# User paulo
# Date 1464852470 25200
# Node ID f833a888c5489dbeffe9a6fd888a138360a38e57
# Parent  6ede61cb9d1286ca0fc3d823ebf33233afbef63f
add cookie-based PIN system, and update laterlinks to use it

diff -r 6ede61cb9d12 -r f833a888c548 cs/cookies.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cs/cookies.js	Thu Jun 02 00:27:50 2016 -0700
@@ -0,0 +1,63 @@
+/*\
+|*|
+|*|  :: cookies.js ::
+|*|
+|*|  A complete cookies reader/writer framework with full unicode support.
+|*|
+|*|  Revision #1 - September 4, 2014
+|*|
+|*|  https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
+|*|  https://developer.mozilla.org/User:fusionchess
+|*|
+|*|  This framework is released under the GNU Public License, version 3 or later.
+|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
+|*|
+|*|  Syntaxes:
+|*|
+|*|  * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
+|*|  * docCookies.getItem(name)
+|*|  * docCookies.removeItem(name[, path[, domain]])
+|*|  * docCookies.hasItem(name)
+|*|  * docCookies.keys()
+|*|
+\*/
+
+var docCookies = {
+  getItem: function (sKey) {
+    if (!sKey) { return null; }
+    return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
+  },
+  setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
+    if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
+    var sExpires = "";
+    if (vEnd) {
+      switch (vEnd.constructor) {
+        case Number:
+          sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
+          break;
+        case String:
+          sExpires = "; expires=" + vEnd;
+          break;
+        case Date:
+          sExpires = "; expires=" + vEnd.toUTCString();
+          break;
+      }
+    }
+    document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
+    return true;
+  },
+  removeItem: function (sKey, sPath, sDomain) {
+    if (!this.hasItem(sKey)) { return false; }
+    document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
+    return true;
+  },
+  hasItem: function (sKey) {
+    if (!sKey) { return false; }
+    return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
+  },
+  keys: function () {
+    var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
+    for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
+    return aKeys;
+  }
+};
diff -r 6ede61cb9d12 -r f833a888c548 cs/index.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cs/index.html	Thu Jun 02 00:27:50 2016 -0700
@@ -0,0 +1,74 @@
+<html>
+<head>
+<link rel="stylesheet" type="text/css" href="index.css">
+<script type="text/javascript" src="cookies.js"></script>
+</head>
+
+<body>
+<h1>Cookies set</h1>
+<div id="set_cookies"></div>
+<div>
+	<input id="set_k">
+	<input type="password" id="set_v">
+	<input type="button" value="Set" onclick="set()">
+</div>
+</body>
+<script type="text/javascript">
+var setCookiesDiv = document.getElementById("set_cookies");
+var setK = document.getElementById("set_k");
+var setV = document.getElementById("set_v");
+
+function addSetCookie(k, v) {
+	var d = document.createElement("div");
+
+	var kInput = document.createElement("input");
+	kInput.disabled = true;
+	kInput.value = k;
+
+	var vInput = document.createElement("input");
+	vInput.type = "password";
+	vInput.disabled = true;
+	vInput.value = v;
+
+	var unset = document.createElement("input");
+	unset.type = "button";
+	unset.value = "Unset";
+	unset.onclick = function() {
+		docCookies.removeItem(k);
+		setCookiesDiv.removeChild(d);
+	};
+
+	d.appendChild(kInput);
+	d.appendChild(vInput);
+	d.appendChild(unset);
+
+	setCookiesDiv.appendChild(d);
+}
+
+function clear() {
+	while (setCookiesDiv.firstChild) {
+		setCookiesDiv.removeChild(setCookiesDiv.firstChild);
+	}
+}
+
+function load() {
+	clear();
+	for (var i=0; i<docCookies.keys().length; i++) {
+		var k = docCookies.keys()[i];
+		if (docCookies.hasItem(k)) {
+			var v = docCookies.getItem(k);
+			addSetCookie(k, v);
+		}
+	}
+}
+
+function set() {
+	docCookies.setItem(setK.value, setV.value, 600);
+	setK.value = "";
+	setV.value = "";
+	load();
+}
+
+load();
+</script>
+</html>
diff -r 6ede61cb9d12 -r f833a888c548 laterlinks2/laterlinks_app.py
--- a/laterlinks2/laterlinks_app.py	Tue May 31 23:40:17 2016 -0600
+++ b/laterlinks2/laterlinks_app.py	Thu Jun 02 00:27:50 2016 -0700
@@ -6,10 +6,11 @@
 
 import html
 
+import pinlib
+
 
 DEBUG = True
 STRTIME_FMT = "%Y-%m-%d %H:%M:%S"
-MY_PIN = "qworpy"
 
 
 class LLDialect(csv.Dialect):
@@ -79,24 +80,17 @@
 	return urlparse.parse_qs(environ["wsgi.input"].read())
 
 
-def get_pin(inp):
-	if "pin" not in inp:
-		raise PinFailError()
-
-	pin = inp["pin"][0]
-	if pin != MY_PIN:
-		raise PinFailError()
-
-	return pin
-
 
 def main(environ):
-	pin = '' 
 	is_post = (environ["REQUEST_METHOD"] == "POST")
+	cookies = pinlib.parse_cookies(environ)
 	inp = parse_wsgi_input(environ)
 
 	if is_post:
-		pin = get_pin(inp)
+		try:
+			pinlib.check(cookies)
+		except pinlib.PinFailError:
+			raise PinFailError
 		if inp["submit"][0] == "Add":
 			lldb_add(inp)
 		elif inp["submit"][0] == "Delete":
@@ -118,7 +112,8 @@
 		for i in environ.items():
 			debug += cgi.escape("%s = %s \n" % i)
 
-		debug += cgi.escape("wsgi.input.read = %s" % inp)
+		debug += cgi.escape("wsgi.input.read = %s \n" % inp)
+		debug += cgi.escape("cookies = %s \n" % cookies)
 
 	form = body.form(action="index.fcgi", method="post")
 
@@ -142,9 +137,6 @@
 	p1.br
 	p1.input(type="submit", name="submit", value="Add")
 
-	p2 = form.p
-	p2.input(type="password", name="pin", value=pin)
-
 	return unicode(root).encode("utf-8")
 
 
@@ -154,7 +146,7 @@
 
 	try:
 		response_body = main(environ)
-	        response_code = "200 OK"
+		response_code = "200 OK"
 		response_type = "text/html; charset=UTF-8"
 	except LLError as e:
 		response_body = str(e)
diff -r 6ede61cb9d12 -r f833a888c548 laterlinks2/pinlib.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/laterlinks2/pinlib.py	Thu Jun 02 00:27:50 2016 -0700
@@ -0,0 +1,36 @@
+import urlparse
+
+
+PIN_KEY = "llpin"
+PIN_FN = "_%s" % PIN_KEY
+
+
+class PinFailError(Exception):
+	pass
+
+
+def load():
+	ret = None
+
+	try:
+		with open(PIN_FN) as pin_f:
+			ret = pin_f.read().strip()
+	except IOError:
+		pass
+
+	return ret
+
+
+def parse_cookies(environ):
+	return urlparse.parse_qs(environ.get("HTTP_COOKIE", ""))
+
+
+def check(cookies):
+	if PIN_KEY not in cookies:
+		raise PinFailError()
+
+	pin = cookies[PIN_KEY][0]
+	if pin != load():
+		raise PinFailError()
+
+	return pin