Ejemplo n.º 1
0
def run(x):
  input = 'c79855a0f5344b655b8bc30220b545e2ef25b88b723745b7fed976d46e260c91' # hash of https://is.gd/Bartha
  result = 0
  count = 0
  while result < x:
    salt = str(secrets.randbits(257))
    result = divide(binascii.hexlify(argon2.argon2_hash(input,salt)))
    data = binascii.hexlify(argon2.argon2_hash(input,salt))
    count += 1
#  return [str(data)[2:-1], int(salt), count]
  return [int(salt), count]
Ejemplo n.º 2
0
def authenticate(username: str, password: str) -> str:
    """
    This method is used to authenticate a user using their credentials.
    If the authentication process is successful, a user token is generated
    and published to the user_cache. The user token is also returned by this method
    so that it can be passed to the client as a response.
    If the user couldn't be authenticated, an empty string is returned.

    :param username: The user's username
    :param password: The user's password
    :return: The user's token
    """
    user = users.get(username)

    if user is None:
        del password
        return ""

    hash_result = argon2.argon2_hash(password, user.password_salt)
    del password

    if hash_result == user.password_hash:

        user_token = os.urandom(64).hex()
        while user_cache.exists(user_token):
            user_token = os.urandom(64).hex()

        pub('user_authenticated', user_token, username, user.privilege_level, config.get('user_ttl'))
        return user_token

    return ""
Ejemplo n.º 3
0
    def _hash_argon2(self, username, pwd, salt=None):
        """Hash username and password, generating salt value if required
        Use argon2

        :returns: base-64 encoded string.
        """
        if not argon2_available:
            raise Exception("argon2 required."
                            " Please install the argon2 library.")

        if salt is None:
            salt = os.urandom(self.saltlength['argon2'])

        assert len(salt) == self.saltlength['argon2'], \
            "Incorrect salt length %s" % salt

        cleartext = "%s\0%s" % (username, pwd)

        # t = time cost (# of iterations)
        # m = memory cost kilobytes
        # p = parallelism degree (# parallel threads)
        h = argon2.argon2_hash(cleartext, salt, t=31, m=31, p=2)

        # 'a' for argon2
        hashed = b'a' + salt + h
        return b64encode(hashed)
Ejemplo n.º 4
0
def users_add():
    """
    Register a new user
    Example:
    requests.post("api/users/add", data={
        "username": "******",
        "password": "******"
    })
    """

    username = request.json.get("username")
    password = request.json.get("password")

    if not (username and password):
        return jsonify({
            "success": False,
            "message": "username and password must not be empty"
        })

    if _exists(users, {"username": username}):
        return jsonify({
            "success": False,
            "message": "A user with this username already exists"
        })

    users.insert_one({
        "username": username,
        "password": argon2.argon2_hash(password, self.salt)
    })

    return jsonify({"success": True, "message": "User created"})
Ejemplo n.º 5
0
def loginUser(username, password):
    loginResult = False
    jsonPassword = ""
    jsonSalt = ""

    with open(CREDENTIALS, "r") as file:
        try:
            fileContents = json.load(file)

            # see if username exists, if not, access denied
            if not (fileContents[username]):
                loginUser = False
            else:
                jsonPassword = fileContents[username]["password"]
                jsonSalt = fileContents[username]["salt"]

                # hash given password using argon2
                hashedPassword = str(
                    argon2.argon2_hash(password=password, salt=jsonSalt))

                # check if stored (hashed) password is same as the hashed inputted password
                if jsonPassword == hashedPassword:
                    loginUser = True
                else:
                    loginUser = False
        except:
            # if file fails to read, access denied
            accessDenied()

    return loginUser
Ejemplo n.º 6
0
def register_user(param):
    conn = None

    try:
        conn = MySQLdb.connect('localhost', 'testuser', 'xxxx', 'cs_bank_2')

        salt = ''.join(random.SystemRandom().choice(string.ascii_uppercase +
                                                    string.digits)
                       for _ in range(20))
        passw = argon2.argon2_hash(
            password=param['password'],
            salt=salt,
            t=16,
            m=8,
            p=1,
            buflen=128,
            argon_type=argon2.Argon2Type.Argon2_i).decode("ISO-8859-15")

        sql = "insert into users (FirstName, LastName, Email, UserName, City, Address, Password, Salt) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')" % (
            param['firstname'], param['lastname'], param['email'],
            param['username'], param['city'], param['address'], passw, salt)
        print(sql, '\n')
        cursor = conn.cursor(MySQLdb.cursors.DictCursor)
        cursor.execute(sql)
        conn.commit()

    except MySQLdb.Error as e:
        print('error {}: {}'.format(e.args[0], e.args[1]))
        return False

    finally:
        if conn:
            conn.close()
            return True
