]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Replace blog demo's google login with a simple email/password scheme.
authorBen Darnell <ben@bendarnell.com>
Sun, 29 Mar 2015 15:45:50 +0000 (11:45 -0400)
committerBen Darnell <ben@bendarnell.com>
Sun, 29 Mar 2015 15:45:50 +0000 (11:45 -0400)
demos/blog/Dockerfile
demos/blog/blog.py
demos/blog/docker-compose.yml
demos/blog/requirements.txt
demos/blog/schema.sql
demos/blog/templates/create_author.html [new file with mode: 0644]
demos/blog/templates/login.html [new file with mode: 0644]

index c9aab508d295f59344971556f9c4701c796ec75a..9ba708f382fb23824e1c830466aa703e69879469 100644 (file)
@@ -1,7 +1,17 @@
-FROM python:2.7-onbuild
+FROM python:2.7
 
 EXPOSE 8888
 
 RUN apt-get update && apt-get install -y mysql-client
 
+# based on python:2.7-onbuild, but if we use that image directly
+# the above apt-get line runs too late.
+RUN mkdir -p /usr/src/app
+WORKDIR /usr/src/app
+
+COPY requirements.txt /usr/src/app/
+RUN pip install -r requirements.txt
+
+COPY . /usr/src/app
+
 CMD python blog.py --mysql_host=mysql
index 26748dec6e63f3e5548e2ec285b461dba28a1672..2707875ccb51cd8fe36ac9c84ec7631ddbbe0799 100755 (executable)
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import bcrypt
+import concurrent.futures
 import MySQLdb
 import markdown
 import os.path
 import re
 import subprocess
 import torndb
-import tornado.auth
+import tornado.escape
+from tornado import gen
 import tornado.httpserver
 import tornado.ioloop
 import tornado.options
@@ -36,6 +39,10 @@ define("mysql_user", default="blog", help="blog database user")
 define("mysql_password", default="blog", help="blog database password")
 
 
+# A thread pool to be used for password hashing with bcrypt.
+executor = concurrent.futures.ThreadPoolExecutor(2)
+
+
 class Application(tornado.web.Application):
     def __init__(self):
         handlers = [
@@ -44,6 +51,7 @@ class Application(tornado.web.Application):
             (r"/feed", FeedHandler),
             (r"/entry/([^/]+)", EntryHandler),
             (r"/compose", ComposeHandler),
+            (r"/auth/create", AuthCreateHandler),
             (r"/auth/login", AuthLoginHandler),
             (r"/auth/logout", AuthLogoutHandler),
         ]
@@ -88,6 +96,9 @@ class BaseHandler(tornado.web.RequestHandler):
         if not user_id: return None
         return self.db.get("SELECT * FROM authors WHERE id = %s", int(user_id))
 
+    def any_author_exists(self):
+        return bool(self.db.get("SELECT * FROM authors LIMIT 1"))
+
 
 class HomeHandler(BaseHandler):
     def get(self):
@@ -160,33 +171,49 @@ class ComposeHandler(BaseHandler):
         self.redirect("/entry/" + slug)
 
 
-class AuthLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
-    @tornado.web.asynchronous
+class AuthCreateHandler(BaseHandler):
     def get(self):
-        if self.get_argument("openid.mode", None):
-            self.get_authenticated_user(self._on_auth)
-            return
-        self.authenticate_redirect()
+        self.render("create_author.html")
+
+    @gen.coroutine
+    def post(self):
+        if self.any_author_exists():
+            raise tornado.web.HTTPError(400, "author already created")
+        hashed_password = yield executor.submit(
+            bcrypt.hashpw, tornado.escape.utf8(self.get_argument("password")),
+            bcrypt.gensalt())
+        author_id = self.db.execute(
+            "INSERT INTO authors (email, name, hashed_password) "
+            "VALUES (%s, %s, %s)",
+            self.get_argument("email"), self.get_argument("name"),
+            hashed_password)
+        self.set_secure_cookie("blogdemo_user", str(author_id))
+        self.redirect(self.get_argument("next", "/"))
 
