about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLei Yan2013-10-11 14:49:54 -0500
committerLei Yan2013-10-11 14:49:54 -0500
commit79aed6879f138bb083af91f50cd4827789683062 (patch)
tree722be02d7f7ecbc7094a7519f59f6802e123fb97
parent8a09358e98dbf88deb101d13107a40bac371de5c (diff)
parent38ae30a5fb46753a361e1c7454871430d7097c3b (diff)
downloadgenenetwork2-79aed6879f138bb083af91f50cd4827789683062.tar.gz
Merge /home/sam/gene
-rw-r--r--wqflask/flask_security/__init__.py4
-rw-r--r--wqflask/flask_security/core.py1
-rw-r--r--wqflask/flask_security/datastore.py1
-rw-r--r--wqflask/flask_security/forms.py9
-rw-r--r--wqflask/flask_security/registerable.py1
-rw-r--r--wqflask/other_config/nginx_conf/sam.conf42
-rw-r--r--wqflask/secure_server.py8
-rwxr-xr-xwqflask/utility/__init__.py23
-rw-r--r--wqflask/wqflask/database.py26
-rw-r--r--wqflask/wqflask/model.py110
-rw-r--r--wqflask/wqflask/pbkdf2.py140
-rw-r--r--wqflask/wqflask/send_mail.py45
-rw-r--r--wqflask/wqflask/static/new/css/parsley.css20
-rw-r--r--wqflask/wqflask/static/new/javascript/password_strength.coffee33
-rw-r--r--wqflask/wqflask/static/new/javascript/password_strength.js42
-rw-r--r--wqflask/wqflask/static/new/js_external/parsley.min.js35
-rw-r--r--wqflask/wqflask/static/new/js_external/zxcvbn/zxcvbn-async.js1
-rw-r--r--wqflask/wqflask/templates/admin/ind_user_manager.html10
-rw-r--r--wqflask/wqflask/templates/admin/user_manager.html14
-rw-r--r--wqflask/wqflask/templates/base.html43
-rw-r--r--wqflask/wqflask/templates/email/verification.txt8
-rw-r--r--wqflask/wqflask/templates/index_page.html7
-rw-r--r--wqflask/wqflask/templates/new_security/_scripts.html1
-rw-r--r--wqflask/wqflask/templates/new_security/login_user.html83
-rw-r--r--wqflask/wqflask/templates/new_security/register_user.html113
-rw-r--r--wqflask/wqflask/templates/new_security/registered.html34
-rw-r--r--wqflask/wqflask/templates/new_security/thank_you.html32
-rw-r--r--wqflask/wqflask/templates/new_security/verified.html32
-rw-r--r--wqflask/wqflask/templates/security/register_user.html16
-rw-r--r--wqflask/wqflask/tracer.py41
-rw-r--r--wqflask/wqflask/user_manager.py336
-rw-r--r--wqflask/wqflask/views.py65
32 files changed, 1314 insertions, 62 deletions
diff --git a/wqflask/flask_security/__init__.py b/wqflask/flask_security/__init__.py
index 297033c9..81e6c89e 100644
--- a/wqflask/flask_security/__init__.py
+++ b/wqflask/flask_security/__init__.py
@@ -12,8 +12,6 @@
 
 __version__ = '1.6.0'
 
-print "using internal flask security"
-
 from .core import Security, RoleMixin, UserMixin, AnonymousUser, current_user
 from .datastore import SQLAlchemyUserDatastore, MongoEngineUserDatastore, PeeweeUserDatastore
 from .decorators import auth_token_required, http_auth_required, \
@@ -23,3 +21,5 @@ from .forms import ForgotPasswordForm, LoginForm, RegisterForm, \
 from .signals import confirm_instructions_sent, password_reset, \
      reset_password_instructions_sent, user_confirmed, user_registered
 from .utils import login_user, logout_user, url_for_security
+
+print "Using our own flask.ext.security"
\ No newline at end of file
diff --git a/wqflask/flask_security/core.py b/wqflask/flask_security/core.py
index d794fad5..0f3a231f 100644
--- a/wqflask/flask_security/core.py
+++ b/wqflask/flask_security/core.py
@@ -207,6 +207,7 @@ def _get_serializer(app, name):
 
 def _get_state(app, datastore, **kwargs):
     for key, value in get_config(app).items():
+        print "in _get_state [{}]: {}".format(key, value)
         kwargs[key.lower()] = value
 
     kwargs.update(dict(
diff --git a/wqflask/flask_security/datastore.py b/wqflask/flask_security/datastore.py
index f8c7218d..634399d9 100644
--- a/wqflask/flask_security/datastore.py
+++ b/wqflask/flask_security/datastore.py
@@ -157,6 +157,7 @@ class UserDatastore(object):
         """Creates and returns a new user from the given parameters."""
 
         user = self.user_model(**self._prepare_create_user_args(**kwargs))
+        print "in abstraced create_user, user is:", user
         return self.put(user)
 
     def delete_user(self, user):
diff --git a/wqflask/flask_security/forms.py b/wqflask/flask_security/forms.py
index e64e1502..54677e77 100644
--- a/wqflask/flask_security/forms.py
+++ b/wqflask/flask_security/forms.py
@@ -89,6 +89,12 @@ def valid_user_email(form, field):
 
 class Form(BaseForm):
     def __init__(self, *args, **kwargs):
+        #print "importing tracer"
+        #from wqflask import tracer
+        #tracer.turn_on()
+        #print "imported tracer"
+        print "in Form, args:", args
+        print "in Form, kwargs:", kwargs
         if current_app.testing:
             self.TIME_LIMIT = None
         super(Form, self).__init__(*args, **kwargs)
@@ -148,10 +154,13 @@ class RegisterFormMixin():
 
     def to_dict(form):
         def is_field_and_user_attr(member):
+            print "in ifaua:", member
             return isinstance(member, Field) and \
                 hasattr(_datastore.user_model, member.name)
 
+        print("green:", vars(form))
         fields = inspect.getmembers(form, is_field_and_user_attr)
+        print("fields:" ,vars(form))
         return dict((key, value.data) for key, value in fields)
 
 
diff --git a/wqflask/flask_security/registerable.py b/wqflask/flask_security/registerable.py
index 4e9f357d..4606c7c6 100644
--- a/wqflask/flask_security/registerable.py
+++ b/wqflask/flask_security/registerable.py
@@ -24,6 +24,7 @@ _datastore = LocalProxy(lambda: _security.datastore)
 
 
 def register_user(**kwargs):
+    print "in register_user kwargs:", kwargs
     confirmation_link, token = None, None
     kwargs['password'] = encrypt_password(kwargs['password'])
     user = _datastore.create_user(**kwargs)
diff --git a/wqflask/other_config/nginx_conf/sam.conf b/wqflask/other_config/nginx_conf/sam.conf
new file mode 100644
index 00000000..3cc443a8
--- /dev/null
+++ b/wqflask/other_config/nginx_conf/sam.conf
@@ -0,0 +1,42 @@
+server {
+    # Modeled after http://flask.pocoo.org/docs/deploying/wsgi-standalone/
+    listen 80;
+
+    server_name sam.genenetwork.org;
+
+    access_log  /var/log/nginx/access.log;
+    error_log  /var/log/nginx/error.log;
+
+    location ^~ /css/ {
+            root /gene/wqflask/wqflask/static/;
+    }
+
+    location ^~ /javascript/ {
+            root /gene/wqflask/wqflask/static/;
+    }
+
+#    location ^~ /image/ {
+#           root /gene/wqflask/wqflask/static/;
+#       }
+
+    location ^~ /images/ {
+            root /gene/wqflask/wqflask/static/;
+    }
+    
+    ### New - added by Sam
+    #location ^~ /static/ {
+    #       root /gene/wqflask/wqflask/static/;
+    #}
+
+    location / {
+            proxy_pass         http://127.0.0.1:5555/;
+            proxy_redirect     off;
+
+            proxy_set_header   Host             $host;
+            proxy_set_header   X-Real-IP        $remote_addr;
+            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
+            
+            proxy_read_timeout 40m;
+    }
+}
+
diff --git a/wqflask/secure_server.py b/wqflask/secure_server.py
index 2abfdb05..a77abf7e 100644
--- a/wqflask/secure_server.py
+++ b/wqflask/secure_server.py
@@ -1,4 +1,4 @@
-from __future__ import print_function, division, absolute_import
+from __future__ import absolute_import, division, print_function
 
 from wqflask import app
 
@@ -25,6 +25,12 @@ app.logger.addHandler(file_handler)
 import logging_tree
 logging_tree.printout()
 
+#import sys
+#print("At startup, path is:", sys.path)
+
+from werkzeug.contrib.fixers import ProxyFix
+app.wsgi_app = ProxyFix(app.wsgi_app)
+
 #print("app.config is:", app.config)
 
 if __name__ == '__main__':
diff --git a/wqflask/utility/__init__.py b/wqflask/utility/__init__.py
index d0e4a3fa..d9856eed 100755
--- a/wqflask/utility/__init__.py
+++ b/wqflask/utility/__init__.py
@@ -1,5 +1,6 @@
 from pprint import pformat as pf
 
+# Todo: Move these out of __init__
 
 class Bunch(object):
     """Like a dictionary but using object notation"""
@@ -10,3 +11,25 @@ class Bunch(object):
         return pf(self.__dict__)
 
 
+class Struct(object):
+    '''The recursive class for building and representing objects with.
+
+    From http://stackoverflow.com/a/6573827/1175849
+
+    '''
+
+    def __init__(self, obj):
+        for k, v in obj.iteritems():
+            if isinstance(v, dict):
+                setattr(self, k, Struct(v))
+            else:
+                setattr(self, k, v)
+
+    def __getitem__(self, val):
+        return self.__dict__[val]
+
+    def __repr__(self):
+        return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for
+            (k, v) in self.__dict__.iteritems()))
+
+
diff --git a/wqflask/wqflask/database.py b/wqflask/wqflask/database.py
new file mode 100644
index 00000000..e55f06a7
--- /dev/null
+++ b/wqflask/wqflask/database.py
@@ -0,0 +1,26 @@
+from sqlalchemy import create_engine
+from sqlalchemy.orm import scoped_session, sessionmaker
+from sqlalchemy.ext.declarative import declarative_base
+
+from wqflask import app
+
+#engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
+engine = create_engine(app.config['DB_URI'], convert_unicode=True)
+
+db_session = scoped_session(sessionmaker(autocommit=False,
+                                         autoflush=False,
+                                         bind=engine))
+Base = declarative_base()
+Base.query = db_session.query_property()
+
+def init_db():
+    # import all modules here that might define models so that
+    # they will be registered properly on the metadata.  Otherwise
+    # you will have to import them first before calling init_db()
+    #import yourapplication.models
+    import wqflask.model
+    print("Creating all..")
+    Base.metadata.create_all(bind=engine)
+    print("Done creating all...")
+    
+init_db()
\ No newline at end of file
diff --git a/wqflask/wqflask/model.py b/wqflask/wqflask/model.py
index 5beba9ff..5c514bde 100644
--- a/wqflask/wqflask/model.py
+++ b/wqflask/wqflask/model.py
@@ -1,12 +1,25 @@
 from __future__ import print_function, division, absolute_import
 
