Skip to content

Commit

Permalink
AuthHelper for basic authentication added.
Browse files Browse the repository at this point in the history
  • Loading branch information
gevorg committed Jan 25, 2013
1 parent b756e2b commit 3cbe3a5
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 55 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ http.createServer(function(req, res) {
## Configurations

- `authRealm` - Authentication realm.
- `authHelper` - Function that allows to override standard authentication method by providing custom user loading mechanism. Works only with digest authentication.
- `authHelper` - Function that allows to override standard authentication method by providing custom user loading mechanism.
- `authFile` - File where user details are stored in format **{user:pass}** or **{user:passHash}** for basic access and **{user:realm:passHash}** for digest access.
- `authList` - List where user details are stored in format **{user:pass}** or **{user:passHash}** for basic access and **{user:realm:passHash}** for digest access, ignored if `authFile` is specified.
- `authType` - Type of authentication, may be **basic** or **digest**, optional, default is **basic**.
Expand Down
8 changes: 4 additions & 4 deletions examples/example_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,25 @@ var http = require('http');
/**
* Requesting new authentication instance.
*/
var digest = auth({
var basic = auth({
authRealm : "Private area.",
// username is mia, password is supergirl.
authHelper : function(user, callback) {
if ( user === 'mia' ) {
callback('mia:Private area.:3a556dc7260e8e7f032d247fb668b06b');
callback('{SHA}x511ncXd+4fOnYAotcGPFD0peYo=');
} else {
callback();
}
},
authType : 'digest'
authType : 'basic'
});

/**
* Creating new HTTP server.
*/
http.createServer(function(req, res) {
// Apply authentication to server.
digest.apply(req, res, function(username) {
basic.apply(req, res, function(username) {
res.end("Welcome to private area - " + username + "!");
});
}).listen(1337);
Expand Down
101 changes: 67 additions & 34 deletions lib/auth/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ module.exports = Basic;
* @param {String} authRealm authentication realm.
* @param {Array} authUsers array of users.
*/
function Basic(authRealm, authUsers) {
function Basic(authRealm, authUsers, authHelper) {
// Realm.
this.realm = authRealm;
// Users.
this.users = authUsers;

// Authentication helper.
this.authHelper = authHelper;

// Used for async callback.
var self = this;

Expand All @@ -41,23 +43,23 @@ function Basic(authRealm, authUsers) {
* @param {Function} next function that will be called after user is authenticated.
*/
this.apply = function(request, response, next) {
var authenticated = self.isAuthenticated(request);
if(!authenticated) {
self.ask(response);
} else {
next(authenticated);
}
self.isAuthenticated(request, function(authenticated) {
if(!authenticated) {
self.ask(response);
} else {
next(authenticated);
}
});
}
};

/**
* Checks authorization header in request.
*
* @param {Request} request HTTP request object.
* @return {Boolean} true if is authenticated, else false.
* @return {String} the authenticated user ID, if authenticated, else undefined.
* @param {Function} callback after authentication is finished.
*/
Basic.prototype.isAuthenticated = function(request) {
Basic.prototype.isAuthenticated = function(request, callback) {
var authenticated = undefined;

// If header exists.
Expand All @@ -70,35 +72,66 @@ Basic.prototype.isAuthenticated = function(request) {
}

if (authHeader) {
var header = request.headers.authorization;
var header = authHeader;
var clientUserString = header.split(" ")[1];
var clientUser = utils.decodeBase64(clientUserString).split(":");
var clientUserName = clientUser[0];
var clientPasswordHash = clientUser[1];
var authUsers = this.users;
var validateClientString = this.validateClientString;

// Searching for user in user list.
if(clientUserString) {
var clientUser = utils.decodeBase64(clientUserString).split(":");
var clientUserName = clientUser[0];
var clientPasswordHash = clientUser[1];
// Searching for user in user list.
if (clientUserString) {
if (this.authHelper) {
this.authHelper(clientUserName, function(passwordHash) {
if ( passwordHash && htpasswd.validate(passwordHash, clientPasswordHash) ) {
callback(clientUserName);
} else {
callback();
}
});
} else {
callback(validateClientString(clientUserName, clientPasswordHash, authUsers));
}
} else {
callback();
}
} else {
callback();
}

if(clientUserName && clientPasswordHash) {
for(var i = 0; i < this.users.length; ++i) {
var myUser = this.users[i].split(":");
var myUserName = myUser[0];
var myPasswordHash = myUser[1];
return authenticated;
};

// Ensure the username and password both match.
if(myUserName === clientUserName) {
if(htpasswd.validate(myPasswordHash, clientPasswordHash)) {
authenticated = myUserName;
break;
}
}
}
}
}
}
/**
* Validates client username and client password hash.
*
* @param {String} username of client.
* @param {String} password hash of client.
* @return {String} the authenticated user ID, if authentication is successful, else undefined.
*/
Basic.prototype.validateClientString = function(clientUserName, clientPasswordHash, users) {
var authenticated = undefined;

return authenticated;
if (clientUserName && clientPasswordHash) {
for (var i = 0; i < users.length; ++i) {
var myUser = users[i].split(":");
var myUserName = myUser[0];
var myPasswordHash = myUser[1];

// Ensure the username and password both match.
if (myUserName === clientUserName) {
if (htpasswd.validate(myPasswordHash, clientPasswordHash)) {
authenticated = myUserName;
break;
}
}
}
}

return authenticated;
};

/**
* Asks client for authentication.
*
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/digest.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Digest.prototype.isAuthenticated = function(request, callback) {
}

if (authHeader) {
var header = request.headers.authorization;
var header = authHeader;
var co = this.parseAuthHeader(header);

// Check for expiration.
Expand Down
2 changes: 1 addition & 1 deletion lib/http-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var opt = require('./options');
*
* - authRealm authentication realm.
* - authHelper function that allows to override standard authentication method
* by providing custom user loading mechanism. Only for digest.
* by providing custom user loading mechanism.
* - authFile file where user details are stored in format {user:pass}.
* - authList list where user details are stored in format {user:pass},
* ignored if authFile is specified.
Expand Down
2 changes: 1 addition & 1 deletion lib/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = {
if(options && options.authType == 'digest') {
return new Digest(options.authRealm, options.authUsers, options.algorithm, options.authHelper);
} else if(options && options.authType == 'basic') {
return new Basic(options.authRealm, options.authUsers);
return new Basic(options.authRealm, options.authUsers, options.authHelper);
} else {
throw new Error("Invalid type, may be digest | basic!");
}
Expand Down
26 changes: 13 additions & 13 deletions tests/auth/test-basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ exports['testIsAuthenticatedTrue'] = function(test) {
var request = {headers : {authorization : header}};

// Source method call, that must return username.
test.equals(source.isAuthenticated(request), "user", "User must be valid!");
// Test is done.
test.done();
source.isAuthenticated(request, function(authenticated) {
test.equals(authenticated, "user", "User must be valid!");
test.done();
});
};
/**
* Test for isAuthenticated, false case.
Expand All @@ -83,10 +83,10 @@ exports['testIsAuthenticatedFalse'] = function(test) {
var request = {headers : {authorization : "Basic: userhash4"}};

// Source method call, that must return false.
test.ok(!source.isAuthenticated(request), "User must not be valid!");
// Test is done.
test.done();
source.isAuthenticated(request, function(authenticated) {
test.ok(!authenticated, "User must not be valid!");
test.done();
});
};
/**
* Test for isAuthenticated, false case, where the password
Expand All @@ -98,10 +98,10 @@ exports['testIsAuthenticatedFalseSamePassword'] = function(test) {
var request = {headers : {authorization : header}};

// Source method call, that must return false.
test.ok(!source.isAuthenticated(request), "User must not be valid!");

// Test is done.
test.done();
source.isAuthenticated(request, function(authenticated) {
test.ok(!authenticated, "User must not be valid!");
test.done();
});
};
/**
* Test for apply, pass case.
Expand Down Expand Up @@ -148,4 +148,4 @@ exports['testApplyAuth'] = function(test) {
response.assert();
// Test is done.
test.done();
};
};

0 comments on commit 3cbe3a5

Please sign in to comment.