I’ve been using the following Database Connection Login script with the Node 4 runtime to automatically migrate users from my Azure SQL database. The script was copied mostly as is from some Auth0 docs (don’t remember where exactly).
/**
* See https://auth0.com/docs/users/migrations/automatic
*
* Once all users are migrated, then update this function to a NO-OP:
*
* function login (email, password, callback) {
* return callback(null, null);
* }
*
* !! Important !!
* Once this function is converted to NO-OP, do NOT disable the
* "Import Users to Auth0" setting on the connection settings page.
* Keeping this setting enabled will ensure that users are directed
* to use the new Auth0 database workflow.
*
* Note: Saving this script complains about 'WrongUsernameOrPasswordError'
* not being defined. This is OK, so save anyway.
*/
function login(email, password, callback) {
//this example uses the "tedious" library
//more info here: http://pekim.github.io/tedious/index.html
var Connection = require('tedious@1.11.0').Connection;
var Request = require('tedious@1.11.0').Request;
var TYPES = require('tedious@1.11.0').TYPES;
var connection = new Connection({
userName: configuration.legacyUserDatabaseUsername,
password: configuration.legacyUserDatabasePassword,
server: configuration.legacyUserDatabaseServer,
options: {
database: configuration.legacyUserDatabase,
encrypt: true,
rowCollectionOnRequestCompletion: true
}
});
console.log(connection);
var query = "SELECT Id, Email, Password " +
"FROM dbo.Users WHERE Email = @Email";
connection.on('debug', function (text) {
// Uncomment next line in order to enable debugging messages
//console.log(text);
}).on('errorMessage', function (text) {
//console.log(JSON.stringify(text, null, 2));
return callback(text);
}).on('infoMessage', function (text) {
// Uncomment next line in order to enable information messages
//console.log(JSON.stringify(text, null, 2));
});
connection.on('connect', function (err) {
if (err) {
return callback(err); }
var request = new Request(query, function (err, rowCount, rows) {
if (err) {
callback(new Error(err));
} else if (rowCount < 1) {
// unauthorized
callback(new WrongUsernameOrPasswordError(email));
} else {
validatePassword(password, rows[0][2].value, function(err, isValid) {
if (!isValid) {
// unauthorized
return callback(new WrongUsernameOrPasswordError(email));
}
var profile = {
user_id: rows[0][0].value,
email: rows[0][1].value
};
callback(null, profile);
});
}
});
request.addParameter('Email', TYPES.VarChar, email);
connection.execSql(request);
});
/**
* fixedTimeComparison
*
* Taken unmodified from template:
* ASP.NET Membership Provider (MVC4 - Simple Membership)
*
* This function gets the password entered by the user, and the original password
* hash and salt from database and performs an HMAC SHA256 hash.
*
* @password {[string]} the password entered by the user
* @originalHash {[string]} the original password hashed from the database
* (including the salt).
* @return {[bool]} true if password validates
*/
function fixedTimeComparison(a, b) {
var mismatch = (a.length === b.length ? 0 : 1);
if (mismatch) {
b = a;
}
for (var i = 0, il = a.length; i < il; ++i) {
var ac = a.charCodeAt(i);
var bc = b.charCodeAt(i);
mismatch += (ac === bc ? 0 : 1);
}
return (mismatch === 0);
}
/**
* validatePassword
*
* Taken unmodified from template:
* ASP.NET Membership Provider (MVC4 - Simple Membership)
*
* This function gets the password entered by the user, and the original password
* hash and salt from database and performs an HMAC SHA256 hash.
*
* @password {[string]} the password entered by the user
* @originalHash {[string]} the original password hashed from the database
* (including the salt).
* @return {[bool]} true if password validates
*/
function validatePassword(password, originalHash, callback) {
var iterations = 1000;
var hashBytes = new Buffer(originalHash, 'base64');
var salt = hashBytes.slice(1, 17).toString('binary');
var hash = hashBytes.slice(17, 49);
crypto.pbkdf2(password, salt, iterations, hash.length, function(err, hashed) {
if (err) {
return callback(err);
}
var hashedBase64 = new Buffer(hashed, 'binary').toString('base64');
var isValid = fixedTimeComparison(hash.toString('base64'), hashedBase64);
return callback(null, isValid);
});
}
}
After upgrading my Auth0 tenant to the Node 8 runtime, the above script stopped working. My logs show the following exception.
{
"code": 500,
"error": "Script generated an unhandled asynchronous exception.",
"details": "TypeError: The \"digest\" argument is required and must not be undefined",
"name": "TypeError",
"message": "The \"digest\" argument is required and must not be undefined",
"stack": "TypeError: The \"digest\" argument is required and must not be undefined\n at pbkdf2 (crypto.js:694:11)\n at Object.exports.pbkdf2 (crypto.js:682:10)\n at validatePassword (/data/io/46303d62-70ee-4b5b-88ea-dc5f46963494/webtask.js:138:12)\n at Request.userCallback (/data/io/46303d62-70ee-4b5b-88ea-dc5f46963494/webtask.js:68:9)\n at Request.callback (/data/_verquire/tedious/1.11.0/node_modules/tedious/lib/request.js:30:27)\n at Connection.message (/data/_verquire/tedious/1.11.0/node_modules/tedious/lib/connection.js:283:29)\n at Connection.dispatchEvent (/data/_verquire/tedious/1.11.0/node_modules/tedious/lib/connection.js:752:59)\n at MessageIO.<anonymous> (/data/_verquire/tedious/1.11.0/node_modules/tedious/lib/connection.js:685:22)\n at emitNone (events.js:106:13)\n at MessageIO.emit (events.js:208:7)"
}
I’ve tried using later versions of the “tedious” package, but then I get errors saying that the script cannot find those versions. What needs to be updated in this script in order for it to run in Node 8?