+import uuid
+import datetime
+
+from flask import request
 from flask.ext.sqlalchemy import SQLAlchemy
-from flask.ext.security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin
+#from flask.ext.security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin
+
+#from flask_security.forms import TextField
+#from flask_security.forms import RegisterForm
 
 from wqflask import app
 
+from sqlalchemy import Column, Integer, String, Table, ForeignKey, Unicode, Boolean, DateTime, Text
+from sqlalchemy.orm import relationship, backref
+
+from wqflask.database import Base, init_db
+
 # Create database connection object
-db = SQLAlchemy(app)
+#db = SQLAlchemy(app)
+
 
 # Is this right? -Sam
 #from sqlalchemy.ext.declarative import declarative_base
@@ -23,45 +36,76 @@ db = SQLAlchemy(app)
 #    """
 #    print("in get cls is:", cls)
 #    print("  key is {} : {}".format(type(key), key))
-#    query = db.Model.query(cls)
+#    query = Model.query(cls)
 #    print("query is: ", query)
 #    record = query.get(key)
 #    return record
 #
 #
-#print("db.Model is:", vars(db.Model))
-#db.Model.get = get
+#print("Model is:", vars(Model))
+#Model.get = get
 
 # Define models
-roles_users = db.Table('roles_users',
-        db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
-        db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
-
-class Role(db.Model, RoleMixin):
-    id = db.Column(db.Integer(), primary_key=True)
-    name = db.Column(db.String(80), unique=True)
-    description = db.Column(db.String(255))
-
-class User(db.Model, UserMixin):
-    id = db.Column(db.Integer(), primary_key=True)
-    email = db.Column(db.String(255), unique=True)
-    password = db.Column(db.String(255))
-    active = db.Column(db.Boolean())
-    confirmed_at = db.Column(db.DateTime())
-
-    last_login_at = db.Column(db.DateTime())
-    current_login_at = db.Column(db.DateTime())
-    last_login_ip = db.Column(db.String(39))
-    current_login_ip = db.Column(db.String(39))
-    login_count = db.Column(db.Integer())
-
-    roles = db.relationship('Role', secondary=roles_users,
-                            backref=db.backref('users', lazy='dynamic'))
+#roles_users = Table('roles_users',
+#        Column('user_id', Integer(), ForeignKey('user.the_id')),
+#        Column('role_id', Integer(), ForeignKey('role.the_id')))
+
+class Role(Base):
+    __tablename__ = "role"
+    id = Column(Unicode(36), primary_key=True, default=lambda: unicode(uuid.uuid4()))
+    name = Column(Unicode(80), unique=True, nullable=False)
+    description = Column(Unicode(255))
+
+class User(Base):
+    __tablename__ = "user"
+    id = Column(Unicode(36), primary_key=True, default=lambda: unicode(uuid.uuid4()))
+    email_address = Column(Unicode(50), unique=True, nullable=False)
+    
+    # Todo: Turn on strict mode for Mysql
+    password = Column(Text, nullable=False)
+    
+    full_name = Column(Unicode(50))
+    organization = Column(Unicode(50))
+    
+    active = Column(Boolean(), nullable=False, default=True)
 
+    registration_info = Column(Text)   # json detailing when they were registered, etc.
+    
+    confirmed = Column(Text) # json detailing when they confirmed, etc.
+
+    #last_login_at = Column(DateTime())
+    #current_login_at = Column(DateTime())
+    #last_login_ip = Column(Unicode(39))
+    #current_login_ip = Column(Unicode(39))
+    #login_count = Column(Integer())
+
+    #roles = relationship('Role', secondary=roles_users,
+    #                        backref=backref('users', lazy='dynamic'))
+
+class Login(Base):
+    __tablename__ = "login"
+    id = Column(Unicode(36), primary_key=True, default=lambda: unicode(uuid.uuid4()))
+    user = Column(Unicode(36), ForeignKey('user.id'))
+    timestamp = Column(DateTime(), default=lambda: datetime.datetime.utcnow())
+    ip_address = Column(Unicode(39))
+    successful = Column(Boolean(), nullable=False)  # False if wrong password was entered
+    session_id = Column(Text)  # Set only if successfully logged in, otherwise should be blank
+    
+    def __init__(self, user):
+        self.user = user.id
+        self.ip_address = request.remote_addr
+        
 # Setup Flask-Security
-user_datastore = SQLAlchemyUserDatastore(db, User, Role)
-security = Security(app, user_datastore)
+#user_datastore = SQLAlchemyUserDatastore(db, User, Role)
+
+#class ExtendedRegisterForm(RegisterForm):
+#    name = TextField('name')
+#    #print("name is:", name['_name'], vars(name))
+#    organization = TextField('organization')
+#
+#security = Security(app, user_datastore, register_form=ExtendedRegisterForm)
+
+
+#user_datastore.create_role(name="Genentech", description="Genentech Beta Project(testing)")
 
-db.metadata.create_all(db.engine)
 
-user_datastore.create_role(name="Genentech", description="Genentech Beta Project(testing)")
diff --git a/wqflask/wqflask/pbkdf2.py b/wqflask/wqflask/pbkdf2.py
new file mode 100644
index 00000000..f7f61a09
--- /dev/null
+++ b/wqflask/wqflask/pbkdf2.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+"""
+    pbkdf2
+    ~~~~~~
+
+    This module implements pbkdf2 for Python.  It also has some basic
+    tests that ensure that it works.  The implementation is straightforward
+    and uses stdlib only stuff and can be easily be copy/pasted into
+    your favourite application.
+
+    Use this as replacement for bcrypt that does not need a c implementation
+    of a modified blowfish crypto algo.
+
+    Example usage:
+
+    >>> pbkdf2_hex('what i want to hash', 'the random salt')
+    'fa7cc8a2b0a932f8e6ea42f9787e9d36e592e0c222ada6a9'
+
+    How to use this:
+
+    1.  Use a constant time string compare function to compare the stored hash
+        with the one you're generating::
+
+            def safe_str_cmp(a, b):
+                if len(a) != len(b):
+                    return False
+                rv = 0
+                for x, y in izip(a, b):
+                    rv |= ord(x) ^ ord(y)
+                return rv == 0
+
+    2.  Use `os.urandom` to generate a proper salt of at least 8 byte.
+        Use a unique salt per hashed password.
+
+    3.  Store ``algorithm$salt:costfactor$hash`` in the database so that
+        you can upgrade later easily to a different algorithm if you need
+        one.  For instance ``PBKDF2-256$thesalt:10000$deadbeef...``.
+
+
+    :copyright: (c) Copyright 2011 by Armin Ronacher.
+    :license: BSD, see LICENSE for more details.
+"""
+import hmac
+import hashlib
+from struct import Struct
+from operator import xor
+from itertools import izip, starmap
+
+
+_pack_int = Struct('>I').pack
+
+
+def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc=None):
+    """Like :func:`pbkdf2_bin` but returns a hex encoded string."""
+    return pbkdf2_bin(data, salt, iterations, keylen, hashfunc).encode('hex')
+
+
+def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
+    """Returns a binary digest for the PBKDF2 hash algorithm of `data`
+    with the given `salt`.  It iterates `iterations` time and produces a
+    key of `keylen` bytes.  By default SHA-1 is used as hash function,
+    a different hashlib `hashfunc` can be provided.
+    """
+    hashfunc = hashfunc or hashlib.sha1
+    mac = hmac.new(data, None, hashfunc)
+    def _pseudorandom(x, mac=mac):
+        h = mac.copy()
+        h.update(x)
+        return map(ord, h.digest())
+    buf = []
+    for block in xrange(1, -(-keylen // mac.digest_size) + 1):
+        rv = u = _pseudorandom(salt + _pack_int(block))
+        for i in xrange(iterations - 1):
+            u = _pseudorandom(''.join(map(chr, u)))
+            rv = list(starmap(xor, izip(rv, u)))
+        buf.extend(rv)
+    return ''.join(map(chr, buf))[:keylen]
+
+
+def safe_str_cmp(a, b):
+    if len(a) != len(b):
+        return False
+    rv = 0
+    for x, y in izip(a, b):
+        rv |= ord(x) ^ ord(y)
+    return rv == 0
+
+
+
+def test():
+    failed = []
+    def check(data, salt, iterations, keylen, expected):
+        rv = pbkdf2_hex(data, salt, iterations, keylen)
+        if rv != expected:
+            print 'Test failed:'
+            print '  Expected:   %s' % expected
+            print '  Got:        %s' % rv
+            print '  Parameters:'
+            print '    data=%s' % data
+            print '    salt=%s' % salt
+            print '    iterations=%d' % iterations
+            print
+            failed.append(1)
+
+    # From RFC 6070
+    check('password', 'salt', 1, 20,
+          '0c60c80f961f0e71f3a9b524af6012062fe037a6')
+    check('password', 'salt', 2, 20,
+          'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957')
+    check('password', 'salt', 4096, 20,
+          '4b007901b765489abead49d926f721d065a429c1')
+    check('passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt',
+          4096, 25, '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038')
+    check('pass\x00word', 'sa\x00lt', 4096, 16,
+          '56fa6aa75548099dcc37d7f03425e0c3')
+    # This one is from the RFC but it just takes for ages
+    ##check('password', 'salt', 16777216, 20,
+    ##      'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984')
+
+    # From Crypt-PBKDF2
+    check('password', 'ATHENA.MIT.EDUraeburn', 1, 16,
+          'cdedb5281bb2f801565a1122b2563515')
+    check('password', 'ATHENA.MIT.EDUraeburn', 1, 32,
+          'cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837')
+    check('password', 'ATHENA.MIT.EDUraeburn', 2, 16,
+          '01dbee7f4a9e243e988b62c73cda935d')
+    check('password', 'ATHENA.MIT.EDUraeburn', 2, 32,
+          '01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86')
+    check('password', 'ATHENA.MIT.EDUraeburn', 1200, 32,
+          '5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13')
+    check('X' * 64, 'pass phrase equals block size', 1200, 32,
+          '139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1')
+    check('X' * 65, 'pass phrase exceeds block size', 1200, 32,
+          '9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a')
+
+    raise SystemExit(bool(failed))
+
+
+if __name__ == '__main__':
+    test()
diff --git a/wqflask/wqflask/send_mail.py b/wqflask/wqflask/send_mail.py
new file mode 100644
index 00000000..be51ad0d
--- /dev/null
+++ b/wqflask/wqflask/send_mail.py
@@ -0,0 +1,45 @@
+from __future__ import absolute_import, division, print_function
+
+import datetime
+
+import simplejson as json
+
+from redis import StrictRedis
+Redis = StrictRedis()
+
+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)
+        
+
+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
diff --git a/wqflask/wqflask/static/new/css/parsley.css b/wqflask/wqflask/static/new/css/parsley.css
new file mode 100644
index 00000000..7d244579
--- /dev/null
+++ b/wqflask/wqflask/static/new/css/parsley.css
@@ -0,0 +1,20 @@
+/* Adapted from parsleyjs.org/documentation.html#parsleyclasses */
+
+input.parsley-success, textarea.parsley-success {
+  color: #468847 !important;
+  background-color: #DFF0D8 !important;
+  border: 1px solid #D6E9C6 !important;
+}
+input.parsley-error, textarea.parsley-error {
+  color: #B94A48 !important;
+  background-color: #F2DEDE !important;
+  border: 1px solid #EED3D7 !important;
+}
+ul.parsley-error-list {
+    font-size: 11px;
+    margin: 2px;
+    list-style-type:none;
+}
+ul.parsley-error-list li {
+    line-height: 11px;
+}
\ No newline at end of file
diff --git a/wqflask/wqflask/static/new/javascript/password_strength.coffee b/wqflask/wqflask/static/new/javascript/password_strength.coffee
new file mode 100644
index 00000000..0bee5836
--- /dev/null
+++ b/wqflask/wqflask/static/new/javascript/password_strength.coffee
@@ -0,0 +1,33 @@
+$ ->
+
+
+    $("#password").keyup ->
+        passtext = $(this).val()
+        result = zxcvbn(passtext)
+        if passtext.length < 6
+            $("#password_strength").html('')
+            $("#password_alert").fadeOut()
+        else
+            word = word_score(result.score)
+            crack_time = result.crack_time_display
+            if crack_time == "instant"
+                crack_time = "a second"
+            display = "This is #{word} password. It can be cracked in #{crack_time}."
+            $("#password_strength").html(display)
+            $("#password_alert").fadeIn()
+
+    
+
+    word_score = (num_score) ->
+        num_score = parseInt(num_score)
+        console.log("num_score is:", num_score)
+        mapping =
+            0:     "a <strong>terrible</strong>"
+            1:     "a <strong>bad</strong>"
+            2:     "a <strong>mediocre</strong>"
+            3:     "a <strong>good</strong>"
+            4:     "an <strong>excellent</strong>"
+        console.log("mapping is:", mapping)
+        result = mapping[num_score]
+        console.log("result is:", result)
+        return result
\ No newline at end of file
diff --git a/wqflask/wqflask/static/new/javascript/password_strength.js b/wqflask/wqflask/static/new/javascript/password_strength.js
new file mode 100644
index 00000000..166e1125
--- /dev/null
+++ b/wqflask/wqflask/static/new/javascript/password_strength.js
@@ -0,0 +1,42 @@
+// Generated by CoffeeScript 1.6.1
+(function() {
+
+  $(function() {
+    var word_score;
+    $("#password").keyup(function() {
+      var crack_time, display, passtext, result, word;
+      passtext = $(this).val();
+      result = zxcvbn(passtext);
+      if (passtext.length < 6) {
+        $("#password_strength").html('');
+        return $("#password_alert").fadeOut();
+      } else {
+        word = word_score(result.score);
+        crack_time = result.crack_time_display;
+        if (crack_time === "instant") {
+          crack_time = "a second";
+        }
+        display = "This is " + word + " password. It can be cracked in " + crack_time + ".";
+        $("#password_strength").html(display);
+        return $("#password_alert").fadeIn();
+      }
+    });
+    return word_score = function(num_score) {
+      var mapping, result;
+      num_score = parseInt(num_score);
+      console.log("num_score is:", num_score);
+      mapping = {
+        0: "a <strong>terrible</strong>",
+        1: "a <strong>bad</strong>",
+        2: "a <strong>mediocre</strong>",
+        3: "a <strong>good</strong>",
+        4: "an <strong>excellent</strong>"
+      };
+      console.log("mapping is:", mapping);
+      result = mapping[num_score];
+      console.log("result is:", result);
+      return result;
+    };
+  });
+
+}).call(this);
diff --git a/wqflask/wqflask/static/new/js_external/parsley.min.js b/wqflask/wqflask/static/new/js_external/parsley.min.js
new file mode 100644
index 00000000..ab85c683
--- /dev/null
+++ b/wqflask/wqflask/static/new/js_external/parsley.min.js
@@ -0,0 +1,35 @@
+/* Parsley dist/parsley.min.js build version 1.1.18 http://parsleyjs.org */
+!function(d){var h=function(a){this.messages={defaultMessage:"This value seems to be invalid.",type:{email:"This value should be a valid email.",url:"This value should be a valid url.",urlstrict:"This value should be a valid url.",number:"This value should be a valid number.",digits:"This value should be digits.",dateIso:"This value should be a valid date (YYYY-MM-DD).",alphanum:"This value should be alphanumeric.",phone:"This value should be a valid phone number."},notnull:"This value should not be null.",
+notblank:"This value should not be blank.",required:"This value is required.",regexp:"This value seems to be invalid.",min:"This value should be greater than or equal to %s.",max:"This value should be lower than or equal to %s.",range:"This value should be between %s and %s.",minlength:"This value is too short. It should have %s characters or more.",maxlength:"This value is too long. It should have %s characters or less.",rangelength:"This value length is invalid. It should be between %s and %s characters long.",
+mincheck:"You must select at least %s choices.",maxcheck:"You must select %s choices or less.",rangecheck:"You must select between %s and %s choices.",equalto:"This value should be the same."};this.init(a)};h.prototype={constructor:h,validators:{notnull:function(a){return 0<a.length},notblank:function(a){return"string"===typeof a&&""!==a.replace(/^\s+/g,"").replace(/\s+$/g,"")},required:function(a){if("object"===typeof a){for(var b in a)if(this.required(a[b]))return!0;return!1}return this.notnull(a)&&
+this.notblank(a)},type:function(a,b){var c;switch(b){case "number":c=/^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;break;case "digits":c=/^\d+$/;break;case "alphanum":c=/^\w+$/;break;case "email":c=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))){2,6}$/i;
+break;case "url":a=/(https?|s?ftp|git)/i.test(a)?a:"http://"+a;case "urlstrict":c=/^(https?|s?ftp|git):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
+break;case "dateIso":c=/^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$/;break;case "phone":c=/^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/;break;default:return!1}return""!==a?c.test(a):!1},regexp:function(a,b,c){return RegExp(b,c.options.regexpFlag||"").test(a)},minlength:function(a,b){return a.length>=b},maxlength:function(a,b){return a.length<=b},rangelength:function(a,b){return this.minlength(a,b[0])&&this.maxlength(a,b[1])},
+min:function(a,b){return Number(a)>=b},max:function(a,b){return Number(a)<=b},range:function(a,b){return a>=b[0]&&a<=b[1]},equalto:function(a,b,c){c.options.validateIfUnchanged=!0;return a===d(b).val()},remote:function(a,b,c){var f={},g={};f[c.$element.attr("name")]=a;"undefined"!==typeof c.options.remoteDatatype&&(g={dataType:c.options.remoteDatatype});var m=function(a,b){"undefined"!==typeof b&&("undefined"!==typeof c.Validator.messages.remote&&b!==c.Validator.messages.remote)&&d(c.ulError+" .remote").remove();
+c.updtConstraint({name:"remote",valid:a},b);c.manageValidationResult()},n=function(a){if("object"===typeof a)return a;try{a=d.parseJSON(a)}catch(b){}return a},e=function(a){return"object"===typeof a&&null!==a?"undefined"!==typeof a.error?a.error:"undefined"!==typeof a.message?a.message:null:null};d.ajax(d.extend({},{url:b,data:f,type:c.options.remoteMethod||"GET",success:function(a){a=n(a);m(1===a||!0===a||"object"===typeof a&&null!==a&&"undefined"!==typeof a.success,e(a))},error:function(a){a=n(a);
+m(!1,e(a))}},g));return null},mincheck:function(a,b){return this.minlength(a,b)},maxcheck:function(a,b){return this.maxlength(a,b)},rangecheck:function(a,b){return this.rangelength(a,b)}},init:function(a){var b=a.validators;a=a.messages;for(var c in b)this.addValidator(c,b[c]);for(c in a)this.addMessage(c,a[c])},formatMesssage:function(a,b){if("object"===typeof b){for(var c in b)a=this.formatMesssage(a,b[c]);return a}return"string"===typeof a?a.replace(/%s/i,b):""},addValidator:function(a,b){this.validators[a]=
+b},addMessage:function(a,b,c){if("undefined"!==typeof c&&!0===c)this.messages.type[a]=b;else if("type"===a)for(var d in b)this.messages.type[d]=b[d];else this.messages[a]=b}};var j=function(a,b,c){this.options=b;this.Validator=new h(b);if("ParsleyFieldMultiple"===c)return this;this.init(a,c||"ParsleyField")};j.prototype={constructor:j,init:function(a,b){this.type=b;this.valid=!0;this.element=a;this.validatedOnce=!1;this.$element=d(a);this.val=this.$element.val();this.isRequired=!1;this.constraints=
+{};"undefined"===typeof this.isRadioOrCheckbox&&(this.isRadioOrCheckbox=!1,this.hash=this.generateHash(),this.errorClassHandler=this.options.errors.classHandler(a,this.isRadioOrCheckbox)||this.$element);this.ulErrorManagement();this.bindHtml5Constraints();this.addConstraints();this.hasConstraints()&&this.bindValidationEvents()},setParent:function(a){this.$parent=d(a)},getParent:function(){return this.$parent},bindHtml5Constraints:function(){if(this.$element.hasClass("required")||this.$element.prop("required"))this.options.required=
+!0;"undefined"!==typeof this.$element.attr("type")&&RegExp(this.$element.attr("type"),"i").test("email url number range")&&(this.options.type=this.$element.attr("type"),RegExp(this.options.type,"i").test("number range")&&(this.options.type="number","undefined"!==typeof this.$element.attr("min")&&this.$element.attr("min").length&&(this.options.min=this.$element.attr("min")),"undefined"!==typeof this.$element.attr("max")&&this.$element.attr("max").length&&(this.options.max=this.$element.attr("max"))));
+"string"===typeof this.$element.attr("pattern")&&this.$element.attr("pattern").length&&(this.options.regexp=this.$element.attr("pattern"))},addConstraints:function(){for(var a in this.options){var b={};b[a]=this.options[a];this.addConstraint(b,!0)}},addConstraint:function(a,b){for(var c in a)c=c.toLowerCase(),"function"===typeof this.Validator.validators[c]&&(this.constraints[c]={name:c,requirements:a[c],valid:null},"required"===c&&(this.isRequired=!0),this.addCustomConstraintMessage(c));"undefined"===
+typeof b&&this.bindValidationEvents()},updateConstraint:function(a,b){for(var c in a)this.updtConstraint({name:c,requirements:a[c],valid:null},b)},updtConstraint:function(a,b){this.constraints[a.name]=d.extend(!0,this.constraints[a.name],a);"string"===typeof b&&(this.Validator.messages[a.name]=b);this.bindValidationEvents()},removeConstraint:function(a){a=a.toLowerCase();delete this.constraints[a];"required"===a&&(this.isRequired=!1);this.hasConstraints()?this.bindValidationEvents():"ParsleyForm"===
+typeof this.getParent()?this.getParent().removeItem(this.$element):this.destroy()},addCustomConstraintMessage:function(a){var b=a+("type"===a&&"undefined"!==typeof this.options[a]?this.options[a].charAt(0).toUpperCase()+this.options[a].substr(1):"")+"Message";"undefined"!==typeof this.options[b]&&this.Validator.addMessage("type"===a?this.options[a]:a,this.options[b],"type"===a)},bindValidationEvents:function(){this.valid=null;this.$element.addClass("parsley-validated");this.$element.off("."+this.type);
+this.options.remote&&!/change/i.test(this.options.trigger)&&(this.options.trigger=!this.options.trigger?"change":" change");var a=(!this.options.trigger?"":this.options.trigger)+(/key/i.test(this.options.trigger)?"":" keyup");this.$element.is("select")&&(a+=/change/i.test(a)?"":" change");a=a.replace(/^\s+/g,"").replace(/\s+$/g,"");this.$element.on((a+" ").split(" ").join("."+this.type+" "),!1,d.proxy(this.eventValidation,this))},generateHash:function(){return"parsley-"+(Math.random()+"").substring(2)},
+getHash:function(){return this.hash},getVal:function(){return this.$element.data("value")||this.$element.val()},eventValidation:function(a){var b=this.getVal();if("keyup"===a.type&&!/keyup/i.test(this.options.trigger)&&!this.validatedOnce||"change"===a.type&&!/change/i.test(this.options.trigger)&&!this.validatedOnce||!this.isRadioOrCheckbox&&this.getLength(b)<this.options.validationMinlength&&!this.validatedOnce)return!0;this.validate()},getLength:function(a){return!a||!a.hasOwnProperty("length")?
+0:a.length},isValid:function(){return this.validate(!1)},hasConstraints:function(){for(var a in this.constraints)return!0;return!1},validate:function(a){var b=this.getVal(),c=null;if(!this.hasConstraints())return null;if(this.options.listeners.onFieldValidate(this.element,this)||""===b&&!this.isRequired)return this.reset(),null;if(!this.needsValidation(b))return this.valid;c=this.applyValidators();("undefined"!==typeof a?a:this.options.showErrors)&&this.manageValidationResult();return c},needsValidation:function(a){if(!this.options.validateIfUnchanged&&
+null!==this.valid&&this.val===a&&this.validatedOnce)return!1;this.val=a;return this.validatedOnce=!0},applyValidators:function(){var a=null,b;for(b in this.constraints){var c=this.Validator.validators[this.constraints[b].name](this.val,this.constraints[b].requirements,this);!1===c?(a=!1,this.constraints[b].valid=a,this.options.listeners.onFieldError(this.element,this.constraints,this)):!0===c&&(this.constraints[b].valid=!0,a=!1!==a,!1===this.options.listeners.onFieldSuccess(this.element,this.constraints,
+this)&&(a=!1))}return a},manageValidationResult:function(){var a=null,b;for(b in this.constraints)!1===this.constraints[b].valid?(this.manageError(this.constraints[b]),a=!1):!0===this.constraints[b].valid&&(this.removeError(this.constraints[b].name),a=!1!==a);this.valid=a;if(!0===this.valid)return this.removeErrors(),this.errorClassHandler.removeClass(this.options.errorClass).addClass(this.options.successClass),!0;if(!1===this.valid)return this.errorClassHandler.removeClass(this.options.successClass).addClass(this.options.errorClass),
+!1;this.ulError&&0===d(this.ulError).children().length&&this.removeErrors();return a},ulErrorManagement:function(){this.ulError="#"+this.hash;this.ulTemplate=d(this.options.errors.errorsWrapper).attr("id",this.hash).addClass("parsley-error-list")},removeError:function(a){a=this.ulError+" ."+a;var b=this;this.options.animate?d(a).fadeOut(this.options.animateDuration,function(){d(this).remove();b.ulError&&0===d(b.ulError).children().length&&b.removeErrors()}):d(a).remove()},addError:function(a){for(var b in a){var c=
+d(this.options.errors.errorElem).addClass(b);d(this.ulError).append(this.options.animate?d(c).html(a[b]).hide().fadeIn(this.options.animateDuration):d(c).html(a[b]))}},removeErrors:function(){this.options.animate?d(this.ulError).fadeOut(this.options.animateDuration,function(){d(this).remove()}):d(this.ulError).remove()},reset:function(){this.valid=null;this.removeErrors();this.validatedOnce=!1;this.errorClassHandler.removeClass(this.options.successClass).removeClass(this.options.errorClass);for(var a in this.constraints)this.constraints[a].valid=
+null;return this},manageError:function(a){d(this.ulError).length||this.manageErrorContainer();if(!("required"===a.name&&null!==this.getVal()&&0<this.getVal().length)&&(!this.isRequired||!("required"!==a.name&&(null===this.getVal()||0===this.getVal().length)))){var b=a.name,c=!1!==this.options.errorMessage?"custom-error-message":b,f={};a=!1!==this.options.errorMessage?this.options.errorMessage:"type"===a.name?this.Validator.messages[b][a.requirements]:"undefined"===typeof this.Validator.messages[b]?
+this.Validator.messages.defaultMessage:this.Validator.formatMesssage(this.Validator.messages[b],a.requirements);d(this.ulError+" ."+c).length||(f[c]=a,this.addError(f))}},manageErrorContainer:function(){var a=this.options.errorContainer||this.options.errors.container(this.element,this.isRadioOrCheckbox),b=this.options.animate?this.ulTemplate.show():this.ulTemplate;"undefined"!==typeof a?d(a).append(b):!this.isRadioOrCheckbox?this.$element.after(b):this.$element.parent().after(b)},addListener:function(a){for(var b in a)this.options.listeners[b]=
+a[b]},destroy:function(){this.$element.removeClass("parsley-validated");this.reset().$element.off("."+this.type).removeData(this.type)}};var l=function(a,b,c){this.initMultiple(a,b);this.inherit(a,b);this.Validator=new h(b);this.init(a,c||"ParsleyFieldMultiple")};l.prototype={constructor:l,initMultiple:function(a,b){this.element=a;this.$element=d(a);this.group=b.group||!1;this.hash=this.getName();this.siblings=this.group?'[data-group="'+this.group+'"]':'input[name="'+this.$element.attr("name")+'"]';
+this.isRadioOrCheckbox=!0;this.isRadio=this.$element.is("input[type=radio]");this.isCheckbox=this.$element.is("input[type=checkbox]");this.errorClassHandler=b.errors.classHandler(a,this.isRadioOrCheckbox)||this.$element.parent()},inherit:function(a,b){var c=new j(a,b,"ParsleyFieldMultiple"),d;for(d in c)"undefined"===typeof this[d]&&(this[d]=c[d])},getName:function(){if(this.group)return"parsley-"+this.group;if("undefined"===typeof this.$element.attr("name"))throw"A radio / checkbox input must have a data-group attribute or a name to be Parsley validated !";
+return"parsley-"+this.$element.attr("name").replace(/(:|\.|\[|\])/g,"")},getVal:function(){if(this.isRadio)return d(this.siblings+":checked").val()||"";if(this.isCheckbox){var a=[];d(this.siblings+":checked").each(function(){a.push(d(this).val())});return a}},bindValidationEvents:function(){this.valid=null;this.$element.addClass("parsley-validated");this.$element.off("."+this.type);var a=this,b=(!this.options.trigger?"":this.options.trigger)+(/change/i.test(this.options.trigger)?"":" change"),b=b.replace(/^\s+/g,
+"").replace(/\s+$/g,"");d(this.siblings).each(function(){d(this).on(b.split(" ").join("."+a.type+" "),!1,d.proxy(a.eventValidation,a))})}};var k=function(a,b,c){this.init(a,b,c||"parsleyForm")};k.prototype={constructor:k,init:function(a,b,c){this.type=c;this.items=[];this.$element=d(a);this.options=b;var f=this;this.$element.find(b.inputs).each(function(){f.addItem(this)});this.$element.on("submit."+this.type,!1,d.proxy(this.validate,this))},addListener:function(a){for(var b in a)if(/Field/.test(b))for(var c=
+0;c<this.items.length;c++)this.items[c].addListener(a);else this.options.listeners[b]=a[b]},addItem:function(a){if(d(a).is(this.options.excluded))return!1;a=d(a).parsley(this.options);a.setParent(this);this.items.push(a)},removeItem:function(a){a=d(a).parsley();for(var b=0;b<this.items.length;b++)if(this.items[b].hash===a.hash)return this.items[b].destroy(),this.items.splice(b,1),!0;return!1},validate:function(a){var b=!0;this.focusedField=!1;for(var c=0;c<this.items.length;c++)if("undefined"!==typeof this.items[c]&&
+!1===this.items[c].validate()&&(b=!1,!this.focusedField&&"first"===this.options.focus||"last"===this.options.focus))this.focusedField=this.items[c].$element;this.focusedField&&!b&&this.focusedField.focus();a=this.options.listeners.onFormSubmit(b,a,this);return"undefined"!==typeof a?a:b},isValid:function(){for(var a=0;a<this.items.length;a++)if(!1===this.items[a].isValid())return!1;return!0},removeErrors:function(){for(var a=0;a<this.items.length;a++)this.items[a].parsley("reset")},destroy:function(){for(var a=
+0;a<this.items.length;a++)this.items[a].destroy();this.$element.off("."+this.type).removeData(this.type)},reset:function(){for(var a=0;a<this.items.length;a++)this.items[a].reset()}};d.fn.parsley=function(a,b){function c(c,g){var e=d(c).data(g);if(!e){switch(g){case "parsleyForm":e=new k(c,f,"parsleyForm");break;case "parsleyField":e=new j(c,f,"parsleyField");break;case "parsleyFieldMultiple":e=new l(c,f,"parsleyFieldMultiple");break;default:return}d(c).data(g,e)}return"string"===typeof a&&"function"===
+typeof e[a]?(e=e[a](b),"undefined"!==typeof e?e:d(c)):e}var f=d.extend(!0,{},d.fn.parsley.defaults,"undefined"!==typeof window.ParsleyConfig?window.ParsleyConfig:{},a,this.data()),g=null;d(this).is("form")||!0===d(this).data("bind")?g=c(d(this),"parsleyForm"):d(this).is(f.inputs)&&!d(this).is(f.excluded)&&(g=c(d(this),!d(this).is("input[type=radio], input[type=checkbox]")?"parsleyField":"parsleyFieldMultiple"));return"function"===typeof b?b():g};d.fn.parsley.Constructor=k;d.fn.parsley.defaults={inputs:"input, textarea, select",
+excluded:"input[type=hidden], input[type=file], :disabled",trigger:!1,animate:!0,animateDuration:300,focus:"first",validationMinlength:3,successClass:"parsley-success",errorClass:"parsley-error",errorMessage:!1,validators:{},showErrors:!0,messages:{},validateIfUnchanged:!1,errors:{classHandler:function(){},container:function(){},errorsWrapper:"<ul></ul>",errorElem:"<li></li>"},listeners:{onFieldValidate:function(){return!1},onFormSubmit:function(){},onFieldError:function(){},onFieldSuccess:function(){}}};
+d(window).on("load",function(){d('[data-validate="parsley"]').each(function(){d(this).parsley()})})}(window.jQuery||window.Zepto);
diff --git a/wqflask/wqflask/static/new/js_external/zxcvbn/zxcvbn-async.js b/wqflask/wqflask/static/new/js_external/zxcvbn/zxcvbn-async.js
new file mode 100644
index 00000000..404944d3
--- /dev/null
+++ b/wqflask/wqflask/static/new/js_external/zxcvbn/zxcvbn-async.js
@@ -0,0 +1 @@
+(function(){var a;a=function(){var a,b;b=document.createElement("script");b.src="//dl.dropbox.com/u/209/zxcvbn/zxcvbn.js";b.type="text/javascript";b.async=!0;a=document.getElementsByTagName("script")[0];return a.parentNode.insertBefore(b,a)};null!=window.attachEvent?window.attachEvent("onload",a):window.addEventListener("load",a,!1)}).call(this);
diff --git a/wqflask/wqflask/templates/admin/ind_user_manager.html b/wqflask/wqflask/templates/admin/ind_user_manager.html
index 9776af0b..2fe4a002 100644
--- a/wqflask/wqflask/templates/admin/ind_user_manager.html
+++ b/wqflask/wqflask/templates/admin/ind_user_manager.html
@@ -30,6 +30,16 @@
                     <th>Value</th>
                 </tr>
             </thead>-->
