async = require('async')
message = require('smc-util/message')
misc = require('smc-util/misc')
{required, defaults} = misc
auth = require('./auth')
sign_in_fails =
email_m : {}
email_h : {}
ip_m : {}
ip_h : {}
clear_sign_in_fails_m = () ->
sign_in_fails.email_m = {}
sign_in_fails.ip_m = {}
clear_sign_in_fails_h = () ->
sign_in_fails.email_h = {}
sign_in_fails.ip_h = {}
_sign_in_fails_intervals = undefined
record_sign_in_fail = (opts) ->
{email, ip, logger} = defaults opts,
email : required
ip : required
logger : undefined
if not _sign_in_fails_intervals?
_sign_in_fails_intervals = [setInterval(clear_sign_in_fails_m, 60000), setInterval(clear_sign_in_fails_h, 60*60000)]
logger?("WARNING: record_sign_in_fail(#{email}, #{ip})")
s = sign_in_fails
if not s.email_m[email]?
s.email_m[email] = 0
if not s.ip_m[ip]?
s.ip_m[ip] = 0
if not s.email_h[email]?
s.email_h[email] = 0
if not s.ip_h[ip]?
s.ip_h[ip] = 0
s.email_m[email] += 1
s.email_h[email] += 1
s.ip_m[ip] += 1
s.ip_h[ip] += 1
sign_in_check = (opts) ->
{email, ip} = defaults opts,
email : required
ip : required
s = sign_in_fails
if s.email_m[email] > 3
return "Wait a minute, then try to login again. If you can't remember your password, reset it or email [email protected]."
if s.email_h[email] > 30
return "Wait an hour, then try to login again. If you can't remember your password, reset it or email [email protected]."
if s.ip_m[ip] > 10
return "Wait a minute, then try to login again. If you can't remember your password, reset it or email [email protected]."
if s.ip_h[ip] > 50
return "Wait an hour, then try to login again. If you can't remember your password, reset it or email [email protected]."
return false
exports.sign_in = (opts) ->
{client, mesg} = opts = defaults opts,
client : required
mesg : required
logger : undefined
database : required
host : undefined
port : undefined
cb : undefined
if opts.logger?
dbg = (m) ->
opts.logger.debug("sign_in(#{mesg.email_address}): #{m}")
dbg()
else
dbg = ->
tm = misc.walltime()
sign_in_error = (error) ->
dbg("sign_in_error -- #{error}")
exports.record_sign_in
database : opts.database
ip_address : client.ip_address
successful : false
email_address : mesg.email_address
account_id : account?.account_id
client.push_to_client(message.sign_in_failed(id:mesg.id, email_address:mesg.email_address, reason:error))
opts.cb?(error)
if not mesg.email_address
sign_in_error("Empty email address.")
return
if not mesg.password
sign_in_error("Empty password.")
return
mesg.email_address = misc.lower_email_address(mesg.email_address)
m = sign_in_check
email : mesg.email_address
ip : client.ip_address
if m
sign_in_error("sign_in_check fail(ip=#{client.ip_address}): #{m}")
return
signed_in_mesg = undefined
account = undefined
async.series([
(cb) ->
dbg("get account and check credentials")
opts.database.get_account
email_address : mesg.email_address
columns : ['password_hash', 'account_id', 'passports']
cb : (err, _account) ->
account = _account; cb(err)
(cb) ->
dbg("got account; now checking if password is correct...")
auth.is_password_correct
database : opts.database
account_id : account.account_id
password : mesg.password
password_hash : account.password_hash
cb : (err, is_correct) ->
if err
cb("Error checking correctness of password -- #{err}")
return
if not is_correct
if not account.password_hash
cb("The account #{mesg.email_address} exists but doesn't have a password. Either set your password by clicking 'Forgot Password?' or log in using #{misc.keys(account.passports).join(', ')}. If that doesn't work, email [email protected] and we will sort this out.")
else
cb("Incorrect password for #{mesg.email_address}. You can reset your password by clicking the 'Forgot Password?' link. If that doesn't work, email [email protected] and we will sort this out.")
else
cb()
(cb) ->
if mesg.remember_me
dbg("remember_me -- setting the remember_me cookie")
signed_in_mesg = message.signed_in
id : mesg.id
account_id : account.account_id
email_address : mesg.email_address
remember_me : false
hub : opts.host + ':' + opts.port
client.remember_me
account_id : signed_in_mesg.account_id
email_address : signed_in_mesg.email_address
cb : cb
else
cb()
], (err) ->
if err
dbg("send error to user (in #{misc.walltime(tm)}seconds) -- #{err}")
sign_in_error(err)
opts.cb?(err)
else
dbg("user got signed in fine (in #{misc.walltime(tm)}seconds) -- sending them a message")
client.signed_in(signed_in_mesg)
client.push_to_client(signed_in_mesg)
opts.cb?()
)
exports.sign_in_using_auth_token = (opts) ->
{client, mesg} = opts = defaults opts,
client : required
mesg : required
logger : undefined
database : required
host : undefined
port : undefined
cb : undefined
if opts.logger?
dbg = (m) ->
opts.logger.debug("sign_in_using_auth_token(#{mesg.email_address}): #{m}")
dbg()
else
dbg = ->
tm = misc.walltime()
sign_in_error = (error) ->
dbg("sign_in_using_auth_token_error -- #{error}")
exports.record_sign_in
database : opts.database
ip_address : client.ip_address
successful : false
email_address : mesg.auth_token
account_id : account?.account_id
client.push_to_client(message.error(id:mesg.id, error:error))
opts.cb?(error)
if not mesg.auth_token
sign_in_error("missing auth_token.")
return
if mesg.auth_token?.length != 24
sign_in_error("auth_token must be exactly 24 characters long")
return
m = sign_in_check
email : mesg.auth_token
ip : client.ip_address
if m
sign_in_error("sign_in_check fail(ip=#{client.ip_address}): #{m}")
return
signed_in_mesg = undefined
account = account_id = undefined
async.series([
(cb) ->
dbg("get account and check credentials")
opts.database.get_auth_token_account_id
auth_token : mesg.auth_token
cb : (err, _account_id) ->
if not err and not _account_id
err = 'auth_token is not valid'
account_id = _account_id; cb(err)
(cb) ->
dbg("successly got account_id; now getting more information about the account")
opts.database.get_account
account_id : account_id
columns : ['email_address']
cb : (err, _account) ->
account = _account; cb(err)
(cb) ->
dbg("remember_me -- setting the remember_me cookie")
signed_in_mesg = message.signed_in
id : mesg.id
account_id : account_id
email_address : account.email_address
remember_me : false
hub : opts.host + ':' + opts.port
client.remember_me
account_id : signed_in_mesg.account_id
email_address : signed_in_mesg.email_address
ttl : 12*3600
cb : cb
], (err) ->
if err
dbg("send error to user (in #{misc.walltime(tm)}seconds) -- #{err}")
sign_in_error(err)
opts.cb?(err)
else
dbg("user got signed in fine (in #{misc.walltime(tm)}seconds) -- sending them a message")
client.signed_in(signed_in_mesg)
client.push_to_client(signed_in_mesg)
opts.cb?()
)
exports.record_sign_in = (opts) ->
opts = defaults opts,
ip_address : required
successful : required
database : required
email_address : undefined
account_id : undefined
remember_me : false
if not opts.successful
record_sign_in_fail
email : opts.email_address
ip : opts.ip_address
else
opts.database.log
event : 'successful_sign_in'
value :
ip_address : opts.ip_address
email_address : opts.email_address ? null
remember_me : opts.remember_me
account_id : opts.account_id