text: 
  /////////////////// common functions used by the QRAccess SDK  ////////////////////////////
  
  exports.auth = function(customer_id, secret_key, http_method, resource, params, hash_method) {
  // create and return a set of custom headers to go in a QRAccess API request, 
  //   including the corresponding CQR 1.0 Authorization:
  
  	// initialize the custom authorization headers
  	var auth_headers = {
  		'X-Client-Id': customer_id,
  		'X-Timestamp': parseInt(new Date().getTime() / 1000),
  		'X-Nonce': require('node-uuid').v4(),
  		'X-Hash-Method': hash_method
  	};
  	
  	// prepare signature - headers and sorted parameters
  	var signature = http_method.toUpperCase() + " " + resource.toLowerCase() + "\n";
  	for (var h in auth_headers)
  		signature += h + ":" + auth_headers[h] + "\n";
  	if ((typeof params == 'object') && (Object.keys(params).length > 0)) {	
          var param_names = Object.keys(params).sort();
          for (var k in param_names)
              signature += param_names[k] + "=" + params[param_names[k]] + "\n";
          signature = signature.substring(0, signature.length - 1);
      } else if ((typeof params == 'string') && (params.length > 0))
          signature += params;
  
  	// hash with client secret key and base64-encode
  	var crypto = require('crypto');
  	var h = crypto.createHmac(hash_method, secret_key).update(signature).digest('base64');
  	auth_headers['Authorization'] = "CQR 1.0 " + h;
  
  	// return the array of headers
  	return auth_headers
  
  }
  
  exports.verify = function(http_method, uri, headers, params, authorization, cb) {
      
  	var auth_parts = authorization.split(" ");
  	if ((auth_parts.length != 3) || (auth_parts[0] != "CQR") || (auth_parts[1] != "1.0")) {
  		cb(false); return;
  	}
  	var received_crypto_sig = auth_parts[2];
  	    
  	// recompose signature with method, url...
  	var signature = http_method.toUpperCase() + " " + uri.toLowerCase() + "\n";
  	// ... auth headers...
  	['X-Client-Id', 'X-Timestamp', 'X-Nonce', 'X-Hash-Method'].map(function(h_name) {
  		if (headers[h_name])
  			signature += h_name + ":" + headers[h_name] + "\n";
  		else {
  			cb(false); return;
  		}
  	});
  	// ... and request params (if any)
  	if ((typeof params == 'object') && (Object.keys(params).length > 0)) {	
          var param_names = Object.keys(params).sort();
          for (var k in param_names)
              signature += param_names[k] + "=" + params[param_names[k]] + "\n";
          signature = signature.substring(0, signature.length - 1);
      } else if ((typeof params == 'string') && (params.length > 0))
          signature += params;
  
  	var dblib = require("./db_interface.js");
  	// verify timestamp drift
  	if (!dblib.verify_timestamp(headers['X-Timestamp'])) {
  		cb(false); return;
  	}
  	// verify nonce uniqueness
  	dblib.verify_nonce(headers['X-Client-Id'], headers['X-Nonce'], function(is_valid_nonce) {
  	    cb(is_valid_nonce && (
              received_crypto_sig == require('crypto')
                  .createHmac(headers['X-Hash-Method'], require("./constants.js").API_SECRET)
                  .update(signature)
                  .digest('base64')
          ));
  	});
  }
  
  exports.authorize = function(auth_data, cb) {
  // A mobile user sent us, via QRAccess, an array of parameters (in json format):
  //    user= their user id
  //    timestamp= the request timestamp collected at source, in epoch format
  //    nonce= a string randomly generated by the user, that should not repeat in a certain time interval
  //    hash_method= the hash method used to generate the authorization string
  //    authorization= the authorization string
  //    some more parameters may exists, depending on what you stored in the user's keychain at registration
  // We reconstruct the signature based on this data, and check that we have the same signature hash 
  //    for them in our database
  
  	var dblib = require("./db_interface.js");
  
  	// quickly validate input params
  	var hash_method = dblib.accepted_hash_method(auth_data.hash_method);
  	if (
  		auth_data.cw_user &&
  		auth_data.authorization &&
  		dblib.verify_timestamp(parseInt(auth_data.timestamp)) &&
  		(hash_method != null)
  	) {
  		dblib.verify_nonce(auth_data.cw_user, auth_data.nonce, function(nonce_ok) {
  		    if (nonce_ok) {
                  // pull signature from list of auth parameters - doesnt go into calculation
                  var h_sig_received = auth_data.authorization.replace(".", "+");
                  delete auth_data.authorization;
                  // order the user's auth params list and build the signature string
                  var signature = "";
                  var auth_elements = Object.keys(auth_data).sort();
                  for (var k in auth_elements)
                      signature += auth_elements[k] + "=" + auth_data[auth_elements[k]] + "\n";
                  signature = signature.substring(0, signature.length - 1);
                  // get user's secret key and hash the signature with it
                  dblib.get_key_and_id_for_qr_login(auth_data.cw_user, function(err, user_creds) {
                      if (err)
                          cb("");
                      else {
                          var crypto = require('crypto')
                              .createHmac(user_creds.hash_method, user_creds.secret)
                              .update(signature)
                              .digest('base64')
                          ;
                          cb((h_sig_received == crypto) ? user_creds.user_id : "");
                      }
                  });
              } else
                  cb("");
          });
  	} else
  		cb("");
  
  }
  
  