+            
+            <tr>
+                <td>Name</td>
+                <td>{{ user.name }}</td>
+            </tr>
+            
+            <tr>
+                <td>Organization</td>
+                <td>{{ user.organization }}</td>
+            </tr>
 
 
             <tr>
diff --git a/wqflask/wqflask/templates/admin/user_manager.html b/wqflask/wqflask/templates/admin/user_manager.html
index 14cd12e0..1308ff4b 100644
--- a/wqflask/wqflask/templates/admin/user_manager.html
+++ b/wqflask/wqflask/templates/admin/user_manager.html
@@ -16,20 +16,20 @@
         <table class="table table-hover">
             <thead>
                 <tr>
-                    <th>ID</th>
                     <th>Email</th>
-                    <th>Confirmed at</th>
+                    <th>Organization</th>
                     <th>Active</th>
+                    <th>Confirmed</th>
                 </tr>
             </thead>
         {% for user in users %}
             <tr>
-                <td title="{{ user.__dict__ }}">
-                    <a href="{{ url_for('manage_user', user_id=user.id) }}">{{ user.id }}</a>
+                <td title="{{ user.id }}">
+                    <a href="{{ url_for('manage_user', user_id=user.id) }}">{{ user.email_address }}</a>
                 </td>
-                <td>{{ user.email }}</td>
-                <td>{{ user.confirmed_at }}</td>
-                <td>{{ user.active }}</td>
+                <td>{{ user.organization }}</td>
+                <td>{{ 'Yes' if user.active else 'No' }}</td>
+                <td title="{{ user.confirmed }}">{{ 'True' if user.confirmed else 'False' }}</td>
             </tr>
         {% endfor %}
         </table>
diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html
index cbed5859..8efba6a7 100644
--- a/wqflask/wqflask/templates/base.html
+++ b/wqflask/wqflask/templates/base.html
@@ -19,16 +19,46 @@
 
     <link rel="stylesheet" type="text/css" href="/static/packages/colorbox/example4/colorbox.css"  />
     <link rel="stylesheet" type="text/css" href="/static/new/css/main.css"  />
+    <link rel="stylesheet" type="text/css" href="/static/new/css/parsley.css"  />
 
     {% block css %}
     {% endblock %}
 
 </head>
 
+{% macro header(main, second) %}
+    <header class="jumbotron subhead" id="overview">
+        <div class="container">
+            <h1>Login</h1>
+            <p class="lead">
+               Gain access to GeneNetwork.
+            </p>               
+        </div>
+    </header>
+    
+    {{ flash_me() }}
+{% endmacro %}
+
+
+{% macro flash_me() -%}
+    {% with messages = get_flashed_messages(with_categories=true) %}
+        {% if messages %}
+        <br />
+        <div class="container">
+            {% for category, message in messages %}
+                <div class="alert {{ category }}">{{ message }}</div>
+            {% endfor %}
+        </div>
+        {% endif %}
+    {% endwith %}
+{% endmacro %}
+
 <body data-spy="scroll" data-target=".bs-docs-sidebar">
     <!-- Navbar
     ================================================== -->
