git.fiddlerwoaroof.com
Browse code

Tweaking login form

- Rate limiting login attempts
- Showing feedback to attempts to create new users/passwords

fiddlerwoaroof authored on 19/03/2015 17:53:00
Showing 7 changed files
... ...
@@ -4,7 +4,7 @@ DROP TABLE IF EXISTS user_links;
4 4
 DROP TABLE IF EXISTS users;
5 5
 CREATE TABLE users (
6 6
   id SERIAL UNIQUE NOT NULL,
7
-  name TEXT UNIQUE NOT NULL,
7
+  name TEXT UNIQUE NOT NULL CHECK (name <> ''),
8 8
   password TEXT NOT NULL,
9 9
   email TEXT NOT NULL,
10 10
   PRIMARY KEY (id)
... ...
@@ -1,5 +1,6 @@
1 1
 Flask==0.10.1
2
-psycopg2==2.6
3 2
 Flask-Cors==1.10.3
3
+Flask-Limiter==0.7.6
4
+psycopg2==2.6
4 5
 psycopg2==2.6
5 6
 uWSGI==2.0.9
... ...
@@ -1,4 +1,5 @@
1
-from flask import Flask, g
1
+from flask import Flask, g, request
2
+from flask_limiter import Limiter
2 3
 import os
3 4
 import base64
4 5
 
... ...
@@ -20,6 +21,10 @@ except ImportError:
20 21
 app.secret_key = config.secret_key
21 22
 app.debug = config.debug
22 23
 
24
+limiter = Limiter(app)
25
+limiter.limit("60/hour 3/second", key_func=lambda: request.host)(user.user_blueprint)
26
+limiter.limit("dd")(user.user_blueprint)
27
+
23 28
 # Blueprints #
24 29
 user.get_users(app)
25 30
 app.register_blueprint(user.user_blueprint, url_prefix='/user')
... ...
@@ -1,8 +1,12 @@
1
+import json
2
+
1 3
 import flask
2
-from flask.ext.cors import cross_origin
3 4
 from flask import Blueprint, session, redirect, url_for, escape, request, abort, g
5
+from flask_limiter import Limiter
6
+from flask.ext.cors import cross_origin
7
+import psycopg2
8
+
4 9
 from . import database
5
-import json
6 10
 
7 11
 user_blueprint = Blueprint('user', __name__)
8 12
 
... ...
@@ -58,17 +62,32 @@ def subscribe(username):
58 62
 @user_blueprint.route('/add', methods=['POST'])
59 63
 def adduser():
60 64
     db = database.get_db()
65
+    result = {'status': False, 'message': ''}
61 66
     with db.cursor() as cur:
62 67
         obj = request.get_json();
63
-        username, password = obj['username'], obj['password']
64
-        username = username.strip().lower()
65
-        password = password.strip()
66
-        cur.execute('INSERT INTO users (name,password,email) VALUES (%s,%s,%s)',
67
-                    (username, password, 'abc@def.com'))
68
-        _get_users()
69
-        db.commit()
70
-        session['username'] = username
71
-        return json.dumps(True)
68
+        try:
69
+            username, password = obj['username'], obj['password']
70
+        except KeyError:
71
+            print obj
72
+            result['message'] = 'Username required' if 'username' not in obj else 'Password required'
73
+        else:
74
+            username = username.strip().lower()
75
+            password = password.strip()
76
+            try:
77
+                cur.execute('INSERT INTO users (name,password,email) VALUES (%s,%s,%s)',
78
+                            (username, password, 'abc@def.com'))
79
+                session['username'] = username
80
+                result['status'] = True
81
+                _get_users()
82
+            except psycopg2.IntegrityError as e:
83
+                db.rollback()
84
+                if e.pgcode == '23505': #username not unique
85
+                    result['message'] = 'Username in use'
86
+                elif e.pgcode == '23502': #username empty
87
+                    result['message'] = 'Username required'
88
+                else: raise
89
+            else: db.commit()
90
+    return json.dumps(result)
72 91
 
73 92
 @user_blueprint.route('/check')
74 93
 def checkuser():
... ...
@@ -82,7 +101,7 @@ def gen_ak(db):
82 101
 @cross_origin(allow_headers='Content-Type')
83 102
 def login():
84 103
     obj = request.get_json();
85
-    result = False
104
+    result = {'status': False, 'message': ''}
86 105
     username, password = obj['username'], obj['password']
87 106
     username = username.strip().lower()
88 107
     password = password.strip()
... ...
@@ -97,7 +116,9 @@ def login():
97 116
             result = {'success': True, 'ak': ak}
98 117
         else:
99 118
             session['username'] = username
100
-            result = True
119
+            result['status'] = True
120
+    else:
121
+        result['message'] = 'Wrong Username or Password'
101 122
     return json.dumps(result)
102 123
 
103 124
 @user_blueprint.route('/logout')
... ...
@@ -132,6 +132,13 @@ main form input[type="text"] {
132 132
 
133 133
 /* @group Login Module */
134 134
 
135
+#login_form div.message{
136
+  position: absolute;
137
+  background: red;
138
+  width: 100%;
139
+  text-align: center;
140
+}
141
+
135 142
 #login_form {
136 143
   position: absolute;
137 144
   z-index: 100;
... ...
@@ -222,6 +229,9 @@ body.is-logged-on #nav-main {
222 229
     left: -25%;
223 230
     top: 66px;
224 231
   }