-    def _on_auth(self, user):
-        if not user:
-            raise tornado.web.HTTPError(500, "Google auth failed")
+
+class AuthLoginHandler(BaseHandler):
+    def get(self):
+        # If there are no authors, redirect to the account creation page.
+        if not self.any_author_exists():
+            self.redirect("/auth/create")
+        else:
+            self.render("login.html", error=None)
+
+    @gen.coroutine
+    def post(self):
         author = self.db.get("SELECT * FROM authors WHERE email = %s",
-                             user["email"])
+                             self.get_argument("email"))
         if not author:
-            # Auto-create first author
-            any_author = self.db.get("SELECT * FROM authors LIMIT 1")
-            if not any_author:
-                author_id = self.db.execute(
-                    "INSERT INTO authors (email,name) VALUES (%s,%s)",
-                    user["email"], user["name"])
-            else:
-                self.redirect("/")
-                return
+            self.render("login.html", error="email not found")
+            return
+        hashed_password = yield executor.submit(
+            bcrypt.hashpw, tornado.escape.utf8(self.get_argument("password")),
+            tornado.escape.utf8(author.hashed_password))
+        if hashed_password == author.hashed_password:
+            self.set_secure_cookie("blogdemo_user", str(author.id))
+            self.redirect(self.get_argument("next", "/"))
         else:
-            author_id = author["id"]
-        self.set_secure_cookie("blogdemo_user", str(author_id))
-        self.redirect(self.get_argument("next", "/"))
+            self.render("login.html", error="incorrect password")
 
 
 class AuthLogoutHandler(BaseHandler):
index c651ed71dadb6e9077a4ea926db0baeb6c5eb80d..247c94beafa4392419c7939539fc1c95917c565b 100644 (file)
@@ -5,6 +5,8 @@ mysql:
     MYSQL_USER: blog
     MYSQL_PASSWORD: blog
     MYSQL_DATABASE: blog
+  ports:
+    - "3306"
 blog:
   build: .
   links:
index 77c06f88595cd93854e9076430af503d48a05eb7..8669e33bd5c1b951010a243021418eb26303d652 100644 (file)
@@ -1,3 +1,5 @@
+bcrypt
+futures
 MySQL-python
 markdown
 tornado
index 86bff9a8ad30f826bdfb364eb7a6ec02460f47fa..a63e91fdefaa876d5d3bbcacf9bc38b8f8e0a766 100644 (file)
@@ -40,5 +40,6 @@ DROP TABLE IF EXISTS authors;
 CREATE TABLE authors (
     id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
     email VARCHAR(100) NOT NULL UNIQUE,
-    name VARCHAR(100) NOT NULL
+    name VARCHAR(100) NOT NULL,
+    hashed_password VARCHAR(100) NOT NULL
 );
diff --git a/demos/blog/templates/create_author.html b/demos/blog/templates/create_author.html
new file mode 100644 (file)
index 0000000..acb0df6
--- /dev/null
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+
+{% block body %}
+<form action="/auth/create" method="POST">
+  Email: <input name="email" type="text"><br>
+  Name: <input name="name" type="text"><br>
+  Password: <input name="password" type="password"><br>
+  {% module xsrf_form_html() %}
+  <input type="submit">
+</form>
+{% end %}
diff --git a/demos/blog/templates/login.html b/demos/blog/templates/login.html
new file mode 100644 (file)
index 0000000..66995f9
--- /dev/null
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+
+{% block body %}
+{% if error %}
+<span style="color: red">Error: {{ error }}</span><p>
+{% end %}
+
+<form action="/auth/login" method="POST">
+  Email: <input name="email" type="text"><br>
+  Password: <input name="password" type="password"><br>
+  {% module xsrf_form_html() %}
+  <input type="submit">
+</form>
+{% end %}