+      
     <div class="navbar navbar-inverse navbar-fixed-top">
+        
         <div class="navbar-inner">
             <div class="container">
                 <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
@@ -69,10 +99,10 @@
                             <a href="/links">Links</a>
                         </li>
                         <li class="">
-                            {% if g.identity.name=="anon" %}
-                            <a id="login_out" class="modalize" href="/login">Sign in</a>
+                            {% if g.user_session.logged_in %}
+                            <a id="login_out"  href="/n/logout">Sign out</a>
                             {% else %}
-                            <a id="login_out" href="/logout">Sign out</a>
+                            <a id="login_in" href="/n/login">Sign in</a>
                             {% endif %}
                         </li>
                     </ul>
@@ -180,12 +210,13 @@
     <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/jquery-ui.min.js" type="text/javascript"></script>
 
     <script language="javascript" type="text/javascript" src="/static/packages/colorbox/jquery.colorbox.js"></script>
-    <script type="text/javascript" src="/static/new/javascript/login.js"></script>
+    <!--<script type="text/javascript" src="/static/new/javascript/login.js"></script>-->
+    
+    <script type="text/javascript" src="/static/new/js_external/parsley.min.js"></script>
+    
     {% block js %}
     {% endblock %}
 
 
-
-
 </body>
 </html>
