UNPKG

http-auth

Version:

Node.js package for HTTP basic and digest access authentication.

122 lines (97 loc) 4.02 kB
# Base authentication module. Base = require './base' # Utility module. utils = require './utils' # UUID module. uuid = require 'node-uuid' # Digest authentication class. class Digest extends Base # Constructor. constructor: (@options, @checker) -> super @options, @checker # Array of random strings sent to clients. @nonces = [] # Algorithm of encryption, could be MD5 or MD5-sess, default is MD5. @options.algorithm = if @options.algorithm is 'MD5-sess' then 'MD5-sess' else 'MD5' # Quality of protection is by default auth. @options.qop = if @options.qop is 'none' then '' else 'auth' # Processes line from authentication file. processLine: (line) -> [username, realm, hash] = line.split ":" if realm is @options.realm # We need only users for given realm. @options.users.push {username: username, hash: hash} # Parse authorization header. parseAuthorization: (header) -> opts = {} parts = header.split(' ') params = parts.slice(1).join(' ') tokens = params.split(/,(?=(?:[^"]|"[^"]*")*$)/) # Split the parameters by comma. if (parts[0].substr 0, 6) is "Digest" # is Digest. # Parse parameters. i = 0 len = tokens.length while i < len param = /(\w+)=["]?([^"]*)["]?$/.exec(tokens[i]) # Strip quotes and whitespace. if param opts[param[1]] = param[2] i++ opts # Validating hash. validate: (ha2, co, hash) -> ha1 = hash if co.algorithm is 'MD5-sess' # Algorithm. ha1 = utils.md5 "#{ha1}:#{co.nonce}:#{co.cnonce}" if co.qop # Quality of protection. response = utils.md5 "#{ha1}:#{co.nonce}:#{co.nc}:#{co.cnonce}:#{co.qop}:#{ha2}" else response = utils.md5 "#{ha1}:#{co.nonce}:#{ha2}" # Returning result. response is co.response # Searching for user. findUser: (req, co, callback) -> if @validateNonce co.nonce ha2 = utils.md5 "#{req.method}:#{co.uri}" if @checker # Custom authentication. @checker.apply this, [co.username, (hash) => callback.apply this, [{user: co.username if (@validate ha2, co, hash)}] ] else # File based. for user in @options.users # Loop users to find the matching one. if user.username is co.username and @validate ha2, co, user.hash found = true break # Stop searching, we found him. callback.apply this, [{user: co.username if found}] else callback.apply this, [{stale: true}] # Remove nonces. removeNonces: (noncesToRemove) -> for nonce in noncesToRemove index = @nonces.indexOf nonce if index != -1 # Nonce found. @nonces.splice index, 1 # Remove it from array. # Validate nonce. validateNonce: (nonce) -> now = Date.now() # Current time. noncesToRemove = [] # Nonces for removal. for serverNonce in @nonces # Searching for not expired ones. if (serverNonce[1] + 3600000) > now # Not expired ones (1 hour lifetime). if serverNonce[0] is nonce found = true else # Removing expired ones. noncesToRemove.push serverNonce @removeNonces noncesToRemove return found # Generates and returns new random nonce. askNonce: () -> nonce = utils.md5 uuid.v4() # Random nonce. @nonces.push [nonce, Date.now()] # Push into nonces. return nonce # Return it. # Generates request header. generateHeader: (result) -> nonce = @askNonce() stale = if result.stale then true else false # Returning it. return "Digest realm=\"#{@options.realm}\", qop=\"#{@options.qop}\", nonce=\"#{nonce}\", algorithm=\"#{@options.algorithm}\", stale=\"#{stale}\"" # Exporting. module.exports = (options, checker) -> new Digest options, checker