Ejemplo n.º 7
0
def banklogin():
    if request.method == 'GET':
        return redirect("/onlinebanking")

    username = request.form.get('username')
    password = request.form.get('password')
    new_session_value = ''
    current_user = ''
    action_login = True

    for user in users:
        if str(username) == user['username'] and base64.b64decode(
                user['password_hash']) == argon2.argon2_hash(
                    password=str(password),
                    salt=user['salt'],
                    t=16,
                    m=8,
                    p=1,
                    buflen=128,
                    argon_type=argon2.Argon2Type.Argon2_i):
            new_session_value = str(uuid.uuid4())
            user['session'] = new_session_value
            current_user = user['username']
            action_login = False
            break

    resp = make_response(
        render_template('onlinebanking.html',
                        transactions=transactions,
                        current_user=current_user,
                        action_login=action_login))
    resp.set_cookie('session-cookie', new_session_value)
    return resp
Ejemplo n.º 8
0
def accepted(username, password):
    userData = {}
    fileContents = {}

    # read file, even if it's empty
    with open(CREDENTIALS, "r") as outfile:

        # check if file is empty
        # this try-catch needs to be here, or else if reading
        #  an empty file, program will crash
        try:
            fileContents = json.load(outfile)
        except:
            '''do nothing'''

    # generate unique salt for user
    salt = generateSalt()
    hashedPassword = str(argon2.argon2_hash(password=password, salt=salt))
    userData[username] = {"password": hashedPassword, "salt": salt}

    fileContents.update(userData)

    # write updates to credential file
    with open("credentials.json", "w") as outfile:
        json.dump(fileContents, outfile)

    print(ACCEPTED)
Ejemplo n.º 9
0
    def _hash_argon2(self, username, pwd, salt=None):
        """Hash username and password, generating salt value if required
        Use argon2

        :returns: base-64 encoded string.
        """
        if not argon2_available:
            raise Exception("argon2 required."
                            " Please install the argon2 library.")

        if salt is None:
            salt = os.urandom(self.saltlength['argon2'])

        assert len(salt) == self.saltlength['argon2'], "Incorrect salt length %s" % salt

        cleartext = "%s\0%s" % (username, pwd)

        # t = time cost (# of iterations)
        # m = memory cost kilobytes
        # p = parallelism degree (# parallel threads)
        h = argon2.argon2_hash(cleartext, salt, t=31, m=31, p=2)

        # 'a' for argon2
        hashed = b'a' + salt + h
        return b64encode(hashed)
Ejemplo n.º 10
0
def SlowKDF(input_passphrase, input_salt, memory, iterations):
    beginning = time()
    xor_digest=sha512(input_passphrase+input_salt).digest()
    xor_digest+=sha512(xor_digest+input_salt+input_passphrase).digest()
    digest=sha512(input_salt+input_passphrase+xor_digest).digest()
    digest=sha3_512(digest).digest()
    start = time()
    for counter in range(iterations):
        print ("Iteration %s from %s..." % (counter+1, iterations) )
        shahehehe=sha512(xor_digest+input_passphrase+input_salt+digest).digest()
        # The passphrase (shahehehe) size of scrypt should be equal to the block size of the SHA-256 (64 bytes)
        # to avoid the initial hashing with SHA-256 if the password is longer or shorter (HMAC-SHA256).
        # Block size should not be confused with the digest size (for SHA-256 the block size is 64 bytes, the digest size is 32 bytes).
        print ("    Computing Scrypt...")
        digest=scrypt.hash(shahehehe, input_salt, N = memory, r = 8, p = 1, buflen = 128)
        xor_digest=strxor(xor_digest, digest)
        shahehehe=sha512(xor_digest+input_passphrase+input_salt+digest).digest()
        kek=sha3_512(shahehehe).digest()
        print ("    Computing Argon2d...")
        digest=argon2.argon2_hash(password=digest+shahehehe+xor_digest+kek, salt=input_salt, t=1, m=memory, p=1, buflen=128, argon_type=argon2.Argon2Type.Argon2_d)
        xor_digest=strxor(xor_digest, digest)
        current = time()
        diff=current - start
        estimated_total = (diff / (counter + 1) ) * iterations 
        print ("    Elapsed time:", TimeToString(current - beginning))
        if counter+1 < iterations:
            print ("    Estimated remaining time:", TimeToString(estimated_total - diff) )
    kek=sha3_512(xor_digest+input_passphrase+input_salt+digest).digest()
    shahehehe=sha512(kek+digest+input_passphrase+input_salt+xor_digest).digest()
    kek2=sha3_512(kek+shahehehe+digest+input_passphrase+input_salt+xor_digest).digest()
    shahehehe=sha512(kek2+kek+shahehehe).digest()
    current = time()
    print ("    Elapsed time:", TimeToString(current - beginning))
    return shahehehe