diff --git a/wqflask/wqflask/templates/email/verification.txt b/wqflask/wqflask/templates/email/verification.txt
new file mode 100644
index 00000000..29229c68
--- /dev/null
+++ b/wqflask/wqflask/templates/email/verification.txt
@@ -0,0 +1,8 @@
+Thank you for signing up for GeneNetwork.
+
+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 )}}
+
diff --git a/wqflask/wqflask/templates/index_page.html b/wqflask/wqflask/templates/index_page.html
index 61e34eca..98682e57 100644
--- a/wqflask/wqflask/templates/index_page.html
+++ b/wqflask/wqflask/templates/index_page.html
@@ -8,11 +8,14 @@
     <header class="jumbotron subhead" id="overview">
         <div class="container">
             <h1>GeneNetwork</h1>
-            <p class="lead">Open source bioinformatics for systems genetics</p>
+            <p class="lead">Open source bioinformatics for systems genetics</p>   
         </div>
     </header>
 
     <div class="container">
+        
+        {{ flash_me() }}
+        
         <div class="row">
             <div class="span3 bs-docs-sidebar">
               <ul class="nav nav-list bs-docs-sidenav">
@@ -24,7 +27,7 @@
                 <li><a href="#websites"><i class="icon-chevron-right"></i> Affiliates and mirrors</a></li>
               </ul>
             </div>
