diff --git a/README.md b/README.md index c66f732b..4e6127e0 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ So far, `everyauth` enables you to login via: - `dropbox`                                    (Credits [Torgeir](https://github.com/torgeir)) - `justin.tv`                                 (Credits [slickplaid](https://github.com/slickplaid)) - `vimeo`                                 (Credits [slickplaid](https://github.com/slickplaid)) + - `netflix`                                 (Credits [slickplaid](https://github.com/slickplaid)) - OAuth2 - `facebook` - `github` @@ -1066,6 +1067,66 @@ To see all parameters that are configurable, the following will return an object everyauth.justintv.configurable(); ``` +## Setting up Netflix OAuth + +You will first need to sign up for a [developer application](http://developer.netflix.com/) to get the [application name, consumer key and secret](http://developer.netflix.com/apps/mykeys). + +```javascript +var everyauth = require('everyauth') + , connect = require('connect'); + +everyauth.netflix + .consumerKey('YOUR CONSUMER KEY HERE') + .consumerSecret('YOUR CONSUMER SECRET HERE') + .moreAuthQueryParams({ + oauth_consumer_key: 'YOUR CONSUMER KEY HERE' + , application_name: 'APPLICATION NAME HERE' + }) + .findOrCreateUser( function (sess, accessToken, accessSecret, user) { + // find or create user logic goes here + // + // e.g., + // return usersByNetflixId[user.id] || (usersByNetflixId[user.id] = user); + }) + .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.netflix + .entryPath('/auth/netflix') + .callbackPath('/auth/netflix/callback'); +``` + +If you want to see what the current value of a +configured parameter is, you can do so via: + +```javascript +everyauth.netflix.callbackPath(); // '/auth/netflix/callback' +everyauth.netflix.entryPath(); // '/auth/netflix' +``` + +To see all parameters that are configurable, the following will return an +object whose parameter name keys map to description values: + +```javascript +everyauth.netflix.configurable(); +``` + ## Setting up Vimeo OAuth You will first need to sign up for a [developer application](http://vimeo.com/api/applications) to get the consumer key and secret. @@ -1594,6 +1655,7 @@ Thanks to the following contributors for the following modules: - [slickplaid](https://github.com/slickplaid) - Justin.tv - Vimeo + - Netflix ### MIT License Copyright (c) 2011 by Brian Noguchi diff --git a/example/conf.js b/example/conf.js index 8cb54960..77aa6f60 100644 --- a/example/conf.js +++ b/example/conf.js @@ -47,6 +47,11 @@ module.exports = { consumerKey: 'Enter your consumer key here' , consumerSecret: 'Enter your consumer secret here' } + , netflix: { + consumerKey: 'Enter your consumer key here' + , consumerSecret: 'Enter your consumer secret here' + , application_name: 'Enter your application name here' + } , box: { apiKey: '5hl66lbfy0quj8qhhzcn57dflb55y4rg' } diff --git a/example/server.js b/example/server.js index 0cf32151..91e22d36 100644 --- a/example/server.js +++ b/example/server.js @@ -20,6 +20,7 @@ function addUser (source, sourceUser) { return user; } +var usersByNetflixId = {}; var usersByVimeoId = {}; var usersByJustintvId = {}; var usersByDropboxId = {}; @@ -243,6 +244,19 @@ everyauth.justintv }) .redirectPath('/') +everyauth.netflix + .consumerKey(conf.netflix.consumerKey) + .consumerSecret(conf.netflix.consumerSecret) + .moreAuthQueryParams({ + oauth_consumer_key: conf.netflix.consumerKey + , application_name: conf.netflix.application_name + }) + .findOrCreateUser( function (sess, accessToken, accessSecret, netflixUser) { + return usersByJustintvId[justintvUser.id] || + (usersByNetflixId[netflixUser.id] = netflixUser); + }) + .redirectPath('/') + everyauth.box .apiKey(conf.box.apiKey) .findOrCreateUser( function (sess, authToken, boxUser) { diff --git a/example/views/home.jade b/example/views/home.jade index 76456b74..144c4cea 100644 --- a/example/views/home.jade +++ b/example/views/home.jade @@ -45,6 +45,9 @@ #justintv-login a(href='/auth/justintv', style='border: 0px') img(src='http://s.jtvnw.net/jtv_user_pictures/hosted_images/new_logo_148_40_black.png') + #netflix-login + a(href='/auth/netflix', style='border: 0px') + img(src='http://developer.netflix.com/public/Mashery/images/clients/netflix/logo.gif') #box-login a(href='/auth/box', style='border: 0px') img(src='http://sites.box.net/apps/web/simpleshare/img/logo.png') diff --git a/lib/modules/netflix.js b/lib/modules/netflix.js new file mode 100644 index 00000000..f0bdb94e --- /dev/null +++ b/lib/modules/netflix.js @@ -0,0 +1,47 @@ +var oauthModule = require('./oauth') + , querystring = require('querystring') + , xml2js = require('xml2js'); + +var netflix = module.exports = +oauthModule.submodule('netflix') + .apiHost('http://api.netflix.com') + .oauthHost('http://api.netflix.com') + + // abnormal authorize URI + .authorizePath('https://api-user.netflix.com/oauth/login') + + .entryPath('/auth/netflix') + .callbackPath('/auth/netflix/callback') + + .getAccessToken( function (reqToken, reqTokenSecret, verifier) { + var promise = this.Promise() + , extraParams = { + consumer_key: this._consumerKey + , consumer_secret: this._consumerSecret + }; + this.oauth._performSecureRequest(reqToken, reqTokenSecret, "POST", this._apiHost + this._accessTokenPath, extraParams, null, null, function(error,data,res){ + if( error ) promise.fail(error); + else { + var results= querystring.parse( data ); + var oauth_access_token= results["oauth_token"]; + delete results["oauth_token"]; + var oauth_access_token_secret= results["oauth_token_secret"]; + delete results["oauth_token_secret"]; + promise.fulfill(oauth_access_token, oauth_access_token_secret, results) + } + }); + return promise; + }) + + .fetchOAuthUser( function (accessToken, accessTokenSecret, params) { + var promise = this.Promise() + , parser = new xml2js.Parser(); + parser.addListener('end', function(data){ + return promise.fulfill(data); + }); + this.oauth.get(this.apiHost() + '/users/'+params.user_id, accessToken, accessTokenSecret, function (err, data) { + if (err) return promise.fail(err); + return parser.parseString(data); + }); + return promise; + }); diff --git a/lib/modules/oauth.js b/lib/modules/oauth.js index 2055f391..2a0a0dee 100644 --- a/lib/modules/oauth.js +++ b/lib/modules/oauth.js @@ -1,6 +1,7 @@ var everyModule = require('./everymodule') , OAuth = require('oauth').OAuth , url = require('url') + , querystring = require('querystring') , extractHostname = require('../utils').extractHostname; var oauth = module.exports = @@ -10,7 +11,8 @@ everyModule.submodule('oauth') , oauthHost: 'the host for the OAuth provider' , requestTokenPath: "the path on the OAuth provider's domain where we request the request token, e.g., /oauth/request_token" , accessTokenPath: "the path on the OAuth provider's domain where we request the access token, e.g., /oauth/access_token" - , authorizePath: 'the path on the OAuth provider where you direct a visitor to login, e.g., /oauth/authorize' + , authorizePath: 'the path or full uri on the OAuth provider where you direct a visitor to login, e.g., /oauth/authorize or http://api-user.netflix.com/' + , moreAuthQueryParams: 'Additional querystrings to provide during the authorize step. e.g., { application_name: "testapp" }' , consumerKey: 'the api key provided by the OAuth provider' , consumerSecret: 'the api secret provided by the OAuth provider' , myHostname: 'e.g., http://localhost:3000 . Notice no trailing slash' @@ -92,7 +94,7 @@ everyModule.submodule('oauth') } var p = this.Promise(); - this.oauth.getOAuthRequestToken( function (err, token, tokenSecret, authUrl, params) { + this.oauth.getOAuthRequestToken( function (err, token, tokenSecret, /* authUrl, */ params) { if (err && !~(err.data.indexOf('Invalid / expired Token'))) { return p.fail(err); } @@ -108,12 +110,53 @@ everyModule.submodule('oauth') _provider.tokenSecret = tokenSecret; }) .redirectToProviderAuth( function (res, token) { + + var url = (/^http/.test(this._authorizePath)) + ? this._authorizePath + : this._oauthHost + this._authorizePath + , params = { + oauth_token: token + , oauth_callback: this._myHostname + this._callbackPath + } + , additionalParams = this._moreAuthQueryParams + , param + , authUri; + + if (additionalParams) for (var k in additionalParams) { + param = additionalParams[k]; + // Leaving this in in case needed in the future. + // Caused problems because req object wasn't passed to this function. + //if ('function' === typeof param) { + // // e.g., for facebook module, param could be + // // function () { + // // return this._scope && this.scope(); + // // } + // additionalParams[k] = // cache the function call + // param = param.call(this); + //} + //if ('function' === typeof param) { + // // this.scope() itself could be a function + // // to allow for dynamic scope determination - e.g., + // // function (req, res) { + // // return req.session.onboardingPhase; // => "email" + // // } + // param = param.call(this, req, res); + //} + params[k] = param; + } + + authUri = url + '?' + querystring.stringify(params); + res.writeHead(303, {'Location': authUri}); + res.end(); + + // Build URI from host + authorizePath if authorizeURI is absent (default behavior) // 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. - res.writeHead(303, { 'Location': this._oauthHost + this._authorizePath + '?oauth_token=' + token + '&oauth_callback=' + this._myHostname + this._callbackPath }); - res.end(); + //res.writeHead(303, { 'Location': this._oauthHost + this._authorizePath + '?oauth_token=' + token + '&oauth_callback=' + this._myHostname + this._callbackPath }); + //res.end(); + }) // Steps for GET `callbackPath`