# 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()