         account_id    : undefined
         remember_me   : false
     if not opts.successful
-        database.update
-            table       : 'failed_sign_ins_by_ip_address'
-            set         : {email_address:opts.email_address}
-            where       : {time:cass.now(), ip_address:opts.ip_address}
-            consistency : cql.types.consistencies.one
-        database.update
-            table       : 'failed_sign_ins_by_email_address'
-            set         : {ip_address:opts.ip_address}
-            where       : {time:cass.now(), email_address:opts.email_address}
-            consistency : cql.types.consistencies.one
+        record_sign_in_fail
+            email : opts.email_address
+            ip    : opts.ip_address
     else
         database.update
             table       : 'successful_sign_ins'

#############################################################################
# User sign in
#
# Anti-DOS cracking throttling policy:
#
#   * POLICY 1: A given email address is allowed at most 5 failed login attempts per minute.
#   * POLICY 2: A given email address is allowed at most 100 failed login attempts per hour.
#   * POLICY 3: A given ip address is allowed at most 100 failed login attempts per minute.
#   * POLICY 4: A given ip address is allowed at most 250 failed login attempts per hour.
#############################################################################
sign_in = (client, mesg) =>
    #winston.debug("sign_in")
    sign_in_error = (error) ->


    mesg.email_address = misc.lower_email_address(mesg.email_address)

    m = sign_in_check
        email : mesg.email_address
        ip    : client.ip_address
    if m
        winston.debug("WARNING: sign_in fail(email=#{mesg.email}, ip=#{client.ip_address}): #{m}")
        sign_in_error(m)
        return

    signed_in_mesg = null
    async.series([
        # POLICY: Don't allow banned users to sign in.  Slow to check and kind of pointless
        # since it is so easy for a user to just make a different account.  Of course,
        # forcing them to lose access to all their work is useful.
        (cb) ->
            database.is_banned_user
                email_address : mesg.email_address
                cb            : (err, is_banned) ->
                    if err
                        sign_in_error(err)
                        cb(err)
                    else
                        if is_banned
                            sign_in_error("User '#{mesg.email_address}' is banned from SageMathCloud due to violation of the terms of usage.")
                            cb(true)
                        else
                            cb()

        (cb) ->
            # get account and check credentials
            # NOTE: Despite people complaining, we do give away info about whether the e-mail address is for a valid user or not.