Ejemplo n.º 11
0
def register(username: str, password: str, privilege_level: int) -> bool:
    """
    Registers a new user.
    Checks, if the user already exists and only registers a new one, if
    the username isn't already taken.

    :param username: The new user's username
    :param password: The new user's password
    :param privilege_level: The new user's privilege level
    :return: Whether the new user was registered or not
    """

    password_salt = os.urandom(512)
    password_hashed = argon2.argon2_hash(password, password_salt)
    del password

    if users.get(username) is not None:
        return False

    user = users.create(username, password_hashed, password_salt)
    with persistance():
        user.privilege_level = privilege_level

    user_token = os.urandom(64).hex()
    while user_cache.exists(user_token):
        user_token = os.urandom(64).hex()

    pub('user_authenticated', user_token, username, user.privilege_level, config.get('user_ttl'))
    return True
Ejemplo n.º 12
0
def enroll(username):
    enrolledUsers = {}

    try:
        enrolledUsers = json.load(open('enrolled.json'))
    except:
        ""
    salt = generate_salt()

    ## key stretching: to be stored in password file
    ## the digest
    ## keep this digest for authentication and store the hash chain into the password file
    digest = str(argon2.argon2_hash(password, salt))

    ## key strengthening
    #strongDigest = str(argon2.argon2_hash(digest, salt, 100))

    ## store the digest into the password file and write it
    enrolledUsers[username] = {
        #"PASSWORD": hashedPwd, "SALT": salt
        "PASSWORD": digest,
        "SALT": salt
    }

    enrolledUsers.update(enrolledUsers)

    with open("enrolled.json", 'w') as out:
        json.dump(enrolledUsers, out)
Ejemplo n.º 13
0
def update_password(usergroup, password):

    password_salt = os.urandom(512)
    password_hashed = argon2.argon2_hash(password, password_salt)
    del password

    usergroup.password_salt = password_salt
    usergroup.password_hash = password_hashed
Ejemplo n.º 14
0
def to_hash(password, salt=None):
    if not salt:
        salt = os.urandom(20)

    phash = argon2.argon2_hash(password=password, salt=salt, t=argon_settings["t"],
                     m=argon_settings["m"], p=argon_settings["p"])

    return [phash, salt]
def hashPassword(password, salt, algo='argon2', params={'t': 5}):
    if algo == 'argon2':
        return binascii.hexlify(
            argon2.argon2_hash(password,
                               salt,
                               **params)).decode()
    else:
        raise ValueError('Unknown algorithm "%s".' % algo)
Ejemplo n.º 16
0
 def hashAndEncrypt(self, password, key):
     dk = hashlib.pbkdf2_hmac(hash_name='sha384',
                              password=password.encode(),
                              salt=b'',
                              iterations=100000)
     hash = binascii.hexlify(dk)
     hash = base64.b64encode(hash)
     cryp = argon2.argon2_hash(hash, key)
     return (cryp)
Ejemplo n.º 17
0
def register_usergroup(name, password_plain):
    password_salt = os.urandom(512)
    password_hashed = argon2.argon2_hash(password_plain, password_salt)
    del password_plain

    if usergroup_by_name(name) is not None:
        raise Exception("user already exists")

    return new_usergroup(name, password_salt, password_hashed)
