From c70bc5c34d8c0941386c121612fd85070e8cee81 Mon Sep 17 00:00:00 2001 From: Damian Krzeminski Date: Sun, 15 Apr 2012 15:41:10 -0700 Subject: [PATCH 1/5] Add isMobile configurable Applications can configure isMobile function, which should be called by authorization modules whenever they need to establish if mobile vs. full login pages need to be rendered. Most oauth/oauth2 providers are capable of disvoring mobile browsers without any external help. But some applications may prefer to force a specific version (for example based on user preferences). isMobile determination can be done on a per request basis (request is passed as a parameter) --- lib/modules/everymodule.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/modules/everymodule.js b/lib/modules/everymodule.js index 32ec5bd6..b3e0be3a 100644 --- a/lib/modules/everymodule.js +++ b/lib/modules/everymodule.js @@ -33,6 +33,8 @@ function EveryModule () { , findUserById: 'function for fetching a user by his/her id -- used to assign to `req.user` - function ( [req], userId, callback) where function callback (err, user)' , performRedirect: 'function for redirecting responses' , userPkey: 'identifying property of the user; defaults to "id"' + , isMobile: 'function used to determine if request is performed by a mobile browser - ' + + 'function(req) - truthy return value should be used by submodule to redirect to mobile version of login pages' }) .get('logoutPath') .step('handleLogout') @@ -51,6 +53,10 @@ function EveryModule () { res.end(); }); + this.isMobile( function() { + return false; + }); + this.moduleTimeout(10000); this.moduleErrback( function (err, seqValues) { if (! (err instanceof Error)) { From 9ea3dfce4986c46a28aeebf8f347103d00deed7d Mon Sep 17 00:00:00 2001 From: Damian Krzeminski Date: Sun, 15 Apr 2012 15:51:13 -0700 Subject: [PATCH 2/5] Extract buildAuthorizePath method authorizePath is used to redirect user to authorization provider's login screen Extracting building this path into a separate configurable function allows module developers to overwrite default everyauth behavior. It's needed to support some quirky oauth providers that do not follow typical patterns. It's also helpful if provider requires a different URLs for mobile and full version of login screens. Existing modules do not have to be changed unless (or until) they want to use this new functionality. --- lib/modules/oauth.js | 13 +++++++++---- lib/modules/oauth2.js | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/modules/oauth.js b/lib/modules/oauth.js index b35491cb..7bc06657 100644 --- a/lib/modules/oauth.js +++ b/lib/modules/oauth.js @@ -19,6 +19,8 @@ everyModule.submodule('oauth') , redirectPath: 'Where to redirect to after a failed or successful OAuth authorization' , convertErr: '(DEPRECATED) a function (data) that extracts an error message from data arg, where `data` is what is returned from a failed OAuth request' , authCallbackDidErr: 'Define the condition for the auth module determining if the auth callback url denotes a failure. Returns true/false.' + , buildAuthorizePath: 'function that returns the host and path to which you direct a visitor to login ' + + 'function(isMobile) - authorizePath is automatically appended' }) .definit( function () { this.oauth = new OAuth( @@ -41,7 +43,7 @@ everyModule.submodule('oauth') .promises(null) .step('redirectToProviderAuth') .description('sends the user to authorization on the OAuth provider site') - .accepts('res token') + .accepts('req res token') .promises(null) .get('callbackPath', @@ -142,12 +144,12 @@ everyModule.submodule('oauth') _provider.token = token; _provider.tokenSecret = tokenSecret; }) - .redirectToProviderAuth( function (res, token) { + .redirectToProviderAuth( function (req, res, token) { // Note: Not all oauth modules need oauth_callback as a uri query parameter. As far as I know, only readability's // module needs it as a uri query parameter. However, in cases such as twitter, it allows you to over-ride // the callback url settings at dev.twitter.com from one place, your app code, rather than in two places -- i.e., // your app code + dev.twitter.com app settings. - var redirectTo = this._oauthHost + this._authorizePath + '?oauth_token=' + token; + var redirectTo = this._buildAuthorizePath(this._isMobile(req)) + '?oauth_token=' + token; if (this._sendCallbackWithAuthorize) { redirectTo += '&oauth_callback=' + this._myHostname + this._callbackPath; } @@ -255,7 +257,10 @@ oauth .handleAuthCallbackError( function (req, res, next) { next(new Error("You must configure handleAuthCallbackError in this module")); }) - .sendCallbackWithAuthorize(true); + .sendCallbackWithAuthorize(true) + .buildAuthorizePath(function() { + return this._oauthHost + this._authorizePath; + }); oauth.moreRequestTokenQueryParams = {}; oauth.cloneOnSubmodule.push('moreRequestTokenQueryParams'); diff --git a/lib/modules/oauth2.js b/lib/modules/oauth2.js index 0cc02834..7ce04792 100644 --- a/lib/modules/oauth2.js +++ b/lib/modules/oauth2.js @@ -19,9 +19,9 @@ everyModule.submodule('oauth2') , oauthHost: 'the host for the OAuth provider' , appId: 'the OAuth app id provided by the host' , appSecret: 'the OAuth secret provided by the host' - , authPath: "the path on the OAuth provider's domain where " + + , authPath: "the path on the OAuth provider's domain where " + "we direct the user for authentication, e.g., /oauth/authorize" - , accessTokenPath: "the path on the OAuth provider's domain " + + , accessTokenPath: "the path on the OAuth provider's domain " + "where we request the access token, e.g., /oauth/access_token" , accessTokenHttpMethod: 'the http method ("get" or "post") with which to make our access token request' , customHeaders: 'any cusomt headers required in the access token request' @@ -30,8 +30,11 @@ everyModule.submodule('oauth2') 'the access token endpoint in the request body' , myHostname: 'e.g., http://local.host:3000 . Notice no trailing slash' , alwaysDetectHostname: 'does not cache myHostname once. Instead, re-detect it on every request. Good for multiple subdomain architectures' + , redirectPath: 'Where to redirect to after a failed or successful OAuth authorization' , convertErr: '(DEPRECATED) a function (data) that extracts an error message from data arg, where `data` is what is returned from a failed OAuth request' , authCallbackDidErr: 'Define the condition for the auth module determining if the auth callback url denotes a failure. Returns true/false.' + , buildAuthorizePath: 'function that returns the host and path to which you direct a visitor to login ' + + 'function(isMobile) - authPath is automatically appended' }) // Declares a GET route that is aliased @@ -94,10 +97,7 @@ everyModule.submodule('oauth2') client_id: this._appId , redirect_uri: this._myHostname + this._callbackPath } - , authPath = this._authPath - , url = (/^http/.test(authPath)) - ? authPath - : (this._oauthHost + authPath) + , url = this._buildAuthorizePath(this._isMobile(req)) , additionalParams = this.moreAuthQueryParams , param; @@ -269,6 +269,13 @@ oauth2 .handleAuthCallbackError( function (req, res, next) { next(Error("You must configure handleAuthCallbackError in this module")); }) + .buildAuthorizePath( function() { + // by default we ignore isMobile value + var authPath = this._authPath; + return (/^http/.test(authPath)) + ? authPath + : (this._oauthHost + authPath); + }); // Add or over-write existing query params that // get tacked onto the oauth authorize url. From 7bd162869aeb570cb2c2d8900f954a51a8e3af75 Mon Sep 17 00:00:00 2001 From: Damian Krzeminski Date: Sun, 15 Apr 2012 16:09:35 -0700 Subject: [PATCH 3/5] tripit: display proper version of login screen Implemented buildAuthorizeApp to redirect user either to full or mobile version of tripit login screen Tripit does not recognize browser type automatically --- lib/modules/tripit.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/modules/tripit.js b/lib/modules/tripit.js index 94a6a8ef..827061aa 100644 --- a/lib/modules/tripit.js +++ b/lib/modules/tripit.js @@ -7,12 +7,9 @@ oauthModule.submodule('tripit') .oauthHost('https://api.tripit.com') .entryPath('/auth/tripit') .callbackPath('/auth/tripit/callback') - .redirectToProviderAuth( function (res, token) { - var redirectTo = 'https://www.tripit.com' + this.authorizePath() + '?oauth_token=' + token; - if (this.sendCallbackWithAuthorize()) { - redirectTo += '&oauth_callback=' + this.myHostname() + this.callbackPath(); - } - this.redirect(res, redirectTo); + .buildAuthorizePath( function (isMobile) { + var host = isMobile ? 'https://m.tripit.com' : 'https://www.tripit.com'; + return host + this.authorizePath(); }) .fetchOAuthUser( function (accessToken, accessTokenSecret, params) { var promise = this.Promise(); From 39f8489b8df9847be763d159bb56f9b605e3a096 Mon Sep 17 00:00:00 2001 From: Damian Krzeminski Date: Sun, 15 Apr 2012 16:12:18 -0700 Subject: [PATCH 4/5] facebook: display proper type of login screen Redirect user to full or mobile version of the application based on the value returned by the isMobile configurable. Older, facebook specific, functionality of forcing mobile/full version by calling 'mobile' function is still supported. --- lib/modules/facebook.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/modules/facebook.js b/lib/modules/facebook.js index 9857cfee..92e71cd0 100644 --- a/lib/modules/facebook.js +++ b/lib/modules/facebook.js @@ -11,7 +11,7 @@ oauthModule.submodule('facebook') .apiHost('https://graph.facebook.com/v2.0') .oauthHost('https://graph.facebook.com/v2.0') - .authPath('https://www.facebook.com/v2.0/dialog/oauth') + .authPath('/dialog/oauth') .entryPath('/auth/facebook') .callbackPath('/auth/facebook/callback') @@ -20,6 +20,11 @@ oauthModule.submodule('facebook') return this._scope && this.scope(); }) + .buildAuthorizePath( function (isMobile) { + var host = isMobile ? 'https://m.facebook.com/v2.0' : 'https://www.facebook.com/v2.0'; + return host + this.authPath(); + }) + .authCallbackDidErr( function (req) { var parsedUrl = url.parse(req.url, true); return parsedUrl.query && !!parsedUrl.query.error; @@ -72,9 +77,11 @@ oauthModule.submodule('facebook') }); fb.mobile = function (isMobile) { - if (isMobile) { - this.authPath('https://m.facebook.com/v2.0/dialog/oauth'); - } + // backward compatibility only + // it's better if application define isMobile function to handle this on a per request basis + this.isMobile(function() { + return isMobile; + }); return this; }; From ec38d792678aac550b6fba5b2b409b93b559b2c0 Mon Sep 17 00:00:00 2001 From: Damian Krzeminski Date: Thu, 12 Sep 2013 12:34:19 -0400 Subject: [PATCH 5/5] linkedin: display mobile login screen on demand --- lib/modules/linkedin.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/modules/linkedin.js b/lib/modules/linkedin.js index 85b933d8..fd323ea5 100644 --- a/lib/modules/linkedin.js +++ b/lib/modules/linkedin.js @@ -32,8 +32,9 @@ oauthModule.submodule('linkedin') .entryPath('/auth/linkedin') .callbackPath('/auth/linkedin/callback') - .redirectToProviderAuth( function (res, token) { - this.redirect(res, 'https://www.linkedin.com' + this.authorizePath() + '?oauth_token=' + token); + .buildAuthorizePath( function (isMobile) { + var host = isMobile ? 'http://touch.www.linkedin.com' : 'https://www.linkedin.com'; + return host + this.authorizePath(); }) .fetchOAuthUser( function (accessToken, accessTokenSecret, params) {