All files / src/api authentication.coffee

22.81% Statements 13/57
0% Branches 0/14
0% Functions 0/5
22.81% Lines 13/57
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 1041x 1x 1x 1x 1x 1x 1x 1x 1x 1x           1x                 1x     1x                                                                                                                                                        
User = require('../model/users').User
crypto = require 'crypto'
logger = require 'winston'
config = require "../config/config"
config.api = config.get('api')
config.auditing = config.get('auditing')
atna = require 'atna-audit'
auditing = require '../auditing'
os = require 'os'
himSourceID = config.auditing.auditEvents.auditSourceID
 
# will NOT audit any successful logins on the following paths (specified as regex patterns)
# only 'noisy' endpoints should be included, such as heartbeats or endpoints that get polled
#
# /transactions is treated as a special case - see below
auditingExemptPaths = [
  /\/tasks/
  /\/events.*/
  /\/metrics.*/
  /\/mediators\/.*\/heartbeat/
  /\/audits/
  /\/logs/
]
 
isUndefOrEmpty = (string) ->
  return not string? or string is ''
 
exports.authenticate = (next) ->
 
  header = this.request.header
  email = header['auth-username']
  authTS = header['auth-ts']
  authSalt = header['auth-salt']
  authToken = header['auth-token']
 
  auditAuthFailure = ->
    audit = atna.userLoginAudit atna.OUTCOME_SERIOUS_FAILURE, himSourceID, os.hostname(), email
    audit = atna.wrapInSyslog audit
    auditing.sendAuditEvent audit, -> logger.debug 'Processed internal audit'
 
  # if any of the required headers aren't present
  if isUndefOrEmpty(email) or isUndefOrEmpty(authTS) or isUndefOrEmpty(authSalt) or isUndefOrEmpty(authToken)
    logger.info "API request made by #{email} from #{this.request.host} is missing required API authentication headers, denying access"
    this.status = 401
    auditAuthFailure()
    return
 
  # check if request is recent
  requestDate = new Date Date.parse authTS
 
  authWindowSeconds = config.api.authWindowSeconds ? 10
  to = new Date()
  to.setSeconds(to.getSeconds() + authWindowSeconds)
  from = new Date()
  from.setSeconds(from.getSeconds() - authWindowSeconds)
 
  if requestDate < from or requestDate > to
    # request expired
    logger.info "API request made by #{email} from #{this.request.host} has expired, denying access"
    this.status = 401
    auditAuthFailure()
    return
 
  user = yield User.findOne(email: email).exec()
  this.authenticated = user
 
  if not user
    # not authenticated - user not found
    logger.info "No user exists for #{email}, denying access to API, request originated from #{this.request.host}"
    this.status = 401
    auditAuthFailure()
    return
 
  hash = crypto.createHash 'sha512'
  hash.update user.passwordHash
  hash.update authSalt
  hash.update authTS
 
  if authToken is hash.digest 'hex'
    # authenticated
 
    if this.path is '/transactions'
      if not this.query.filterRepresentation or this.query.filterRepresentation isnt 'full'
        # exempt from auditing success
        yield next
        return
    else
      for pathTest in auditingExemptPaths
        if pathTest.test this.path
          # exempt from auditing success
          yield next
          return
 
    # send audit
    audit = atna.userLoginAudit atna.OUTCOME_SUCCESS, himSourceID, os.hostname(), email, user.groups.join(','), user.groups.join(',')
    audit = atna.wrapInSyslog audit
    auditing.sendAuditEvent audit, -> logger.debug 'Processed internal audit'
    yield next
  else
    # not authenticated - token mismatch
    logger.info "API token did not match expected value, denying access to API, the request was made by #{email} from #{this.request.host}"
    this.status = 401
    auditAuthFailure()