From: Ben Darnell Date: Sun, 29 Mar 2015 15:45:50 +0000 (-0400) Subject: Replace blog demo's google login with a simple email/password scheme. X-Git-Tag: v4.2.0b1~46 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a8986e481ec72a1c578753543bb26738c8a8abe5;p=thirdparty%2Ftornado.git Replace blog demo's google login with a simple email/password scheme. --- diff --git a/demos/blog/Dockerfile b/demos/blog/Dockerfile index c9aab508d..9ba708f38 100644 --- a/demos/blog/Dockerfile +++ b/demos/blog/Dockerfile @@ -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 diff --git a/demos/blog/blog.py b/demos/blog/blog.py index 26748dec6..2707875cc 100755 --- a/demos/blog/blog.py +++ b/demos/blog/blog.py @@ -14,13 +14,16 @@ # 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): diff --git a/demos/blog/docker-compose.yml b/demos/blog/docker-compose.yml index c651ed71d..247c94bea 100644 --- a/demos/blog/docker-compose.yml +++ b/demos/blog/docker-compose.yml @@ -5,6 +5,8 @@ mysql: MYSQL_USER: blog MYSQL_PASSWORD: blog MYSQL_DATABASE: blog + ports: + - "3306" blog: build: . links: diff --git a/demos/blog/requirements.txt b/demos/blog/requirements.txt index 77c06f885..8669e33bd 100644 --- a/demos/blog/requirements.txt +++ b/demos/blog/requirements.txt @@ -1,3 +1,5 @@ +bcrypt +futures MySQL-python markdown tornado diff --git a/demos/blog/schema.sql b/demos/blog/schema.sql index 86bff9a8a..a63e91fde 100644 --- a/demos/blog/schema.sql +++ b/demos/blog/schema.sql @@ -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 index 000000000..acb0df695 --- /dev/null +++ b/demos/blog/templates/create_author.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block body %} +
+ Email:
+ Name:
+ Password:
+ {% module xsrf_form_html() %} + +
+{% end %} diff --git a/demos/blog/templates/login.html b/demos/blog/templates/login.html new file mode 100644 index 000000000..66995f91c --- /dev/null +++ b/demos/blog/templates/login.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block body %} +{% if error %} +Error: {{ error }}

+{% end %} + +

+ Email:
+ Password:
+ {% module xsrf_form_html() %} + +
+{% end %}