about summary refs log tree commit diff
path: root/wqflask
diff options
context:
space:
mode:
Diffstat (limited to 'wqflask')
-rw-r--r--wqflask/secure_server.py40
-rw-r--r--wqflask/wqflask/send_mail.py39
-rw-r--r--wqflask/wqflask/templates/email/forgot_password.txt5
-rw-r--r--wqflask/wqflask/templates/email/verification.txt3
-rw-r--r--wqflask/wqflask/templates/new_security/forgot_password.html65
-rw-r--r--wqflask/wqflask/templates/new_security/forgot_password_step2.html33
-rw-r--r--wqflask/wqflask/templates/new_security/login_user.html55
-rw-r--r--wqflask/wqflask/templates/new_security/password_reset.html80
-rw-r--r--wqflask/wqflask/templates/new_security/register_user.html56
-rw-r--r--wqflask/wqflask/templates/new_security/registered.html11
-rw-r--r--wqflask/wqflask/user_manager.py342
-rw-r--r--wqflask/wqflask/views.py64
12 files changed, 546 insertions, 247 deletions
diff --git a/wqflask/secure_server.py b/wqflask/secure_server.py
index a77abf7e..d5f1a291 100644
--- a/wqflask/secure_server.py
+++ b/wqflask/secure_server.py
@@ -1,12 +1,18 @@
 from __future__ import absolute_import, division, print_function
 
+import time
+import sys
+
 from wqflask import app
 
 from flask import Flask, render_template
 
+import redis
+Redis = redis.StrictRedis()
+
 # Setup mail
-from flask.ext.mail import Mail
-mail = Mail(app)
+#from flask.ext.mail import Mail
+#mail = Mail(app)
 
 from wqflask.model import *
 
@@ -33,8 +39,38 @@ app.wsgi_app = ProxyFix(app.wsgi_app)
 
 #print("app.config is:", app.config)
 
+
+
+def check_send_mail_running():
+    """Ensure send_mail.py is running before we start the site
+    
+    It would be really easy to accidentally run the site
+    without our mail program running
+    This will make sure our mail program is running...or at least recently run...
+
+    """
+    error_msg = "Make sure your are running send_mail.py"
+    send_mail_ping = Redis.get("send_mail:ping")
+    print("send_mail_ping is:", send_mail_ping)
+    if not send_mail_ping:
+        sys.exit(error_msg)
+
+    last_ping = time.time() - float(send_mail_ping)
+    if not (0 < last_ping < 100):
+        sys.exit(error_msg)
+
+
+    print("send_mail.py seems to be running...")
+
+
 if __name__ == '__main__':
     #create_user()