-
+            
             <div class="span9">
                 <section id="quick-search">
                     <div class="page-header">
diff --git a/wqflask/wqflask/templates/new_security/_scripts.html b/wqflask/wqflask/templates/new_security/_scripts.html
new file mode 100644
index 00000000..5fefe305
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/_scripts.html
@@ -0,0 +1 @@
+<!--<script type="text/javascript" src="/static/new/javascript/login.js"></script>-->
diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html
new file mode 100644
index 00000000..f232ccbc
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/login_user.html
@@ -0,0 +1,83 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+
+    {{ header("Login", "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/>
+                        </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 %}  
+    <!--<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/register_user.html b/wqflask/wqflask/templates/new_security/register_user.html
new file mode 100644
index 00000000..2a02e7ca
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/register_user.html
@@ -0,0 +1,113 @@
+{% 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>
+
+    <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">  
+                    <strong>Please note:</strong>
+                    <ul>
+                        {% for error in errors %}
+                            <li>{{error}}</li>
+                        {% endfor %}
+                    </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">
+                            <input id="email_address" name="email_address" class="focused" type="text" value="{{values.email_address}}"
+                                   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">
+                            <input id="full_name" name="full_name" type="text" value="{{values.full_name}}"
+                                   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 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="Create account">
+                        </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/registered.html b/wqflask/wqflask/templates/new_security/registered.html
new file mode 100644
index 00000000..391a6044
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/registered.html
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+    <header class="jumbotron subhead" id="overview">
+        <div class="container">
+            <h1>Thank you</h1>
+            <p class="lead">
+                Thanks for verifying.
+            </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>"GeneNetwork email verification"</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 %}  
+    <!--<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/thank_you.html b/wqflask/wqflask/templates/new_security/thank_you.html
new file mode 100644
index 00000000..97cb7807
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/thank_you.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+    <header class="jumbotron subhead" id="overview">
+        <div class="container">
+            <h1>Thank you</h1>
+            <p class="lead">
+                Thanks for verifying.
+            </p>
+        </div>
+    </header>
+
+    <div class="container">
+        <div class="page-header">
+            <h3>You are done registering</h3>
+        </div>
+    
+        <p>Enjoy using the site.</p>
+        
+        <p>Go to the <a href="{{ url_for("index_page") }}">homepage</a></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/verified.html b/wqflask/wqflask/templates/new_security/verified.html
new file mode 100644
index 00000000..97cb7807
--- /dev/null
+++ b/wqflask/wqflask/templates/new_security/verified.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+{% block title %}Register{% endblock %}
+{% block content %}
+    <header class="jumbotron subhead" id="overview">
+        <div class="container">
+            <h1>Thank you</h1>
+            <p class="lead">
+                Thanks for verifying.
+            </p>
+        </div>
+    </header>
+
+    <div class="container">
+        <div class="page-header">
+            <h3>You are done registering</h3>
+        </div>
+    
+        <p>Enjoy using the site.</p>
+        
+        <p>Go to the <a href="{{ url_for("index_page") }}">homepage</a></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/security/register_user.html b/wqflask/wqflask/templates/security/register_user.html
index 3cd021b0..8e6908ff 100644
--- a/wqflask/wqflask/templates/security/register_user.html
+++ b/wqflask/wqflask/templates/security/register_user.html
@@ -28,8 +28,22 @@
                 </div>
                 {{ render_only_errors(register_user_form.email) }}
             </div>
+            
+            <div class="control-group">
+                <label class="control-label" for="email">Name</label>
+                <div class="controls">
+                    <input id="name" name="name" type="text" value="">
+                </div>
+            </div>
 
             <div class="control-group">
+                <label class="control-label" for="email">Organization</label>
+                <div class="controls">
+                    <input id="organization" name="organization" 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="">
@@ -49,7 +63,7 @@
 
             <div class="control-group">
                 <div class="controls"">
-                    <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Sign in">
+                    <input class="btn btn-primary" id="submit" name="submit" type="submit" value="Create account">
                 </div>
             </div>
 
diff --git a/wqflask/wqflask/tracer.py b/wqflask/wqflask/tracer.py
new file mode 100644
index 00000000..43a0f15e
--- /dev/null
+++ b/wqflask/wqflask/tracer.py
@@ -0,0 +1,41 @@
+from __future__ import absolute_import, division, print_function
+
+print("At top of tracer")
+
+import sys
+
+####################################################################################
+
+# Originally based on http://stackoverflow.com/a/8315566
+def tracefunc(frame, event, arg, indent=[0]):
+    
+    func = dict(funcname = frame.f_code.co_name,
+                     filename = frame.f_code.co_filename,
+                     lineno = frame.f_lineno)
+    
+    #These are too common to bother printing...
+    too_common = (
+        '/home/sam/ve27/local/lib/python2.7/site-packages/werkzeug/',
+        '/home/sam/ve27/local/lib/python2.7/site-packages/jinja2/',
+    )
+    
+    
+    if func['filename'].startswith(too_common):
+        return tracefunc
+    
+    info = "{funcname} [{filename}: {lineno}]".format(**func)
+
+    if event == "call":
+        indent[0] += 2
+        #print("-" * indent[0] + "> call function", frame.f_code.co_name)
+        print("-" * indent[0] + "> call function:", info)
+    elif event == "return":
+        print("<" + "-" * indent[0], "exit function:", info)
+        indent[0] -= 2
+    return tracefunc
+
+def turn_on():
+    sys.settrace(tracefunc)
+    print("Tracing turned on!!!!")  
+####################################################################################
+
diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py
index 9e666bbd..a2dff7f2 100644
--- a/wqflask/wqflask/user_manager.py
+++ b/wqflask/wqflask/user_manager.py
@@ -7,18 +7,80 @@ from __future__ import print_function, division, absolute_import
 
 """
 
-from wqflask import model
+import os
+import hashlib
+import datetime
+import time
+
+import uuid
+import hashlib
+import hmac
+import base64
+
+import simplejson as json
+
+from redis import StrictRedis
+Redis = StrictRedis()
+
+
+from flask import (Flask, g, render_template, url_for, request, make_response,
+                   redirect, flash)
+
+from wqflask import app
 
-from flask import Flask, g
 
 from pprint import pformat as pf
 
+from wqflask import pbkdf2
+
+from wqflask.database import db_session
+
+from wqflask import model
+
+from utility import Bunch, Struct
+
+
+
 from base.data_set import create_datasets_list
 
-#from app import db
-print("globals are:", globals())
 
 
+def timestamp():
+    return datetime.datetime.utcnow().isoformat()
+
+
+
+
+class UserSession(object):
+    cookie_name = 'session_id'
+    
+    def __init__(self):
+        cookie = request.cookies.get(self.cookie_name)
+        if not cookie:
+            self.logged_in = False
+            return
+        else:
+            session_id, separator, session_id_signature = cookie.partition(':')
+            assert len(session_id) == 36, "Is session_id a uuid?"
+            assert separator == ":", "Expected a : here"
+            assert session_id_signature == actual_hmac_creation(session_id), "Uh-oh, someone tampering with the cookie?"
+            self.redis_key = "session_id:" + session_id
+            print("self.redis_key is:", self.redis_key)
+            self.session_id = session_id
+            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()
+
 class UsersManager(object):
     def __init__(self):
         self.users = model.User.query.all()
@@ -28,7 +90,7 @@ class UsersManager(object):
 
 class UserManager(object):
     def __init__(self, kw):
-        self.user_id = int(kw['user_id'])
+        self.user_id = kw['user_id']
         print("In UserManager locals are:", pf(locals()))
         #self.user = model.User.get(user_id)
         #print("user is:", user)
@@ -43,6 +105,260 @@ 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.')
+
+        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)
+        
+        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()
+        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):
+        verification_code = str(uuid.uuid4())
+        key = "verification_code:" + verification_code
+        
+        data = json.dumps(dict(id=self.new_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",
+                               verification_code = verification_code)
+        send_email(to, subject, body)
+
+class Password(object):
+    def __init__(self, unencrypted_password, salt, iterations, keylength, hashfunc):
+        hashfunc = getattr(hashlib, hashfunc)
+        print("hashfunc is:", hashfunc)
+        # On our computer it takes around 1.4 seconds in 2013
+        start_time = time.time()
+        salt = base64.b64decode(salt)
+        print("now salt is:", salt)
+        self.password = pbkdf2.pbkdf2_hex(str(unencrypted_password),
+                                          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'))
+
+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.confirmed = json.dumps(basic_info(), sort_keys=True)
+    db_session.commit()
+                                
+def login():
+    params = request.form if request.form else request.args
+    print("in login params are:", params)
+    if not params:
+        return render_template("new_security/login_user.html")
+    else:
+        user = model.User.query.filter_by(email_address=params['email_address']).one()
+        submitted_password = params['password']
+        pwfields = Struct(json.loads(user.password))
+        encrypted = Password(submitted_password,
+                                      pwfields.salt,
+                                      pwfields.iterations,
+                                      pwfields.keylength,
+                                      pwfields.hashfunc)
+        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())
+            #session_id = "session_id:{}".format(login_rec.session_id)
+            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")
+            
+            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:
+            login_rec.successful = False
+            flash("Invalid email-address or password. Please try again.", "alert-error")
+            response = make_response(redirect(url_for('login')))
+        db_session.add(login_rec)
+        db_session.commit()
+        return response
+ 
+@app.route("/n/logout")     
+def logout():
+    print("Logging out...")
+    UserSession().delete_session()
+    flash("You are now logged out. We hope you come back soon!")
+    response = make_response(redirect(url_for('index_page')))
+    # Delete the cookie
+    response.set_cookie(UserSession.cookie_name, '', expires=0)
+    return response
+    
+       
+################################# 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 = "&"
+    else:
+        combiner = "?"
+    return url + combiner + "hm=" + hm
+
+def verify_url_hmac(url):
+    """Pass in a url that was created with url_hmac and this assures it hasn't been tampered with"""
+    print("url passed in to verify is:", url)
+    # Verify parts are correct at the end - we expect to see &hm= or ?hm= followed by an hmac
+    assert url[-23:-20] == "hm=", "Unexpected url (stage 1)"
+    assert url[-24] in ["?", "&"], "Unexpected url (stage 2)"
+    hmac = url[-20:]
+    url = url[:-24]  # Url without any of the hmac stuff
+
+    #print("before urlsplit, url is:", url)
+    #url = divide_up_url(url)[1]
+    #print("after urlsplit, url is:", 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)
+    hm = hmaced.hexdigest()
+    # "Conventional wisdom is that you don't lose much in terms of security if you throw away up to half of the output."
+    # http://www.w3.org/QA/2009/07/hmac_truncation_in_xml_signatu.html
+    hm = hm[:20]
+    return hm
+
+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
+#    salts aren't immediately known"""
+#    secret_salt = app.confing['SECRET_SALT']
+#    assert len(user_salt) == 32
+#    assert len(secret_salt) == 32
+#    combined = ""
+#    for x, y in user_salt, secret_salt:
+#        combined = combined + x + y
+#    return combined
+    
 
 
 class GroupsManager(object):
@@ -54,3 +370,13 @@ class RolesManager(object):
     def __init__(self):
         self.roles = model.Role.query.all()
         print("Roles are:", self.roles)
+
+
+#class Password(object):
+#    """To generate a master password: dd if=/dev/urandom bs=32 count=1 > master_salt"""
+#    
+#    master_salt = 
+
+
+
+
diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py
index d57bbaa7..deccf459 100644
--- a/wqflask/wqflask/views.py
+++ b/wqflask/wqflask/views.py
@@ -23,7 +23,8 @@ import sqlalchemy
 
 from wqflask import app
 
-from flask import render_template, request, make_response, Response, Flask, g, config, jsonify
+from flask import (render_template, request, make_response, Response,
+                   Flask, g, config, jsonify, redirect, url_for)
 
 from wqflask import search_results
 from base.data_set import DataSet    # Used by YAML in marker_regression
@@ -50,6 +51,11 @@ 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
+#    tracer.turn_on()
 
 @app.route("/")
 def index_page():
@@ -261,15 +267,15 @@ def sharing_info_page():
     template_vars = SharingInfoPage.SharingInfoPage(fd)
     return template_vars
 
-# Take this out or secure it before going into production
+# Take this out or secure it before putting into production
 @app.route("/get_temp_data")
 def get_temp_data():
     temp_uuid = request.args['key']
     return flask.jsonify(temp_data.TempData(temp_uuid).get_all())
 
-@app.route("/thank_you")
-def thank_you():
-    return render_template("security/thank_you.html")
+
+###################################################################################################
+
 
 @app.route("/manage/users")
 def manage_users():
@@ -287,6 +293,55 @@ def manage_groups():
     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")
+
+
+##########################################################################
+
 def json_default_handler(obj):
     '''Based on http://stackoverflow.com/a/2680060/1175849'''
     # Handle datestamps