232
+  #login_form div.message{
233
+    top: -33px;
234
+  }
225 235
   #login_form form {
226 236
     margin-top: 100px;
227 237
   }
... ...
@@ -311,6 +321,9 @@ body.is-logged-on #nav-main {
311 321
   #login_form form {
312 322
     margin-top: 20px;
313 323
   }
324
+  #login_form div.message{
325
+    margin-top 10px;
326
+  }
314 327
   form input[type="password"] {
315 328
   }
316 329
 
... ...
@@ -64,6 +64,8 @@ marrowApp.config(['$locationProvider',
64 64
 ]);
65 65
 
66 66
 marrowApp.controller('LoginCtrl', function ($scope,$http,$route,$location) {
67
+  $scope.message = '';
68
+
67 69
   check_login().success(
68 70
     function(is_loggedon) {
69 71
       is_loggedon = JSON.parse(is_loggedon);
... ...
@@ -77,8 +79,8 @@ marrowApp.controller('LoginCtrl', function ($scope,$http,$route,$location) {
77 79
     console.log(postObj);
78 80
     $http.post("/api/user/add", postObj)
79 81
     .success(function(added_user) {
80
-      added_user = JSON.parse(added_user);
81
-      if(added_user === true) {$location.url('/');}
82
+      if (added_user.status === true) {$location.url('/');}
83
+      else {$scope.message = added_user.message;}
82 84
     });
83 85
   };
84 86
 
... ...
@@ -89,10 +91,9 @@ marrowApp.controller('LoginCtrl', function ($scope,$http,$route,$location) {
89 91
     $http.post("/api/user/login", {"username":username, "password":password})
90 92
     .success(
91 93
       function (login_succeeded) {
92
-        login_succeeded = JSON.parse(login_succeeded);
93 94
         var el = angular.element(document.querySelector('#login_form'));
94
-        if (login_succeeded === true) {el.removeClass('is-hidden');}
95
-        $location.url('/');
95
+        if (login_succeeded.status === true) {$location.url('/');}
96
+        else {$scope.message = login_succeeded.message;}
96 97
     });
97 98
   };
98 99
 });
... ...
@@ -231,7 +232,9 @@ marrowApp.controller('SidebarCtrl', function ($scope,$http,$location,$route) {
231 232
   };
232 233
 
233 234
   $scope.logout = function() {
234
-    $http.get('/api/user/logout').finally(function() { $location.url('/login'); });
235
+    $http.get('/api/user/logout').success(function() {
236
+      $location.url('/login');
237
+    });
235 238
   };
236 239
 });
237 240
 
... ...
@@ -1,6 +1,6 @@
1 1
 <div id="login_form">
2
-  <div class="message"></div>
3 2
   <form>
3
+    <div class="message">{{message}}</div>
4 4
     <input type="text" ng-model="username" placeholder="Username" />
5 5
     <input type="password" ng-model="password" placeholder="Password" />
6 6
     <button ng-click="login()">Log In</button>