From 0975e092882bfeb32a9bf57e0942bdf645438b98 Mon Sep 17 00:00:00 2001 From: Or Kaplan Date: Sun, 12 Feb 2012 12:41:07 +0200 Subject: [PATCH 1/2] fixes: windows live oauth, fix multi instance oauth2 bug, add seq.values to moduleErrback arguments --- README.md | 55 ++++++++++++++++++++++++++++ example/conf.js | 4 +++ example/server.js | 10 ++++++ example/views/home.jade | 6 ++++ lib/modules/live.js | 77 ++++++++++++++++++++++++++++++++++++++++ lib/modules/oauth2.js | 8 +++-- lib/step.js | 9 +++-- media/live.ico | Bin 0 -> 1150 bytes 8 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 lib/modules/live.js create mode 100644 media/live.ico diff --git a/README.md b/README.md index de2a96ef..b97b76ad 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ So far, `everyauth` enables you to login via: Google Google Hybrid RocketLabs Development LinkedIn + Windows Live Dropbox Torgeir Tumblr Evernote Danny Amey @@ -816,6 +817,60 @@ object whose parameter name keys map to description values: everyauth.linkedin.configurable(); ``` +## Setting up Windows Live OAuth + +```javascript +var everyauth = require('everyauth') + , connect = require('connect'); + +everyauth.live + .appId('YOUR CLIENT ID HERE') + .appSecret('YOUR CLIENT SECRET HERE') + .findOrCreateUser( function (session, accessToken, accessTokenExtra, liveUserMetadata) { + // find or create user logic goes here + }) + .redirectPath('/'); + +var routes = function (app) { + // Define your routes here +}; + +connect( + connect.bodyParser() + , connect.cookieParser() + , connect.session({secret: 'whodunnit'}) + , everyauth.middleware() + , connect.router(routes); +).listen(3000); +``` + +You can also configure more parameters (most are set to defaults) via +the same chainable API: + +```javascript +everyauth.live + .entryPath('/auth/live') + .callbackPath('/auth/live/callback'); +``` + +If you want to see what the current value of a +configured parameter is, you can do so via: + +```javascript +everyauth.live.callbackPath(); // '/auth/live/callback' +everyauth.live.entryPath(); // '/auth/live' +``` + +To see all parameters that are configurable, the following will return an +object whose parameter name keys map to description values: + +```javascript +everyauth.live.configurable(); +``` + +To run the Windows Live sample please run access the server through the url local.hosti:3000, +since Live limits the apps by one app per domain and local.host was taken. + ## Setting up Google OAuth2 ```javascript diff --git a/example/conf.js b/example/conf.js index f276df16..d1492963 100644 --- a/example/conf.js +++ b/example/conf.js @@ -27,6 +27,10 @@ module.exports = { apiKey: 'pv6AWspODUeHIPNZfA531OYcFyB1v23u3y-KIADJdpyw54BXh-ciiQnduWf6FNRH' , apiSecret: 'Pdx7DCoJRdAk0ai3joXsslZvK1DPCQwsLn-T17Opkae22ZYDP5R7gmAoFes9TNHy' } + , live: { + apiKey: '0000000048088A51' + , apiSecret: 'z5zxAbjIgFMlT4lyeuu5HFJodwku5XGs' + } , google: { clientId: '3335216477.apps.googleusercontent.com' , clientSecret: 'PJMW_uP39nogdu0WpBuqMhtB' diff --git a/example/server.js b/example/server.js index 649bcb8b..8fa8ca12 100644 --- a/example/server.js +++ b/example/server.js @@ -32,6 +32,7 @@ var usersByInstagramId = {}; var usersByFoursquareId = {}; var usersByGowallaId = {}; var usersByLinkedinId = {}; +var usersByLiveId = {}; var usersByGoogleId = {}; var usersByAngelListId = {}; var usersByYahooId = {}; @@ -202,6 +203,14 @@ everyauth.linkedin return usersByLinkedinId[linkedinUser.id] || (usersByLinkedinId[linkedinUser.id] = addUser('linkedin', linkedinUser)); }) .redirectPath('/'); + + everyauth.live + .appId(conf.live.apiKey) + .appSecret(conf.live.apiSecret) + .findOrCreateUser( function (sess, accessToken, accessSecret, liveUser) { + return usersByLiveId[liveUser.id] || (usersByLiveId[liveUser.id] = addUser('live', liveUser)); + }) + .redirectPath('/'); everyauth.google .appId(conf.google.clientId) @@ -370,3 +379,4 @@ everyauth.helpExpress(app); app.listen(3000); console.log('Go to http://local.host:3000'); +console.log('For live Go to http://local.hosti:3000'); diff --git a/example/views/home.jade b/example/views/home.jade index bee0aff3..c09db138 100644 --- a/example/views/home.jade +++ b/example/views/home.jade @@ -29,6 +29,9 @@ #linkedin-login a(href='/auth/linkedin', style='border: 0px') img(style='border: 0px', src='http://press.linkedin.com/sites/all/themes/presslinkedin/images/LinkedIn_WebLogo_LowResExample.jpg') + #windows-live-login + a(href='/auth/live', style='border: 0px') + img(style='border: 0px', src='http://upload.wikimedia.org/wikipedia/en/thumb/5/50/Windows_Live_logo.svg/300px-Windows_Live_logo.svg.png') #google-login a(href='/auth/google', style='border: 0px') img(style='border: 0px', src='https://www.google.com/favicon.ico') @@ -104,6 +107,9 @@ - if (everyauth.linkedin) h3 LinkedIn User Data p= JSON.stringify(everyauth.linkedin.user) + - if (everyauth.live) + h3 Windows Live User Data + p= JSON.stringify(everyauth.live.user) - if (everyauth.google) h3 Google User Data p= JSON.stringify(everyauth.google.user) diff --git a/lib/modules/live.js b/lib/modules/live.js new file mode 100644 index 00000000..4bf0f27a --- /dev/null +++ b/lib/modules/live.js @@ -0,0 +1,77 @@ +var oauthModule = require('./oauth2') + , url = require('url'); + +var fb = module.exports = + oauthModule.submodule('live') + .configurable({ + scope: 'specify types of access: http://msdn.microsoft.com/en-us/library/hh243646(en-us).aspx', + display: 'The display type to be used for the authorization page. Valid values are "popup", "touch", "page", or "none".', + locale: 'Optional. A market string that determines how the consent UI is localized. If the value of this parameter is missing or is not valid, a market value is determined by using an internal algorithm.' + }) + + .apiHost('https://apis.live.net/v5.0') + .oauthHost('https://oauth.live.com') + + .authPath('https://oauth.live.com/authorize') + + .entryPath('/auth/live') + .accessTokenHttpMethod('get') + .accessTokenPath('/token') + .callbackPath('/auth/live/callback') + + .scope('wl.signin') + .display('page') + + .authQueryParam('scope', function () { + return this._scope && this.scope(); + }) + + .authQueryParam('response_type', function () { + return 'code'; + }) + + .accessTokenParam('grant_type', function () { + return 'authorization_code'; + }) + + .authQueryParam('display', function () { + return this._display && this.display(); + }) + + .authCallbackDidErr( function (req) { + var parsedUrl = url.parse(req.url, true); + return parsedUrl.query && !!parsedUrl.query.error; + }) + + .handleAuthCallbackError( function (req, res) { + var parsedUrl = url.parse(req.url, true) + , errorDesc = parsedUrl.query.error_description; + if (res.render) { + res.render(__dirname + '/../views/auth-fail.jade', { + errorDescription: errorDesc + }); + } else { + // TODO Replace this with a nice fallback + throw new Error("You must configure handleAuthCallbackError if you are not using express"); + } + }) + + .fetchOAuthUser( function (accessToken) { + var p = this.Promise(); + this.oauth.get(this.apiHost() + '/me', accessToken, function (err, data) { + if (err) + return p.fail(err); + var oauthUser = JSON.parse(data); + p.fulfill(oauthUser); + }) + return p; + }) + + .convertErr( function (data) { + if (typeof data == 'string') + return new Error(JSON.parse(data.data).error.message); + if (data) + return new Error(data.error + ' - ' + data.error_description); + else + return new Error('unknown error'); + }); diff --git a/lib/modules/oauth2.js b/lib/modules/oauth2.js index c964a12f..74abb725 100644 --- a/lib/modules/oauth2.js +++ b/lib/modules/oauth2.js @@ -55,7 +55,7 @@ everyModule.submodule('oauth2') .promises('code') .canBreakTo('authCallbackErrorSteps') .step('getAccessToken') - .accepts('code') + .accepts('req code') .promises('accessToken extra') .step('fetchOAuthUser') .accepts('accessToken') @@ -134,7 +134,11 @@ everyModule.submodule('oauth2') } return parsedUrl.query && parsedUrl.query.code; }) - .getAccessToken( function (code) { + .getAccessToken( function (req, code) { + // Automatic hostname detection + assignment + if (!this._myHostname || this._alwaysDetectHostname) { + this.myHostname(extractHostname(req)); + } var p = this.Promise() , params = { client_id: this.appId() diff --git a/lib/step.js b/lib/step.js index 8fde9e04..e1af24b4 100644 --- a/lib/step.js +++ b/lib/step.js @@ -64,13 +64,13 @@ Step.prototype = { if (promises && promises.length && 'undefined' === typeof ret) { - // TODO Scope this fn errorCallback( new Error('Step ' + this.name + ' of `' + _module.name + '` is promising: ' + promises.join(', ') + ' ; however, the step returns nothing. ' + 'Fix the step by returning the expected values OR ' + - 'by returning a Promise that promises said values.') + 'by returning a Promise that promises said values.'), + seq.values ); } // Convert return value into a Promise @@ -99,14 +99,13 @@ Step.prototype = { } else if ('string' === typeof err) { err = new Error(err); } - return oldFn.call(this, err); + return oldFn.call(this, err, scope); }; return oldErrback.call(this, fn, scope); }; } - // TODO Scope this fn -- i.e., errorCallback? - ret.errback(errorCallback); + ret.errback(errorCallback, seq.values); ret.callback( function () { // Store the returned values diff --git a/media/live.ico b/media/live.ico new file mode 100644 index 0000000000000000000000000000000000000000..2376bb8b3f4b254c84467c869a57eafd76c73bcb GIT binary patch literal 1150 zcmdUv|5MCc7{^Z|l(ly4QZ1Um9>-Yi!ZySnv@v1+%76sNf$Mk>yq31p3QuIyZ^v_&b-d^oM%4o=R9Z5oDU&Z zba1(Z&O#DlO^6*K#En8zH1i`*-Kg@-blv*2J;qj1mZb1uFi zp#nSmaZLYf=L^O&G2^qMg6-DiRBUBcjeuPXu&)EGOMrC|AUN^)FR%GO`!nBOigi7@ zbMg4n5Fdw#$k2s~f*4V5d$Nr=rzB79P=zmUEr6p5m{JW)DxU9hrlE|{*JlTM)<%vQZipv% zl123e?)alninljn9&Bp(r`)>=hca7?XzAC&-v(1fmB1nchTO~1DXRG1>RI`z-ucS6?zMXorA!ZvNrJqN z!*uU_Cz#7NW8h{yy3WgStL=8NeJLIW*YzJyxmUeE@y?kX#l_TuRhsyd%fl3YjJ`19 zCvmlkrGKoM`qq5L@AJU54Ie%>r`CMvdnrk8xuINLsYFOk8UhR{5MNWmxpWJB_6Ih( z&73uQcN1lQ+6v`{{EfRZGqSU?_oZd&RdUtudPzcWSIOhHLu;<9vAewjLB>3I*J_|XHHk~Rol(hxk99j#%-DVZL|49vOw zYq$91HobdFfZj>z)Hyf7yPwf>ea+S^x3;IY^B#b+@;N4Nehu#SH{h@DhHpe{l)Uap zqml*jOH#I*A`5edmj@PwExXp)?F@9n=HSC|1^LQ d3nOC)NuUr0D;iS{TA(3=k;bD;B&0_Q{0rs%oqqrT literal 0 HcmV?d00001 From 524c028d2eadb97ee478112c9990ba01a042962d Mon Sep 17 00:00:00 2001 From: Or Kaplan Date: Wed, 22 Feb 2012 13:34:39 +0200 Subject: [PATCH 2/2] rename live into windows live - everyauth module and files --- README.md | 20 +++++++++++--------- example/conf.js | 2 +- example/server.js | 14 +++++++------- example/views/home.jade | 6 +++--- lib/modules/{live.js => windowsLive.js} | 6 +++--- media/{live.ico => windows_live.ico} | Bin 6 files changed, 25 insertions(+), 23 deletions(-) rename lib/modules/{live.js => windowsLive.js} (95%) rename media/{live.ico => windows_live.ico} (100%) diff --git a/README.md b/README.md index b97b76ad..2492a0e6 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ So far, `everyauth` enables you to login via: Google Google Hybrid RocketLabs Development LinkedIn - Windows Live + Windows Live Dropbox Torgeir Tumblr Evernote Danny Amey @@ -823,7 +823,7 @@ everyauth.linkedin.configurable(); var everyauth = require('everyauth') , connect = require('connect'); -everyauth.live +everyauth.windowsLive .appId('YOUR CLIENT ID HERE') .appSecret('YOUR CLIENT SECRET HERE') .findOrCreateUser( function (session, accessToken, accessTokenExtra, liveUserMetadata) { @@ -848,28 +848,28 @@ You can also configure more parameters (most are set to defaults) via the same chainable API: ```javascript -everyauth.live - .entryPath('/auth/live') - .callbackPath('/auth/live/callback'); +everyauth.windowsLive + .entryPath('/auth/windowslive') + .callbackPath('/auth/windowslive/callback'); ``` If you want to see what the current value of a configured parameter is, you can do so via: ```javascript -everyauth.live.callbackPath(); // '/auth/live/callback' -everyauth.live.entryPath(); // '/auth/live' +everyauth.windowsLive.callbackPath(); // '/auth/windowslive/callback' +everyauth.windowsLive.entryPath(); // '/auth/windowslive' ``` To see all parameters that are configurable, the following will return an object whose parameter name keys map to description values: ```javascript -everyauth.live.configurable(); +everyauth.windowsLive.configurable(); ``` To run the Windows Live sample please run access the server through the url local.hosti:3000, -since Live limits the apps by one app per domain and local.host was taken. +since Windows Live limits the apps by one app per domain and local.host was taken. ## Setting up Google OAuth2 @@ -2292,6 +2292,8 @@ Thanks to the following contributors for the following modules: - VKontakte - [Rodolphe Stoclin](https://github.com/srod) - Skyrock +- [Or Kaplan](https://github.com/orkaplan) + - Windows Live ### MIT License Copyright (c) 2011 by Brian Noguchi diff --git a/example/conf.js b/example/conf.js index d1492963..64e77e22 100644 --- a/example/conf.js +++ b/example/conf.js @@ -27,7 +27,7 @@ module.exports = { apiKey: 'pv6AWspODUeHIPNZfA531OYcFyB1v23u3y-KIADJdpyw54BXh-ciiQnduWf6FNRH' , apiSecret: 'Pdx7DCoJRdAk0ai3joXsslZvK1DPCQwsLn-T17Opkae22ZYDP5R7gmAoFes9TNHy' } - , live: { + , windowsLive: { apiKey: '0000000048088A51' , apiSecret: 'z5zxAbjIgFMlT4lyeuu5HFJodwku5XGs' } diff --git a/example/server.js b/example/server.js index 8fa8ca12..270e7f1e 100644 --- a/example/server.js +++ b/example/server.js @@ -32,7 +32,7 @@ var usersByInstagramId = {}; var usersByFoursquareId = {}; var usersByGowallaId = {}; var usersByLinkedinId = {}; -var usersByLiveId = {}; +var usersByWindowsLiveId = {}; var usersByGoogleId = {}; var usersByAngelListId = {}; var usersByYahooId = {}; @@ -204,11 +204,11 @@ everyauth.linkedin }) .redirectPath('/'); - everyauth.live - .appId(conf.live.apiKey) - .appSecret(conf.live.apiSecret) - .findOrCreateUser( function (sess, accessToken, accessSecret, liveUser) { - return usersByLiveId[liveUser.id] || (usersByLiveId[liveUser.id] = addUser('live', liveUser)); + everyauth.windowsLive + .appId(conf.windowsLive.apiKey) + .appSecret(conf.windowsLive.apiSecret) + .findOrCreateUser( function (sess, accessToken, accessSecret, windowsLiveUser) { + return usersByWindowsLiveId[windowsLiveUser.id] || (usersByWindowsLiveId[windowsLiveUser.id] = addUser('windowsLive', windowsLiveUser)); }) .redirectPath('/'); @@ -379,4 +379,4 @@ everyauth.helpExpress(app); app.listen(3000); console.log('Go to http://local.host:3000'); -console.log('For live Go to http://local.hosti:3000'); +console.log('For Windows Live Go to http://local.hosti:3000'); diff --git a/example/views/home.jade b/example/views/home.jade index c09db138..eadc8810 100644 --- a/example/views/home.jade +++ b/example/views/home.jade @@ -30,7 +30,7 @@ a(href='/auth/linkedin', style='border: 0px') img(style='border: 0px', src='http://press.linkedin.com/sites/all/themes/presslinkedin/images/LinkedIn_WebLogo_LowResExample.jpg') #windows-live-login - a(href='/auth/live', style='border: 0px') + a(href='/auth/windowslive', style='border: 0px') img(style='border: 0px', src='http://upload.wikimedia.org/wikipedia/en/thumb/5/50/Windows_Live_logo.svg/300px-Windows_Live_logo.svg.png') #google-login a(href='/auth/google', style='border: 0px') @@ -107,9 +107,9 @@ - if (everyauth.linkedin) h3 LinkedIn User Data p= JSON.stringify(everyauth.linkedin.user) - - if (everyauth.live) + - if (everyauth.windowsLive) h3 Windows Live User Data - p= JSON.stringify(everyauth.live.user) + p= JSON.stringify(everyauth.windowsLive.user) - if (everyauth.google) h3 Google User Data p= JSON.stringify(everyauth.google.user) diff --git a/lib/modules/live.js b/lib/modules/windowsLive.js similarity index 95% rename from lib/modules/live.js rename to lib/modules/windowsLive.js index 4bf0f27a..5dd468cc 100644 --- a/lib/modules/live.js +++ b/lib/modules/windowsLive.js @@ -2,7 +2,7 @@ var oauthModule = require('./oauth2') , url = require('url'); var fb = module.exports = - oauthModule.submodule('live') + oauthModule.submodule('windowsLive') .configurable({ scope: 'specify types of access: http://msdn.microsoft.com/en-us/library/hh243646(en-us).aspx', display: 'The display type to be used for the authorization page. Valid values are "popup", "touch", "page", or "none".', @@ -14,10 +14,10 @@ var fb = module.exports = .authPath('https://oauth.live.com/authorize') - .entryPath('/auth/live') + .entryPath('/auth/windowslive') .accessTokenHttpMethod('get') .accessTokenPath('/token') - .callbackPath('/auth/live/callback') + .callbackPath('/auth/windowslive/callback') .scope('wl.signin') .display('page') diff --git a/media/live.ico b/media/windows_live.ico similarity index 100% rename from media/live.ico rename to media/windows_live.ico