+
+
+
+    check_send_mail_running()
+
+
     app.run(host='0.0.0.0',
         port=app.config['SERVER_PORT'],
         use_debugger=False,
diff --git a/wqflask/wqflask/send_mail.py b/wqflask/wqflask/send_mail.py
index be51ad0d..bf5d0dd8 100644
--- a/wqflask/wqflask/send_mail.py
+++ b/wqflask/wqflask/send_mail.py
@@ -1,6 +1,7 @@
 from __future__ import absolute_import, division, print_function
 
 import datetime
+import time
 
 import simplejson as json
 
@@ -12,34 +13,40 @@ import mailer
 def timestamp():
     ts = datetime.datetime.utcnow()
     return ts.isoformat()
-    
+
 def main():
     while True:
-        print("Waiting for message to show up in queue...")
-        msg = Redis.blpop("mail_queue")
-        
-        # Queue name is the first element, we want the second, which is the actual message
-        msg = msg[1]
-        
-        print("\nGot a msg in queue at {}: {}".format(timestamp(), msg))
-        # Todo: Truncate mail_processed when it gets to long
-        Redis.rpush("mail_processed", msg)
-        process_message(msg)
-        
+        print("I'm alive!")
+
+        # Set something so we know it's running (or at least been running recently)
+        Redis.setex("send_mail:ping", 300, time.time())
+
+        msg = Redis.blpop("mail_queue", 30)
+
+        if msg:
+            # Queue name is the first element, we want the second, which is the actual message
+            msg = msg[1]
+
+            print("\n\nGot a msg in queue at {}: {}".format(timestamp(), msg))
+            # Todo: Truncate mail_processed when it gets to long
+            Redis.rpush("mail_processed", msg)
+            process_message(msg)
+
+
 
 def process_message(msg):
     msg = json.loads(msg)
-    
+
     message = mailer.Message()
     message.From = msg['From']
     message.To = msg['To']
     message.Subject = msg['Subject']
     message.Body = msg['Body']
-    
+
     sender = mailer.Mailer('localhost')
     sender.send(message)
     print("Sent message at {}: {}\n".format(timestamp(), msg))
-  
+
 
 if __name__ == '__main__':
-    main()
\ No newline at end of file
+    main()
diff --git a/wqflask/wqflask/templates/email/forgot_password.txt b/wqflask/wqflask/templates/email/forgot_password.txt
new file mode 100644
index 00000000..e7d1389b
--- /dev/null
+++ b/wqflask/wqflask/templates/email/forgot_password.txt
@@ -0,0 +1,5 @@
+Sorry to hear you lost your GeneNetwork password.
+
+To reset your password please click the following link, or cut and paste it into your browser window:
+
+{{ url_for_hmac("password_reset", code = verification_code, _external=True )}}
diff --git a/wqflask/wqflask/templates/email/verification.txt b/wqflask/wqflask/templates/email/verification.txt
index 29229c68..76149a3a 100644
--- a/wqflask/wqflask/templates/email/verification.txt
+++ b/wqflask/wqflask/templates/email/verification.txt
@@ -4,5 +4,4 @@ We need to verify your email address.
 
 To do that please click the following link, or cut and paste it into your browser window:
 
-{{ url_for_hmac("verify", code = verification_code, _external=True )}}
-
+{{ url_for_hmac("verify_email", code = verification_code, _external=True )}}
diff --git a/wqflask/wqflask/templates/new_security/forgot_password.html b/wqflask/wqflask/templates/new_security/forgot_password.html
new file mode 100644
index 00000000..39e51f96
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/forgot_password.html
@@ -0,0 +1,65 @@
+{% extends "base.html" %}
+{% block title %}Forgot Password{% endblock %}
+{% block content %}
+
+    {{ header("Forgot Password", "Easily reset your password.") }}
+
+    <div class="container">
+        <div class="page-header">
+            <h1>Password Reset</h1>
+        </div>
+
+
+        <div class="security_box">
+
+            <h4>Enter your email address</h4>
+
+            <h5>And we'll send you a link to reset your password</h5>
+
+
+
+            <form class="form-horizontal" action="/n/forgot_password_submit"
+                  method="POST" name="login_user_form">
+                <fieldset>
+
+
+                    <div class="control-group">
+                        <label class="control-label" for="email_address">Email Address</label>
+                        <div class="controls">
+                            <input id="email_address" class="focused" name="email_address" type="text" value="">
+                        </div>
+                    </div>
+
+
+                    <div class="control-group">
+                        <div class="controls">
+                            <input id="next" name="next" type="hidden" value="">
+
+                            <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Send link">
+                        </div>
+                    </div>
+
+                </fieldset>
+
+                <br />
+
+                <div class="well">
+                    <h5>Has your email address changed?</h5>
+
+                    If you no longer use the email address connected to your account, you can contact us for assistance.
+
+                </div>
+
+            </form>
+        </div>
+        </div>
+    </div>
+
+  {% endblock %}
+
+{% block js %}
+    <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>-->
+
+    {% include "new_security/_scripts.html" %}
+
+{% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/forgot_password_step2.html b/wqflask/wqflask/templates/new_security/forgot_password_step2.html
new file mode 100644
index 00000000..1295e589
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/forgot_password_step2.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+    <header class="jumbotron subhead" id="overview">
+        <div class="container">
+            <h1>Password Reset</h1>
+            <p class="lead">
+                Check your email.
+            </p>
+        </div>
+    </header>
+
+    <div class="container">
+        <div class="page-header">
+            <h3>One last step</h3>
+        </div>
+
+        <p>You will receive an email with the subject <strong>"{{ subject }}"</strong>.</p>
+
+        <p>You must click the link in the email to reset the password.</p>
+
+        <p>If you don't see the email, check your spam folder.</p>
+    </div>
+
+{% endblock %}
+
+{% block js %}
+    <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>-->
+
+    {% include "new_security/_scripts.html" %}
+    <script type="text/javascript" src="/static/new/js_external/zxcvbn/zxcvbn-async.js"></script>
+    <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
+{% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html
index f232ccbc..4e308c75 100644
--- a/wqflask/wqflask/templates/new_security/login_user.html
+++ b/wqflask/wqflask/templates/new_security/login_user.html
@@ -2,82 +2,81 @@
 {% block title %}Register{% endblock %}
 {% block content %}
 
-    {{ header("Login", "Gain access to GeneNetwork.") }}
+    {{ header("Sign in", "Gain access to GeneNetwork") }}
 
     <div class="container">
         <div class="page-header">
             <h1>Login</h1>
         </div>
-    
+
 
         <div class="security_box">
-        
+
             <h4>Don't have an account?</h4>
-        
-            
+
+
             <a href="/n/register" class="btn btn-info modalize">Create a new account</a>
-            
-        
+
+
             <hr />
-        
+
             <h4>Already have an account?</h4>
-        
+
             <h5>Sign in here</h5>
-            
-           
-        
+
+
+
             <form class="form-horizontal" action="/n/login"
                   method="POST" name="login_user_form">
                 <fieldset>
-        
-        
+
+
                     <div class="control-group">
                         <label class="control-label" for="email_address">Email Address</label>
                         <div class="controls">
                             <input id="email_address" class="focused" name="email_address" type="text" value="">
                         </div>
                     </div>
-        
+
                     <div class="control-group">
                         <label class="control-label" for="password">Password</label>
                         <div class="controls">
                             <input id="password" name="password" type="password" value="">
                             <br />
-                            <a href="url_for_security('forgot_password')">Forgot your password?</a><br/>
+                            <a href="/n/forgot_password">Forgot your password?</a><br/>
                         </div>
                     </div>
-        
-        
+
+
                     <div class="control-group">
                         <div class="controls">
                             <label class="checkbox">
                                 <input id="remember" name="remember" type="checkbox" value="y"> Remember me
                             </label>
                         </div>
-        
-        
+
+
                     <div class="control-group">
                         <div class="controls">
                             <input id="next" name="next" type="hidden" value="">
-        
+
                             <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Sign in">
                         </div>
-        
-        
+
+
                     </div>
               </fieldset>
-        
+
             </form>
         </div>
         </div>
     </div>
-        
+
   {% endblock %}
 
-{% block js %}  
+{% block js %}
     <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>-->
-    
+
     {% include "new_security/_scripts.html" %}
 
 {% endblock %}
-   
diff --git a/wqflask/wqflask/templates/new_security/password_reset.html b/wqflask/wqflask/templates/new_security/password_reset.html
new file mode 100644
index 00000000..cda1e477
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/password_reset.html
@@ -0,0 +1,80 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+
+    {{ header("Password Reset", "Create a new password.") }}
+
+
+    <div class="container">
+        <div class="page-header">
+            <h1>Password reset</h1>
+        </div>
+
+        <div class="security_box">
+
+
+            <h4>Enter your new password</h4>
+
+
+            {% if errors %}
+                <div class="alert alert-error">
+                    <strong>Please note:</strong>
+                    <ul>
+                        {% for error in errors %}
+                            <li>{{error}}</li>
+                        {% endfor %}
+                    </ul>
+                </div>
+            {% endif %}
+
+            <form class="form-horizontal" action="/n/password_reset_step2" data-validate="parsley"
+                  method="POST" name="login_user_form">
+
+                <fieldset>
+
+                    <input class="hidden" name="user_encode" value="{{ user_encode }}">
+
+
+                    <div class="control-group">
+                        <label class="control-label" for="password">Password</label>
+                        <div class="controls">
+                            <input id="password" name="password" type="password" value=""
+                                   data-trigger="change" data-required="true" data-minlength="6">
+                        </div>
+                    </div>
+
+                    <div class="control-group" style="display: none" id="password_alert">
+                        <div class="controls"">
+                            <span id="password_strength" class="alert"></span>
+                        </div>
+                    </div>
+
+                    <div class="control-group">
+                        <label class="control-label" for="password_confirm">Confirm Password</label>
+                        <div class="controls">
+                            <input id="password" name="password_confirm" type="password" value=""
+                                   data-trigger="change" data-required="true" data-equalto="#password">
+                        </div>
+                    </div>
+
+                    <div class="control-group">
+                        <div class="controls"">
+                            <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Reset password">
+                        </div>
+                    </div>
+
+                </fieldset>
+
+            </form>
+        </div>
+    </div>
+
+{% endblock %}
+
+{% block js %}
+    <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>-->
+
+    {% include "new_security/_scripts.html" %}
+    <script type="text/javascript" src="/static/new/js_external/zxcvbn/zxcvbn-async.js"></script>
+    <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
+{% endblock %}
diff --git a/wqflask/wqflask/templates/new_security/register_user.html b/wqflask/wqflask/templates/new_security/register_user.html
index 2a02e7ca..998d2a7b 100644
--- a/wqflask/wqflask/templates/new_security/register_user.html
+++ b/wqflask/wqflask/templates/new_security/register_user.html
@@ -1,36 +1,31 @@
 {% extends "base.html" %}
 {% block title %}Register{% endblock %}
 {% block content %}
-    <header class="jumbotron subhead" id="overview">
-        <div class="container">
-            <h1>Register</h1>
-            <p class="lead">
-                It's easy and fast to make an account.
-            </p>
-        </div>
-    </header>
+
+    {{ header("Register", "It's fast and easy to make an account.") }}
+
 
     <div class="container">
         <div class="page-header">
             <h1>Registration</h1>
         </div>
-    
+
         <div class="security_box">
             <h4>Already have an account?</h4>
-        
-            
+
+
             <a href="/n/login"
                 class="btn btn-info modalize">Sign in using existing account</a>
-            
-        
+
+
             <hr />
-        
+
             <h4>Don't have an account?</h4>
-        
+
             <h5>Register here</h5>
-            
+
             {% if errors %}
-                <div class="alert alert-error">  
+                <div class="alert alert-error">
                     <strong>Please note:</strong>
                     <ul>
                         {% for error in errors %}
@@ -39,11 +34,11 @@
                     </ul>
                 </div>
             {% endif %}
-           
+
             <form class="form-horizontal" action="/n/register" data-validate="parsley"
                   method="POST" name="login_user_form">
                 <fieldset>
-        
+
                     <div class="control-group">
                         <label class="control-label" for="email_address">Email Address</label>
                         <div class="controls">
@@ -51,7 +46,7 @@
                                    data-trigger="change" data-required="true" data-type="email" data-maxlength="50">
                         </div>
                     </div>
-                    
+
                     <div class="control-group">
                         <label class="control-label" for="full_name">Full Name</label>
                         <div class="controls">
@@ -59,28 +54,28 @@
                                    data-trigger="change" data-required="true" data-minlength="5" data-maxlength="50">
                         </div>
                     </div>
-        
+
                     <div class="control-group">
                         <label class="control-label" for="organization">Organization</label>
                         <div class="controls">
                             <input id="organization" name="organization" type="text" value="{{values.organization}}" data-minlength="3" data-maxlength="50">
                         </div>
                     </div>
-                    
+
                     <div class="control-group">
                         <label class="control-label" for="password">Password</label>
                         <div class="controls">
                             <input id="password" name="password" type="password" value=""
                                    data-trigger="change" data-required="true" data-minlength="6">
                         </div>
-                    </div>           
-      
+                    </div>
+
                     <div class="control-group" style="display: none" id="password_alert">
                         <div class="controls"">
                             <span id="password_strength" class="alert"></span>
                         </div>
                     </div>
-             
+
                     <div class="control-group">
                         <label class="control-label" for="password_confirm">Confirm Password</label>
                         <div class="controls">
@@ -88,26 +83,25 @@
                                    data-trigger="change" data-required="true" data-equalto="#password">
                         </div>
                     </div>
-        
+
                     <div class="control-group">
                         <div class="controls"">
                             <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Create account">
                         </div>
                     </div>
-        
+
                 </fieldset>
-        
+
             </form>
         </div>
     </div>
 
 {% endblock %}
 
-{% block js %}  
+{% block js %}
     <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>-->
-    
+
     {% include "new_security/_scripts.html" %}
     <script type="text/javascript" src="/static/new/js_external/zxcvbn/zxcvbn-async.js"></script>
     <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
 {% endblock %}
-
diff --git a/wqflask/wqflask/templates/new_security/registered.html b/wqflask/wqflask/templates/new_security/registered.html
index 391a6044..49dc961f 100644
--- a/wqflask/wqflask/templates/new_security/registered.html
+++ b/wqflask/wqflask/templates/new_security/registered.html
@@ -15,20 +15,19 @@
             <h3>One last step</h3>
         </div>
     
-        <p>You will receive an email with the subject <strong>"GeneNetwork email verification"</strong>.</p>
-        
+        <p>You will receive an email with the subject <strong>"{{ subject }}"</strong>.</p>
+
         <p>You must click the link in the email to complete registration.</p>
-        
+
         <p>If you don't see the email, check your spam folder.</p>
     </div>
 
 {% endblock %}
 
-{% block js %}  
+{% block js %}
     <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>-->
-    
+
     {% include "new_security/_scripts.html" %}
     <script type="text/javascript" src="/static/new/js_external/zxcvbn/zxcvbn-async.js"></script>
     <script type="text/javascript" src="/static/new/javascript/password_strength.js"></script>
 {% endblock %}
-
diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py
index a2dff7f2..766f49df 100644
--- a/wqflask/wqflask/user_manager.py
+++ b/wqflask/wqflask/user_manager.py
@@ -17,10 +17,15 @@ import hashlib
 import hmac
 import base64
 
+import urlparse
+
 import simplejson as json
 
-from redis import StrictRedis
-Redis = StrictRedis()
+from sqlalchemy import orm
+
+#from redis import StrictRedis
+import redis
+Redis = redis.StrictRedis()
 
 
 from flask import (Flask, g, render_template, url_for, request, make_response,
@@ -53,7 +58,7 @@ def timestamp():
 
 class UserSession(object):
     cookie_name = 'session_id'
-    
+
     def __init__(self):
         cookie = request.cookies.get(self.cookie_name)
         if not cookie:
@@ -70,13 +75,13 @@ class UserSession(object):
             self.record = Redis.hgetall(self.redis_key)
             print("record is:", self.record)
             self.logged_in = True
-        
-        
+
+
     def delete_session(self):
         # And more importantly delete the redis record
         Redis.delete(self.cookie_name)
         print("At end of delete_session")
-        
+
 @app.before_request
 def before_request():
     g.user_session = UserSession()
@@ -105,22 +110,22 @@ class UserManager(object):
             print("  ID:", dataset.id)
             print("  Confidential:", dataset.check_confidentiality())
         #print("   ---> self.datasets:", self.datasets)
-        
+
 
 class RegisterUser(object):
     def __init__(self, kw):
         self.thank_you_mode = False
         self.errors = []
         self.user = Bunch()
-        
+
         self.user.email_address = kw.get('email_address', '').strip()
         if not (5 <= len(self.user.email_address) <= 50):
             self.errors.append('Email Address needs to be between 5 and 50 characters.')
-            
+
         self.user.full_name = kw.get('full_name', '').strip()
         if not (5 <= len(self.user.full_name) <= 50):
             self.errors.append('Full Name needs to be between 5 and 50 characters.')
-            
+
         self.user.organization = kw.get('organization', '').strip()
         if self.user.organization and not (5 <= len(self.user.organization) <= 50):
             self.errors.append('Organization needs to be empty or between 5 and 50 characters.')
@@ -128,81 +133,94 @@ class RegisterUser(object):
         password = str(kw.get('password', ''))
         if not (6 <= len(password)):
             self.errors.append('Password needs to be at least 6 characters.')
-            
+
         if kw.get('password_confirm') != password:
             self.errors.append("Passwords don't match.")
-        
+
         if self.errors:
             return
-        
+
         print("No errors!")
-        
-        self.set_password(password)
-        
+
+        set_password(password, user)
+
         self.user.registration_info = json.dumps(basic_info(), sort_keys=True)
-        
+
         self.new_user = model.User(**self.user.__dict__)
         db_session.add(self.new_user)
         db_session.commit()
-        
+
         print("Adding verification email to queue")
-        self.send_email_verification()
+        #self.send_email_verification()
+        VerificationEmail(self.new_user)
         print("Added verification email to queue")
-        
+
         self.thank_you_mode = True
-        
-    
-    def set_password(self, password):
-        pwfields = Bunch()
-        
-        pwfields.algorithm = "pbkdf2"
-        pwfields.hashfunc = "sha256"
-        #hashfunc = getattr(hashlib, pwfields.hashfunc)
-        
-        # Encoding it to base64 makes storing it in json much easier 
-        pwfields.salt = base64.b64encode(os.urandom(32))
-
-        # https://forums.lastpass.com/viewtopic.php?t=84104
-        pwfields.iterations = 100000   
-        pwfields.keylength = 32
-        
-        pwfields.created_ts = timestamp()
-        # One more check on password length
-        assert len(password) >= 6, "Password shouldn't be so short here"
-        
-        print("pwfields:", vars(pwfields))
-        print("locals:", locals())
-
-        enc_password = Password(password,
-                                pwfields.salt,
-                                pwfields.iterations,
-                                pwfields.keylength,
-                                pwfields.hashfunc)
-        
-        pwfields.password = enc_password.password
-        pwfields.encrypt_time = enc_password.encrypt_time
-        
-        self.user.password = json.dumps(pwfields.__dict__,
-                                        sort_keys=True,             
-                                       )
-        
-    def send_email_verification(self):
+
+
+def set_password(password, user):
+    pwfields = Bunch()
+
+    pwfields.algorithm = "pbkdf2"
+    pwfields.hashfunc = "sha256"
+    #hashfunc = getattr(hashlib, pwfields.hashfunc)
+
+    # Encoding it to base64 makes storing it in json much easier
+    pwfields.salt = base64.b64encode(os.urandom(32))
+
+    # https://forums.lastpass.com/viewtopic.php?t=84104
+    pwfields.iterations = 100000
+    pwfields.keylength = 32
+
+    pwfields.created_ts = timestamp()
+    # One more check on password length
+    assert len(password) >= 6, "Password shouldn't be so short here"
+
+    print("pwfields:", vars(pwfields))
+    print("locals:", locals())
+
+    enc_password = Password(password,
+                            pwfields.salt,
+                            pwfields.iterations,
+                            pwfields.keylength,
+                            pwfields.hashfunc)
+
+    pwfields.password = enc_password.password
+    pwfields.encrypt_time = enc_password.encrypt_time
+
+    user.password = json.dumps(pwfields.__dict__,
+                                    sort_keys=True,
+                                   )
+
+
+class VerificationEmail(object):
+    template_name =  "email/verification.txt"
+    key_prefix = "verification_code"
+    subject = "GeneNetwork email verification"
+
+    def __init__(self, user):
         verification_code = str(uuid.uuid4())
-        key = "verification_code:" + verification_code
-        
-        data = json.dumps(dict(id=self.new_user.id,
+        key = self.key_prefix + ":" + verification_code
+
+        data = json.dumps(dict(id=user.id,
                                timestamp=timestamp())
                           )
-                          
+
         Redis.set(key, data)
         two_days = 60 * 60 * 24 * 2
-        Redis.expire(key, two_days)  
-        to = self.user.email_address
-        subject = "GeneNetwork email verification"
-        body = render_template("email/verification.txt",
+        Redis.expire(key, two_days)
+        to = user.email_address
+        subject = self.subject
+        body = render_template(self.template_name,
                                verification_code = verification_code)
         send_email(to, subject, body)
 
+class ForgotPasswordEmail(VerificationEmail):
+    template_name = "email/forgot_password.txt"
+    key_prefix = "forgot_password_code"
+    subject = "GeneNetwork password reset"
+
+
 class Password(object):
     def __init__(self, unencrypted_password, salt, iterations, keylength, hashfunc):
         hashfunc = getattr(hashlib, hashfunc)
@@ -215,24 +233,80 @@ class Password(object):
                                           salt, iterations, keylength, hashfunc)
         self.encrypt_time = round(time.time() - start_time, 3)
         print("Creating password took:", self.encrypt_time)
-        
-    
+
+
 def basic_info():
     return dict(timestamp = timestamp(),
                 ip_address = request.remote_addr,
                 user_agent = request.headers.get('User-Agent'))
 
+@app.route("/manage/verify_email")
 def verify_email():
-    print("in verify_email request.url is:", request.url)
-    verify_url_hmac(request.url)
-    verification_code = request.args['code']
-    data = Redis.get("verification_code:" + verification_code)
-    data = json.loads(data)
-    print("data is:", data)
-    user = model.User.query.get(data['id'])
+    user = DecodeUser(VerificationEmail.key_prefix).user
     user.confirmed = json.dumps(basic_info(), sort_keys=True)
     db_session.commit()
-                                
+
+@app.route("/n/password_reset")
+def password_reset():
+    print("in password_reset request.url is:", request.url)
+
+    # We do this mainly just to assert that it's in proper form for displaying next page
+    # Really not necessary but doesn't hurt
+    user_encode = DecodeUser(ForgotPasswordEmail.key_prefix).reencode_standalone()
+
+    return render_template("new_security/password_reset.html", user_encode=user_encode)
+
+@app.route("/n/password_reset_step2", methods=('POST',))
+def password_reset_step2():
+    print("in password_reset request.url is:", request.url)
+
+    errors = []
+
+    user_encode = request.form['user_encode']
+    verification_code, separator, hmac = user_encode.partition(':')
+
+    hmac_verified = actual_hmac_creation(verification_code)
+    print("locals are:", locals())
+
+
+    assert hmac == hmac_verified, "Someone has been naughty"
+
+    user = DecodeUser.actual_get_user(ForgotPasswordEmail.key_prefix, verification_code)
+    print("user is:", user)
+
+    password = request.form['password']
+
+    set_password(password, user)
+    db_session.commit()
+
+    flash("Password changed successfully. You can now sign in.", "alert-info")
+    response = make_response(redirect(url_for('login')))
+
+    return response
+
+class DecodeUser(object):
+
+    def __init__(self, code_prefix):
+        verify_url_hmac(request.url)
+
+        #params = urlparse.parse_qs(url)
+
+        self.verification_code = request.args['code']
+        self.user = self.actual_get_user(code_prefix, self.verification_code)
+
+    def reencode_standalone(self):
+        hmac = actual_hmac_creation(self.verification_code)
+        return self.verification_code + ":" + hmac
+
+    @staticmethod
+    def actual_get_user(code_prefix, verification_code):
+        data = Redis.get(code_prefix + ":" + verification_code)
+        print("in get_coded_user, data is:", data)
+        data = json.loads(data)
+        print("data is:", data)
+        return model.User.query.get(data['id'])
+
+@app.route("/n/login", methods=('GET', 'POST'))
 def login():
     params = request.form if request.form else request.args
     print("in login params are:", params)
@@ -250,10 +324,10 @@ def login():
         print("\n\nComparing:\n{}\n{}\n".format(encrypted.password, pwfields.password))
         valid = pbkdf2.safe_str_cmp(encrypted.password, pwfields.password)
         print("valid is:", valid)
-        
+
         login_rec = model.Login(user)
-        
-        
+
+
         if valid:
             login_rec.successful = True
             login_rec.session_id = str(uuid.uuid4())
@@ -261,17 +335,17 @@ def login():
             session_id_signature = actual_hmac_creation(login_rec.session_id)
             session_id_signed = login_rec.session_id + ":" + session_id_signature
             print("session_id_signed:", session_id_signed)
-            
+
             session = dict(login_time = time.time(),
                            user_id = user.id,
                            user_email_address = user.email_address)
-            
-            flash("Thank you for logging in.", "alert-success")
-            
+
+            flash("Thank you for logging in {}.".format(user.full_name), "alert-success")
+
             key = "session_id:" + login_rec.session_id
             print("Key when signing:", key)
             Redis.hmset(key, session)
-            
+
             response = make_response(redirect(url_for('index_page')))
             response.set_cookie(UserSession.cookie_name, session_id_signed)
         else:
@@ -281,8 +355,8 @@ def login():
         db_session.add(login_rec)
         db_session.commit()
         return response
- 
-@app.route("/n/logout")     
+
+@app.route("/n/logout")
 def logout():
     print("Logging out...")
     UserSession().delete_session()
@@ -291,15 +365,89 @@ def logout():
     # Delete the cookie
     response.set_cookie(UserSession.cookie_name, '', expires=0)
     return response
-    
-       
+
+
+@app.route("/n/forgot_password")
+def forgot_password():
+    return render_template("new_security/forgot_password.html")
+
+@app.route("/n/forgot_password_submit", methods=('POST',))
+def forgot_password_submit():
+    params = request.form
+    email_address = params['email_address']
+    try:
+        user = model.User.query.filter_by(email_address=email_address).one()
+    except orm.exc.NoResultFound:
+        flash("Couldn't find a user associated with the email address {}. Sorry.".format(
+            email_address))
+        return redirect(url_for("login"))
+    ForgotPasswordEmail(user)
+    return render_template("new_security/forgot_password_step2.html",
+                            subject=ForgotPasswordEmail.subject)
+
+
+
+@app.route("/manage/users")
+def manage_users():
+    template_vars = UsersManager()
+    return render_template("admin/user_manager.html", **template_vars.__dict__)
+
+@app.route("/manage/user")
+def manage_user():
+    template_vars = UserManager(request.args)
+    return render_template("admin/ind_user_manager.html", **template_vars.__dict__)
+
+@app.route("/manage/groups")
+def manage_groups():
+    template_vars = GroupsManager(request.args)
+    return render_template("admin/group_manager.html", **template_vars.__dict__)
+
+
+@app.route("/n/register", methods=('GET', 'POST'))
+def register():
+    params = None
+    errors = None
+
+    #if request.form:
+    #    params = request.form
+    #else:
+    #    params = request.args
+
+    params = request.form if request.form else request.args
+
+    if params:
+        print("Attempting to register the user...")
+        result = RegisterUser(params)
+        errors = result.errors
+
+        if result.thank_you_mode:
+            assert not errors, "Errors while in thank you mode? That seems wrong..."
+            return render_template("new_security/registered.html",
+                                   subject=VerificationEmail.subject)
+
+    return render_template("new_security/register_user.html", values=params, errors=errors)
+
+
+
+
+#@app.route("/n/login", methods=('GET', 'POST'))
+#def login():
+#    return user_manager.login()
+#
+#@app.route("/manage/verify")
+#def verify():
+#    user_manager.verify_email()
+#    return render_template("new_security/verified.html")
+
+
+
 ################################# Sign and unsign #####################################
 
 def url_for_hmac(endpoint, **values):
     """Like url_for but adds an hmac at the end to insure the url hasn't been tampered with"""
 
     url = url_for(endpoint, **values)
-    
+
     hm = actual_hmac_creation(url)
     if '?' in url:
         combiner = "&"
@@ -323,10 +471,10 @@ def verify_url_hmac(url):
     hm = actual_hmac_creation(url)
 
     assert hm == hmac, "Unexpected url (stage 3)"
-    
+
 def actual_hmac_creation(stringy):
     """Helper function to create the actual hmac"""
-    
+
     secret = app.config['SECRET_HMAC_CODE']
 
     hmaced = hmac.new(secret, stringy, hashlib.sha1)
@@ -339,14 +487,14 @@ def actual_hmac_creation(stringy):
 app.jinja_env.globals.update(url_for_hmac=url_for_hmac)
 
 #######################################################################################
-        
+
 def send_email(to, subject, body):
     msg = json.dumps(dict(From="no-reply@genenetwork.org",
                      To=to,
                      Subject=subject,
                      Body=body))
     Redis.rpush("mail_queue", msg)
-    
+
 
 #def combined_salt(user_salt):
 #    """Combine the master salt with the user salt...we use two seperate salts so that if the database is compromised, the
@@ -358,7 +506,7 @@ def send_email(to, subject, body):
 #    for x, y in user_salt, secret_salt:
 #        combined = combined + x + y
 #    return combined
-    
+
 
 
 class GroupsManager(object):
@@ -374,9 +522,5 @@ class RolesManager(object):
 
 #class Password(object):
 #    """To generate a master password: dd if=/dev/urandom bs=32 count=1 > master_salt"""
-#    
-#    master_salt = 
-
-
-
-
+#
+#    master_salt =
diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py
index deccf459..a060ba3c 100644
--- a/wqflask/wqflask/views.py
+++ b/wqflask/wqflask/views.py
@@ -51,7 +51,7 @@ from wqflask import user_manager
 @app.before_request
 def connect_db():
     g.db = sqlalchemy.create_engine(app.config['DB_URI'])
-    
+
 #@app.before_request
 #def trace_it():
 #    from wqflask import tracer
@@ -277,68 +277,6 @@ def get_temp_data():
 ###################################################################################################
 
 
-@app.route("/manage/users")
-def manage_users():
-    template_vars = user_manager.UsersManager()
-    return render_template("admin/user_manager.html", **template_vars.__dict__)
-
-@app.route("/manage/user")
-def manage_user():
-    template_vars = user_manager.UserManager(request.args)
-    return render_template("admin/ind_user_manager.html", **template_vars.__dict__)
-
-@app.route("/manage/groups")
-def manage_groups():
-    template_vars = user_manager.GroupsManager(request.args)
-    return render_template("admin/group_manager.html", **template_vars.__dict__)
-
-
-@app.route("/n/register", methods=('GET', 'POST'))
-def register():
-    params = None
-    errors = None
-
-    #if request.form:
-    #    params = request.form
-    #else:
-    #    params = request.args
-    
-    params = request.form if request.form else request.args
-    
-    if params:
-        print("Attempting to register the user...")
-        result = user_manager.RegisterUser(params)
-        errors = result.errors
-        
-        if result.thank_you_mode:
-            assert not errors, "Errors while in thank you mode? That seems wrong..."
-            return render_template("new_security/registered.html")
-       
-    return render_template("new_security/register_user.html", values=params, errors=errors)
-
-#@app.route("/n/register_submit", methods=('POST',))
-#def register_submit():
-#    print("request.args are: ", request.args)
-#    result = user_manager.RegisterUser(request.form)
-#    if result.errors:
-#        print("Redirecting")
-#        # 307 preserves the post on the redirect (maybe)
-#        errors = result.errors
-#        #errors = json.dumps(errors)
-#        print("request.args are: ", request.args)
-#        return render_template("new_security/register_user.html", errors=errors, values=request.form)
-#        #return redirect(url_for('new_register', errors=errors), code=307)
-
-
-@app.route("/n/login", methods=('GET', 'POST'))
-def login():
-    return user_manager.login()
-
-@app.route("/manage/verify")
-def verify():
-    user_manager.verify_email()
-    return render_template("new_security/verified.html")
-
 
 ##########################################################################