Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Improved the http/digest Authorization header parsing (still not conv…

…inced its right).

Added support for facebook's OAuth2 sign-on.
  • Loading branch information...
commit 15a8df6b2abbdf37aaf02bad765abd518a1d7297 1 parent c0379b0
@ciaranj authored
View
45 examples/app.js
@@ -4,11 +4,12 @@ var sys= require('sys');
kiwi.require('express')
require('express/plugins')
kiwi.seed('oauth')
-var OAuth= require('oauth').OAuth;
//require.paths.unshift(__dirname+ "/../lib/node-oauth/lib/")
+var OAuth= require('oauth').OAuth;
+var OAuth2= require('oauth2').OAuth2;
-global.merge(require('../lib/express/plugins/auth'));
+Object.merge(global, require('../lib/express/plugins/auth'));
var getPasswordForUserFunction= function(user, callback) {
var result;
@@ -17,11 +18,18 @@ var getPasswordForUserFunction= function(user, callback) {
}
use(Cookie)
+use(Logger)
use(Session, { lifetime: (150).seconds, reapInterval: (10).seconds })
+// N.B. TO USE the facebook strategy you must specify these values correctly for your application.
+var fbId= "";
+var fbSecret= "";
+
+
var StrategyDefinition= require('../lib/express/plugins/strategyDefinition').StrategyDefinition;
use(Auth, {strategies:{"anon": new StrategyDefinition(Anonymous),
"never": new StrategyDefinition(Never),
+ "facebook": new StrategyDefinition(Facebook, {appId : fbId, appSecret: fbSecret, scope: "email"}),
"twitter": new StrategyDefinition(Twitter, {consumerKey: "TOqGJsdtsicNz4FDSW4N5A", consumerSecret: "CN15nhsuAGQVGL3MDAzfJ3F5FFhp1ce9U4ZbaFZrSwA"}),
"http": new StrategyDefinition(Http, {getPasswordForUser: getPasswordForUserFunction}),
"basic": new StrategyDefinition(Basic, {getPasswordForUser: getPasswordForUserFunction}),
@@ -38,11 +46,26 @@ get ('/twitter', function() {
"1.0",
"HMAC-SHA1");
oa.getProtectedResource("http://twitter.com/statuses/user_timeline.xml", "GET", self.session.auth["oauth_token"], self.session.auth["oauth_token_secret"], function (error, data) {
- self.halt(200, "<html><h1>Hello! Twitter authenticated user ("+self.session.auth.user.username+")</h1>"+data+ "</html>")
+ sys.p('got protected resource ')
+ self.respond(200, "<html><h1>Hello! Twitter authenticated user ("+self.session.auth.user.username+")</h1>"+data+ "</html>")
});
}
else {
- self.halt(200, "<html><h1>Twitter authentication failed :( </h1></html>")
+ self.respond(200, "<html><h1>Twitter authentication failed :( </h1></html>")
+ }
+ });
+})
+
+get ('/facebook', function() {
+ var self=this;
+ require('sys').puts('/facebook')
+ self.authenticate(['facebook'], function(error, authenticated) {
+ if( authenticated ) {
+
+ self.respond(200, "<html><h1>Hello Facebook user:" + JSON.stringify( self.session.auth.user ) + ".</h1></html>")
+ }
+ else {
+ self.respond(200, "<html><h1>Twitter authentication failed :( </h1></html>")
}
});
})
@@ -50,14 +73,20 @@ get ('/twitter', function() {
get('/anon', function() {
var self=this;
self.authenticate(['anon'], function(error, authenticated) {
- self.halt(200, "<html><h1>Hello! Full anonymous access</h1></html>")
+ self.respond(200, "<html><h1>Hello! Full anonymous access</h1></html>")
});
})
get('/digest', function() {
var self=this;
self.authenticate(['digest'], function(error, authenticated) {
- self.halt(200, "<html><h1>Hello! My little digestive"+ self.session.auth.user.username+ "</h1>" + "<p>" + (self.session.counter++) +"</p></html>")
+ if( authenticated ) {
+ if( ! self.session.counter ) self.session.counter= 0;
+ self.respond(200, "<html><h1>Hello! My little digestive"+ self.session.auth.user.username+ "</h1>" + "<p>" + (self.session.counter++) +"</p></html>")
+ }
+ else {
+ self.respond(200, "<html><h1>should not be happening...</h1></html>")
+ }
});
})
@@ -66,10 +95,10 @@ get('/', function() {
self.authenticate(['never', 'digest', 'anon'], function(error, authenticated) {
if( authenticated ) {
if( ! self.session.counter ) self.session.counter= 0;
- self.halt(200, "<html><h1>Hello!"+ self.session.auth.user.username+ "</h1>" + "<p>" + (self.session.counter++) +"</p></html>")
+ self.respond(200, "<html><h1>Hello!"+ self.session.auth.user.username+ "</h1>" + "<p>" + (self.session.counter++) +"</p></html>")
}
else {
- self.halt(200, "<html><h1>Who are you, you seem to be un-authenticateable</h1></html>")
+ self.respond(200, "<html><h1>Who are you, you seem to be un-authenticateable</h1></html>")
}
});
})
View
21 lib/express/plugins/auth.js
@@ -1,17 +1,18 @@
-exports.merge(require('./auth.strategy.base'))
+Object.merge(exports, require('./auth.strategy.base'))
AuthStrategy= exports.AuthStrategy
BaseHttpStrategy= require('./auth.strategies/http/base').BaseHttpStrategy
-exports.merge(require('./auth.strategies/http/digest'))
-exports.merge(require('./auth.strategies/http/basic'))
-exports.merge(require('./auth.strategies/http/http'))
-exports.merge(require('./auth.strategies/anonymous'))
-exports.merge(require('./auth.strategies/never'))
-exports.merge(require('./auth.strategies/twitter'))
+Object.merge(exports, require('./auth.strategies/http/digest'))
+Object.merge(exports, require('./auth.strategies/http/basic'))
+Object.merge(exports, require('./auth.strategies/http/http'))
+Object.merge(exports, require('./auth.strategies/anonymous'))
+Object.merge(exports, require('./auth.strategies/never'))
+Object.merge(exports, require('./auth.strategies/twitter'))
+Object.merge(exports, require('./auth.strategies/facebook'))
-exports.merge(require('./strategies'))
-exports.merge(require('./strategyExecutor'))
+Object.merge(exports, require('./strategies'))
+Object.merge(exports, require('./strategyExecutor'))
Http= exports.Http
Never= exports.Never
Anonymous= exports.Anonymous
-exports.merge(require('./auth.core'))
+Object.merge(exports, require('./auth.core'))
View
7 lib/express/plugins/auth.strategies/http/base.js
@@ -6,14 +6,13 @@ exports.BaseHttpStrategy= AuthStrategy.extend({
},
_badRequest: function ( request, callback ) {
- request.halt(400, 'Bad Request');
+ request.respond(400, 'Bad Request');
this.halt(callback);
},
_unAuthenticated: function( request, callback ) {
request.header('WWW-Authenticate', this.getAuthenticateResponseHeader());
- request.halt(401, "Authorization Required");
+ request.respond(401, "Authorization Required");
this.halt(callback);
- },
-
+ }
});
View
112 lib/express/plugins/auth.strategies/http/digest.js
@@ -1,52 +1,106 @@
var md5= require('support/ext/lib/ext/md5'),
- utils = require('express/utils');
-
-var sys= require('sys');
+ BaseHttpStrategy= require('./base').BaseHttpStrategy;
exports.Digest= BaseHttpStrategy.extend({
constructor: function(options){
var options= options || {}
BaseHttpStrategy.prototype.constructor.call(this, options)
- this._realm= options.realm || "test"
+ this._realm= options.realm || "secure"
this._getPasswordForUser= options.getPasswordForUser;
},
authenticate: function(request, callback) {
var self= this;
var authHeader= request.header('Authorization');
- // Digest username="foo", realm="test", nonce="b343d03296358b5d7f985500568b", uri="/", response="52bc08c966a3b16bedb62f1b4a5b40f8"
- //TODO: parse this properly, temporary regex hack.
var isDigest= /^[D]igest\s.+"/.exec(authHeader)
- var username= /^[D]igest\susername="(.+?)"/.exec(authHeader)
- var response= /^[D]igest.+?response="(.+?)"/.exec(authHeader)
- var nonce= /^[D]igest.+?nonce="(.+?)"/.exec(authHeader)
- if( isDigest && username && username[1] && response && response[1] && nonce && nonce[1]) {
- nonce= nonce[1];
- username= username[1];
- response= response[1];
- }
- var method= request.method
- var href= request.url.href
+ if(authHeader) {
+ var credentials= this._splitAuthorizationHeader(authHeader);
+ var method= request.method
+ var href= request.url.href
- this._getPasswordForUser(username, function(error, password){
- if(error) callback(error);
- else {
- var HA1= md5.hash( username+":"+ self._realm + ":"+ password)
- var HA2= md5.hash( method+ ":" + href )
- var myResponse= md5.hash(HA1 + ":"+ nonce + ":"+ HA2 )
- if( myResponse == response ) {
- self.success({"username":username}, callback);
- }
+ this._getPasswordForUser(credentials.username, function(error, password){
+ if(error) callback(error);
else {
- self._unAuthenticated(request, callback)
+ var HA1= md5.hash( credentials.username+":"+ self._realm + ":"+ password)
+ var HA2= md5.hash( method+ ":" + href )
+ var myResponse= md5.hash(HA1 + ":"+ credentials.nonce + ":"+ HA2 )
+ if( myResponse == credentials.response ) {
+ self.success({ username : credentials.username}, callback);
+ }
+ else {
+ self._unAuthenticated(request, callback)
+ }
}
- }
- })
+ })
+ }
+ else {
+ self._unAuthenticated(request, callback)
+ }
},
getAuthenticateResponseHeader: function( request ) {
- return "Digest realm=" + this._realm + ", nonce="+ utils.uid();
+ return "Digest realm=\"" + this._realm.replace("\"","\\\"") + "\", nonce=\""+ this._getNonce(32)+"\"";
+ },
+
+ /**
+ * Given a valid Digest Authorization HTTP Header will return an object literal
+ * that contains the passed credentials.
+ *
+ * @return {object} The digest credentials, un-encoded and un-quoted.
+ * @api private
+ */
+ _splitAuthorizationHeader: function( authorizationHeader ) {
+
+ var results= {};
+
+ var parameterPairs= [];
+ var isInQuotes= false;
+ var lastStringStartingBoundary= 0;
+
+ //Need to pull off authentication type first
+ results.type= /^([a-zA-Z]+)\s/.exec(authorizationHeader)[1];
+ authorizationHeader= authorizationHeader.substring( results.type.length + 1 ) // type + 1 whitespace
+
+ for(var i=0;i< authorizationHeader.length;i++) {
+ if( authorizationHeader[i] == "\"" && authorizationHeader[i-1] != "\\" ) {
+ // WE've found an un-escaped quote (do escaped quotes exist, need to check the RFC)
+ isInQuotes= !isInQuotes;
+ }
+ if( authorizationHeader[i] == "," && !isInQuotes ) {
+ var credentialsPart= authorizationHeader.substr(lastStringStartingBoundary, (i-lastStringStartingBoundary));
+ //Strip whitespace..
+ credentialsPart= credentialsPart.replace(/^\s+|\s+$/g,'')
+
+
+ parameterPairs[parameterPairs.length]= credentialsPart;
+ lastStringStartingBoundary= i+1; // skip the comma.
+ }
+ }
+
+ // Refactor this code.
+ if( lastStringStartingBoundary < authorizationHeader.length ) {
+ var credentialsPart= authorizationHeader.substr(lastStringStartingBoundary, (authorizationHeader.length-lastStringStartingBoundary));
+ //Strip whitespace..
+ credentialsPart= credentialsPart.replace(/^\s+|\s+$/g,'')
+ parameterPairs[parameterPairs.length]= credentialsPart;
+ lastStringStartingBoundary= i+1; // skip the comma.
+ }
+
+
+ for(var key in parameterPairs) {
+ var pair= /^(.+)?=(.+)/.exec(parameterPairs[key])
+
+ //de-code quotes and un-escape inter-stitial quotes if appropriate
+ // I'm lost as to the correct behaviour of this bit tbh, the rfcs don't seem to be specifc
+ // around whether quoted strings need to quote the quotes or not!! (that I can find anyway :) )
+ var value= pair[2].replace(/^"|"$/g, '')
+ value= value.replace(/\\"/g, '"')
+
+ results[pair[1]]= value
+ }
+
+ return results;
}
});
View
5 lib/express/plugins/auth.strategies/http/http.js
@@ -65,14 +65,13 @@ exports.Http= BaseHttpStrategy.extend({
},
_badRequest: function ( request, callback ) {
- request.halt(400, 'Bad Request');
+ request.respond(400, 'Bad Request');
this.halt(callback)
},
_unAuthenticated: function( request, callback ) {
- require('sys').puts('_unAuthenticated')
request.header('WWW-Authenticate', this.getAuthenticateResponseHeader());
- request.halt(401, "Authorization Required");
+ request.respond(401, "Authorization Required");
this.halt(callback)
},
View
1  lib/express/plugins/auth.strategies/twitter.js
@@ -33,7 +33,6 @@ exports.Twitter= AuthStrategy.extend({
username: additionalParameters.screen_name }
self.executionResult.user= user;
-
// Need to sort out redirection proeprly
//TODO: sort out the redirect to original url (currently tis a mess )
request.redirect("/twitter")
View
3  spec/node.js
@@ -23,7 +23,8 @@ specs = {
'strategies',
'auth.strategies.anonymous',
'auth.strategies.never',
- 'auth.strategy.base'
+ 'auth.strategy.base',
+ 'auth.strategies.http.digest'
]
}
Please sign in to comment.
Something went wrong with that request. Please try again.