Ejemplo n.º 18
0
def hashed_password(password: str, salt: str = None) -> (str, str):
    '''
    Prepares typed-in password to be dumped to the db. Basically it's just:
    sha256(sha256(password) + salt) with some sort of permutations.
    I'm not considering using of the pepper thing because of its several flaws that
    make it pretty much useless. Read more at https://stackoverflow.com/a/16896216
    '''
    salt = salt if salt else token_hex(salt_length)
    return (argon2_hash(password, salt).hex(), salt)
Ejemplo n.º 19
0
 def test_simple_call(self):
     actual = argon2.argon2_hash("password", "some_salt", )
     self.assertEqual(
         actual,
         (b'\xa8&x\xc7\xd9\xc4\x1f\xdf[2\xd9hq\xab\xe5\xb4WV\x89\xca\xa4\xee\xb3\x98\xf1I\xd1'
          b'\xdaf\xe7=\xfaA\x04\xeb\xe1\xfd\x94)\xad\x84\r\x9ed<8xE\xc3\xd3\xfb\x13\xcbN\xcf\\}'
          b'\xfd-9\x8b\x07@\xd8\x10\x1a\x83\x05\xd5\xfd\xc4m\x9f\xd7\x81\xdcX\x87\xb2\x02\xa9R'
          b'\xc1\x9d\xaf6\xbb\x8c\xe1vH+\x07\xc7Y\x80\xb3\xb5\xf8\xba\xbd\x87\xd8\xf5\xea\x1a'
          b'\x04V&\xf7\xde\x9b\x93\x8dbQ\x91e\xf6\xd6\xa2\xd8G8\xe3\x9a\x03\xf3'))
Ejemplo n.º 20
0
 def test_specified_call(self):
     actual = argon2.argon2_hash(password="******", salt="some salt", t=16, m=8, p=1,
                                 buflen=128, argon_type=argon2.Argon2Type.Argon2_i)
     self.assertEqual(
         actual,
         (b"\x1f>\xe0\xb8\x88%\xef\xee\xb3n\\\xb85\x03\x14\xb8\xb8O\x02Zk\xbf<\xd5\xa0C\xf2,p"
          b"\x00\xda\xd7Bc\xa71\x84\x10\x00\x8cx'\xec?Q\x8499\x9b\xd4)\xf1\x98F\x13!\x8bB\x12!"
          b"\xc3U\x8d\x9a\xb5\x10\x8cIo\xd2p\xcd'\x8c\x96d\xa5?{\x1d*\xaf\xab\x99\x9e\xe9c\xa4"
          b"\xb7\xb2\x00\xfa\x82\x96/\xdei_1Nun\x92j\n\xf3D#\x05\tj\xa2\x92\xd5\xf4nym\xd1Kq"
          b"\xa1|\xd19\xa9Q8"))
Ejemplo n.º 21
0
    def create(name, email, password, address, postal_code):
        s = urandom(16)

        ph = argon2.argon2_hash(password, s)

        Users.add(name=name,
                  email=email,
                  salt=s,
                  pass_hash=ph,
                  address=address,
                  postal_code=postal_code)
Ejemplo n.º 22
0
 def get_key(key):
     """
     Query for a key from the database.
     """
     mid_hash = b64encode(
         argon2_hash(password=key,
                     salt=API_KEY_SALT,
                     t=HASH_TIME_PARAM,
                     m=HASH_MEMORY_PARAM,
                     p=HASH_PARALLELIZATION_PARAM)).decode()
     return APIKey.objects.get(key=API_KEY_SALT + "$" + mid_hash)  # pylint: disable=no-member
Ejemplo n.º 23
0
def login_route():
    if request.method == "POST":
        if request.form.get('username') == username and base64.b64encode(
                argon2_hash(request.form.get('password'),
                            salt)).decode('utf-8') == password:
            session['logged_in'] = True
            return redirect('/', code=303)

        flash("Invalid credentials!", category="error")

    return render_template("login.html")
Ejemplo n.º 24
0
    def create(name, username, email, password, lang):
        s = urandom(16)

        ph = argon2.argon2_hash(password, s)

        return Users.add(name=name,
                         username=username,
                         email=email,
                         salt=s,
                         pass_hash=ph,
                         lang=lang)
Ejemplo n.º 25
0
def make_hash(password, salt):
    """Generate a hash from a password and a salt."""

    computed_hash = argon2.argon2_hash(password=password,
                                       salt=salt,
                                       t=TIME,
                                       m=MEM,
                                       p=PAR,
                                       buflen=SIZE,
                                       argon_type=ARGON_TYPE)
    return computed_hash
