auth.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. 'use strict'
  2. var caseless = require('caseless')
  3. , uuid = require('node-uuid')
  4. , helpers = require('./helpers')
  5. var md5 = helpers.md5
  6. , toBase64 = helpers.toBase64
  7. function Auth (request) {
  8. // define all public properties here
  9. this.request = request
  10. this.hasAuth = false
  11. this.sentAuth = false
  12. this.bearerToken = null
  13. this.user = null
  14. this.pass = null
  15. }
  16. Auth.prototype.basic = function (user, pass, sendImmediately) {
  17. var self = this
  18. if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) {
  19. self.request.emit('error', new Error('auth() received invalid user or password'))
  20. }
  21. self.user = user
  22. self.pass = pass
  23. self.hasAuth = true
  24. var header = user + ':' + (pass || '')
  25. if (sendImmediately || typeof sendImmediately === 'undefined') {
  26. var authHeader = 'Basic ' + toBase64(header)
  27. self.sentAuth = true
  28. return authHeader
  29. }
  30. }
  31. Auth.prototype.bearer = function (bearer, sendImmediately) {
  32. var self = this
  33. self.bearerToken = bearer
  34. self.hasAuth = true
  35. if (sendImmediately || typeof sendImmediately === 'undefined') {
  36. if (typeof bearer === 'function') {
  37. bearer = bearer()
  38. }
  39. var authHeader = 'Bearer ' + (bearer || '')
  40. self.sentAuth = true
  41. return authHeader
  42. }
  43. }
  44. Auth.prototype.digest = function (method, path, authHeader) {
  45. // TODO: More complete implementation of RFC 2617.
  46. // - check challenge.algorithm
  47. // - support algorithm="MD5-sess"
  48. // - handle challenge.domain
  49. // - support qop="auth-int" only
  50. // - handle Authentication-Info (not necessarily?)
  51. // - check challenge.stale (not necessarily?)
  52. // - increase nc (not necessarily?)
  53. // For reference:
  54. // http://tools.ietf.org/html/rfc2617#section-3
  55. // https://github.com/bagder/curl/blob/master/lib/http_digest.c
  56. var self = this
  57. var challenge = {}
  58. var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
  59. for (;;) {
  60. var match = re.exec(authHeader)
  61. if (!match) {
  62. break
  63. }
  64. challenge[match[1]] = match[2] || match[3]
  65. }
  66. var ha1 = md5(self.user + ':' + challenge.realm + ':' + self.pass)
  67. var ha2 = md5(method + ':' + path)
  68. var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
  69. var nc = qop && '00000001'
  70. var cnonce = qop && uuid().replace(/-/g, '')
  71. var digestResponse = qop
  72. ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
  73. : md5(ha1 + ':' + challenge.nonce + ':' + ha2)
  74. var authValues = {
  75. username: self.user,
  76. realm: challenge.realm,
  77. nonce: challenge.nonce,
  78. uri: path,
  79. qop: qop,
  80. response: digestResponse,
  81. nc: nc,
  82. cnonce: cnonce,
  83. algorithm: challenge.algorithm,
  84. opaque: challenge.opaque
  85. }
  86. authHeader = []
  87. for (var k in authValues) {
  88. if (authValues[k]) {
  89. if (k === 'qop' || k === 'nc' || k === 'algorithm') {
  90. authHeader.push(k + '=' + authValues[k])
  91. } else {
  92. authHeader.push(k + '="' + authValues[k] + '"')
  93. }
  94. }
  95. }
  96. authHeader = 'Digest ' + authHeader.join(', ')
  97. self.sentAuth = true
  98. return authHeader
  99. }
  100. Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
  101. var self = this
  102. , request = self.request
  103. var authHeader
  104. if (bearer === undefined && user === undefined) {
  105. self.request.emit('error', new Error('no auth mechanism defined'))
  106. } else if (bearer !== undefined) {
  107. authHeader = self.bearer(bearer, sendImmediately)
  108. } else {
  109. authHeader = self.basic(user, pass, sendImmediately)
  110. }
  111. if (authHeader) {
  112. request.setHeader('authorization', authHeader)
  113. }
  114. }
  115. Auth.prototype.onResponse = function (response) {
  116. var self = this
  117. , request = self.request
  118. if (!self.hasAuth || self.sentAuth) { return null }
  119. var c = caseless(response.headers)
  120. var authHeader = c.get('www-authenticate')
  121. var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
  122. request.debug('reauth', authVerb)
  123. switch (authVerb) {
  124. case 'basic':
  125. return self.basic(self.user, self.pass, true)
  126. case 'bearer':
  127. return self.bearer(self.bearerToken, true)
  128. case 'digest':
  129. return self.digest(request.method, request.path, authHeader)
  130. }
  131. }
  132. exports.Auth = Auth