Ejemplo n.º 26
0
 def _hash(self, string, salt):
     """
     Hashes string with salt, argon2, and params from constants
     :param string: Thing to hash
     :param salt: Salt to hash it with
     :return: bytearray
     """
     return argon2.argon2_hash(password=string,
                               salt=salt,
                               t=argon_settings["t"],
                               m=argon_settings["m"],
                               p=argon_settings["p"])
Ejemplo n.º 27
0
def _get_seed_argon2(passphrase):
    """
    Given a string passphrase, generates a 64-byte seed.

    The expectations from the seed are that it
    1. Is deterministic.
    2. Is statistically uniform.
    3. Is hard to compute.
    4. Preserves the enthropy given (up to 64 bytes).
    """
    from argon2 import argon2_hash
    return argon2_hash(passphrase, passphrase, t=4096, m=16, buflen=64)
Ejemplo n.º 28
0
 def test_simple_call(self):
     actual = argon2.argon2_hash(
         "password",
         "some_salt",
     )
     self.assertEqual(actual, (
         b'\xa8&x\xc7\xd9\xc4\x1f\xdf[2\xd9hq\xab\xe5\xb4WV\x89\xca\xa4\xee\xb3\x98\xf1I\xd1'
         b'\xdaf\xe7=\xfaA\x04\xeb\xe1\xfd\x94)\xad\x84\r\x9ed<8xE\xc3\xd3\xfb\x13\xcbN\xcf\\}'
         b'\xfd-9\x8b\x07@\xd8\x10\x1a\x83\x05\xd5\xfd\xc4m\x9f\xd7\x81\xdcX\x87\xb2\x02\xa9R'
         b'\xc1\x9d\xaf6\xbb\x8c\xe1vH+\x07\xc7Y\x80\xb3\xb5\xf8\xba\xbd\x87\xd8\xf5\xea\x1a'
         b'\x04V&\xf7\xde\x9b\x93\x8dbQ\x91e\xf6\xd6\xa2\xd8G8\xe3\x9a\x03\xf3'
     ))
Ejemplo n.º 29
0
    def authenticated(self, username: str, password: str) -> str:
        """
        Validate user credentials and return user document ID
        :param username: Plaintext username
        :param password: Hashed password
        :return: user's document ID as string, "" if not authorized
        """

        user_doc = self.users.find_one({"username": username})
        if user_doc and (user_doc["password"] == argon2.argon2_hash(password, self.salt)):
            return str(user_doc["_id"])

        return ""  # Not authorized
Ejemplo n.º 30
0
 def hash_password(password, salt=None):
     """
     Returns a hash of a password.
     """
     if salt is None:
         salt = b64encode(urandom(15)).decode()
     mid_hash = b64encode(
         argon2_hash(password=password,
                     salt=salt,
                     t=HASH_TIME_PARAM,
                     m=HASH_MEMORY_PARAM,
                     p=HASH_PARALLELIZATION_PARAM)).decode()
     return salt + "$" + mid_hash
Ejemplo n.º 31
0
def validatePassword(salt, password, pwdHash):
    """
    Uses the stored salt and password hash (from password file) to validate the given password.
    
    Args:
        salt: Retrieved from password file using passed username from cmd line argument
        password: The password given via cmd line argument
        pwdHash: Retrieved from password file using passed username from cmd line argument
    """
    if argon2.argon2_hash(password, salt) == pwdHash:
        accessGranted()
    else:
        accessDenied()
Ejemplo n.º 32
0
def create_api_key(params):
    """
    ### Overview
    Create an API key for a user. Only administrators may create api keys for other users.

    ### Parameters
    allowed_api_calls (optional): A list of API calls that the API token can perform. If left
                                  empty, all of the user's permissions will be granted to the
                                  token. This may not specify any API call that the user does not
                                  currently have access to. <list>
    user_context (optional, requires administrator) <str>
    """
    # Retrieve the current user object (Allowing for administrator overrides)
    user, allowed_methods, _ = get_context(params)

    # Set the owner of the new API key to be the current user
    owner = user.username

    allowed_api_calls = params.get('allowed_api_calls')

    # Verify allowed api calls
    if allowed_api_calls:
        if any(method not in allowed_methods for method in allowed_api_calls):
            if '*' not in allowed_methods:
                raise PermissionDenied(
                    'Cannot create API key with more permissions than key owner.'
                )
    else:
        allowed_api_calls = allowed_methods

    # Create the key
    original_key = '{}{}{}{}{}'.format(
        str(uuid4()),
        str(uuid4()),
        str(uuid4()),
        str(uuid4()),
        str(uuid4()),
    )
    mid_hash = b64encode(
        argon2_hash(password=original_key,
                    salt=API_KEY_SALT,
                    t=HASH_TIME_PARAM,
                    m=HASH_MEMORY_PARAM,
                    p=HASH_PARALLELIZATION_PARAM)).decode()

    key = APIKey(key=API_KEY_SALT + "$" + mid_hash,
                 owner=owner,
                 allowed_api_calls=allowed_api_calls)
    key.save(force_insert=True)

    return success_response(api_key=original_key)
Ejemplo n.º 33
0
def authenticate():
    u = Users.query.filter_by(username=request.json["username"]).first()
    if u is None:
        return {"status": "fail", "error": ""}
    if u.password_hash == argon2.argon2_hash(request.json["password"], u.salt):
        g.user = u
        g.token = make_jwt(
            {
                "uid": u.id,
                "at": int(time.time()),
                "exp": int(time.time()) + 604800
            }, app.config["SECRET_KEY"])
        return {"status": "ok", "username": u.username}
    return {"status": "fail", "error": ""}
Ejemplo n.º 34
0
def generate_keys(key, salt, time, memory, distribution):
    """ generate and derive keys for encryption"""
    log("i", "Generating keys....")
    keys = []
    keys.append(argon2.argon2_hash(key, salt, t=time, m=memory))

    # Derive keys from Main Argon Key
    for i in range(0, distribution):
        keys.append(hashlib.sha256(keys[i]).digest())

    # Remove argon base key
    keys.pop(0)

    return keys
Ejemplo n.º 35
0
def hash_secret_strong(s):
    """Hash secret, case-sensitive string to binary data.

    This is the strong version which should be used for passwords but not for
    huge data sets like indentification numbers.
    """
    if not s:
        s = ""

    # WARNING: changing these parameter invalides the entire table!
    # INFO: buflen is in bytes, not bits! So this is a 256bit output
    #       which is higher than the current (2015-12) recommendation
    #       of 128bit. We use 2 lanes and 4MB of memory. 4 passes seems
    #       to be a good choice.
    return argon2_hash(s.encode("utf8"), app.config["ARGON2_SALT"], buflen=32, t=4, p=2, m=(1 << 12))
Ejemplo n.º 36
0
def hash_secret_weak(s):
    """Hash secret, case-sensitive string to binary data.

    This is the weak version which should be used for large data sets like
    identifiers, but NOT for passwords!
    """
    if not s:
        s = ""

    # WARNING: changing these parameter invalides the entire table!
    # INFO: buflen is in bytes, not bits! So this is a 256bit output
    #       which is higher than the current (2015-12) recommendation
    #       of 128bit. We use 2 lanes and 64KB of memory. One pass has
    #       to be enough, because otherwise we need to much time while
    #       importing.
    return argon2_hash(s.encode("utf8"), app.config["ARGON2_SALT"], buflen=32, t=1, p=2, m=(1 << 6))
Ejemplo n.º 37
0
Archivo: mok.py Proyecto: alex-dot/mok
def do_login(db):
    logged_in = check_token( request.get_cookie("token"), db )
    username = request.forms.get('username')
    password = request.forms.get('password')

    user = db.execute('SELECT id,salt,password from users where username=?', (username,)).fetchone()
    if user == None:
        return dict(login='******', logged_in=logged_in)

    password = argon2_hash( password, user['salt'], buflen=32 )
    password = hexlify(password).decode()
    if password != user['password']:
        return dict(login='******', logged_in=logged_in)

    token_random = Random.get_random_bytes(32)
    token        = hexlify(token_random).decode()
    token_expiry = int(time.time()) + 3600
    db.execute('UPDATE users SET token=?,token_expiry=? WHERE id=?', (token, token_expiry, user['id']))

    response.set_cookie("token", token, max_age=3600, expires=token_expiry)
    # do max_age and expires really work?

    return dict(login='******', logged_in=logged_in)