2014-01-29 16:42:06 +00:00
( function ( module ) {
"use strict" ;
2014-08-16 02:54:24 +00:00
/ *
Welcome to the SSO OAuth plugin ! If you 're inspecting this code, you' re probably looking to
hook up NodeBB with your existing OAuth endpoint .
Step 1 : Fill in the "constants" section below with the requisite informaton . Either the "oauth"
or "oauth2" section needs to be filled , depending on what you set "type" to .
Step 2 : Give it a whirl . If you see the congrats message , you ' re doing well so far !
Step 3 : Customise the ` parseUserReturn ` method to normalise your user route ' s data return into
a format accepted by NodeBB . Instructions are provided there . ( Line 137 )
Step 4 : If all goes well , you ' ll be able to login / register via your OAuth endpoint credentials .
* /
2014-01-29 16:42:06 +00:00
var User = module . parent . require ( './user' ) ,
2014-07-22 14:03:52 +00:00
Groups = module . parent . require ( './groups' ) ,
2014-02-22 04:10:12 +00:00
meta = module . parent . require ( './meta' ) ,
2014-01-29 16:42:06 +00:00
db = module . parent . require ( '../src/database' ) ,
passport = module . parent . require ( 'passport' ) ,
2014-01-29 16:42:50 +00:00
fs = module . parent . require ( 'fs' ) ,
path = module . parent . require ( 'path' ) ,
nconf = module . parent . require ( 'nconf' ) ,
winston = module . parent . require ( 'winston' ) ,
2014-07-10 07:22:44 +00:00
async = module . parent . require ( 'async' ) ,
2014-01-29 16:42:50 +00:00
2014-08-16 02:21:03 +00:00
constants = Object . freeze ( {
type : '' , // Either 'oauth' or 'oauth2'
name : '' , // Something unique to your OAuth provider in lowercase, like "github", or "nodebb"
oauth : {
requestTokenURL : '' ,
accessTokenURL : '' ,
userAuthorizationURL : '' ,
consumerKey : '' ,
consumerSecret : ''
} ,
oauth2 : {
authorizationURL : '' ,
tokenURL : '' ,
clientID : '' ,
clientSecret : ''
2014-08-16 02:41:49 +00:00
} ,
2014-08-16 02:54:24 +00:00
userRoute : '' // This is the address to your app's "user profile" API endpoint (expects JSON)
2014-08-16 02:21:03 +00:00
} ) ,
configOk = false ,
2014-08-16 02:41:49 +00:00
OAuth = { } , passportOAuth , opts ;
if ( ! constants . name ) {
2014-08-16 02:54:24 +00:00
winston . error ( '[sso-oauth] Please specify a name for your OAuth provider (library.js:32)' ) ;
2014-08-16 02:41:49 +00:00
} else if ( ! constants . type || ( constants . type !== 'oauth' && constants . type !== 'oauth2' ) ) {
2014-08-16 02:54:24 +00:00
winston . error ( '[sso-oauth] Please specify an OAuth strategy to utilise (library.js:31)' ) ;
2014-08-16 02:41:49 +00:00
} else if ( ! constants . userRoute ) {
winston . error ( '[sso-oauth] User Route required (library.js:31)' ) ;
} else {
configOk = true ;
}
2014-02-04 17:09:46 +00:00
2014-03-19 00:04:18 +00:00
OAuth . getStrategy = function ( strategies , callback ) {
2014-08-16 02:21:03 +00:00
if ( configOk ) {
passportOAuth = require ( 'passport-oauth' ) [ constants . type === 'oauth' ? 'OAuthStrategy' : 'OAuth2Strategy' ] ;
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
if ( constants . type === 'oauth' ) {
// OAuth options
opts = constants . oauth ;
2014-08-16 02:41:49 +00:00
opts . callbackURL = nconf . get ( 'url' ) + '/auth/' + constants . name + '/callback' ;
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
passportOAuth . Strategy . prototype . userProfile = function ( token , secret , params , done ) {
2014-08-16 02:41:49 +00:00
this . _oauth . get ( constants . userRoute , token , secret , function ( err , body , res ) {
2014-08-16 02:21:03 +00:00
if ( err ) { return done ( new InternalOAuthError ( 'failed to fetch user profile' , err ) ) ; }
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
try {
var json = JSON . parse ( body ) ;
2014-08-16 02:48:52 +00:00
OAuth . parseUserReturn ( json , function ( err , profile ) {
2014-08-16 02:41:49 +00:00
if ( err ) return done ( err ) ;
profile . provider = constants . name ;
done ( null , profile ) ;
2014-08-16 02:21:03 +00:00
} ) ;
} catch ( e ) {
done ( e ) ;
}
} ) ;
} ;
} else if ( constants . type === 'oauth2' ) {
// OAuth 2 options
2014-08-16 02:41:49 +00:00
opts = constants . oauth2 ;
2014-08-16 02:21:03 +00:00
opts . callbackURL = nconf . get ( 'url' ) + '/auth/' + constants . name + '/callback' ;
passportOAuth . Strategy . prototype . userProfile = function ( accessToken , done ) {
2014-08-16 02:41:49 +00:00
this . _oauth2 . get ( constants . userRoute , accessToken , function ( err , body , res ) {
2014-08-16 02:21:03 +00:00
if ( err ) { return done ( new InternalOAuthError ( 'failed to fetch user profile' , err ) ) ; }
try {
var json = JSON . parse ( body ) ;
2014-08-16 02:48:52 +00:00
OAuth . parseUserReturn ( json , function ( err , profile ) {
2014-08-16 02:41:49 +00:00
if ( err ) return done ( err ) ;
profile . provider = constants . name ;
done ( null , profile ) ;
2014-08-16 02:21:03 +00:00
} ) ;
} catch ( e ) {
done ( e ) ;
}
} ) ;
} ;
}
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
passport . use ( constants . name , new passportOAuth ( opts , function ( token , secret , profile , done ) {
OAuth . login ( {
oAuthid : profile . id ,
handle : profile . displayName ,
email : profile . emails [ 0 ] . value ,
isAdmin : profile . isAdmin
} , function ( err , user ) {
if ( err ) {
return done ( err ) ;
}
done ( null , user ) ;
} ) ;
} ) ) ;
strategies . push ( {
name : constants . name ,
url : '/auth/' + constants . name ,
callbackURL : '/auth/' + constants . name + '/callback' ,
2014-08-16 02:41:49 +00:00
icon : 'fa-check-square' ,
2014-08-16 02:21:03 +00:00
scope : ( constants . scope || '' ) . split ( ',' )
} ) ;
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
callback ( null , strategies ) ;
} else {
callback ( new Error ( 'OAuth Configuration is invalid' ) ) ;
}
} ;
2014-07-22 14:03:52 +00:00
2014-08-16 02:21:03 +00:00
OAuth . parseUserReturn = function ( data , callback ) {
// Alter this section to include whatever data is necessary
// NodeBB *requires* the following: id, displayName, emails.
// Everything else is optional.
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
// Find out what is available by uncommenting this line:
// console.log(data);
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
var profile = { } ;
profile . id = data . id ;
profile . displayName = data . name ;
profile . emails = [ { value : data . email } ] ;
2014-02-04 17:09:46 +00:00
2014-08-16 02:21:03 +00:00
// Do you want to automatically make somebody an admin? This line might help you do that...
// profile.isAdmin = data.isAdmin ? true : false;
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
// Delete or comment out the next TWO (2) lines when you are ready to proceed
process . stdout . write ( '===\nAt this point, you\'ll need to customise the above section to id, displayName, and emails into the "profile" object.\n===' ) ;
2014-08-16 02:41:49 +00:00
return callback ( new Error ( 'Congrats! So far so good -- please see server log for details' ) ) ;
2014-01-29 16:42:06 +00:00
2014-08-16 02:41:49 +00:00
callback ( null , profile ) ;
2014-08-16 02:21:03 +00:00
}
2014-01-29 16:42:06 +00:00
2014-07-22 14:03:52 +00:00
OAuth . login = function ( payload , callback ) {
OAuth . getUidByOAuthid ( payload . oAuthid , function ( err , uid ) {
2014-01-29 16:42:06 +00:00
if ( err ) {
return callback ( err ) ;
}
if ( uid !== null ) {
// Existing User
callback ( null , {
uid : uid
} ) ;
} else {
// New User
var success = function ( uid ) {
// Save provider-specific information to the user
2014-08-16 02:21:03 +00:00
User . setUserField ( uid , constants . name + 'Id' , payload . oAuthid ) ;
db . setObjectField ( constants . name + 'Id:uid' , payload . oAuthid , uid ) ;
2014-07-22 14:03:52 +00:00
if ( payload . isAdmin ) {
Groups . join ( 'administrators' , uid , function ( err ) {
callback ( null , {
uid : uid
} ) ;
} ) ;
} else {
callback ( null , {
uid : uid
} ) ;
}
2014-01-29 16:42:06 +00:00
} ;
2014-07-22 14:03:52 +00:00
User . getUidByEmail ( payload . email , function ( err , uid ) {
2014-01-29 16:42:06 +00:00
if ( err ) {
return callback ( err ) ;
}
if ( ! uid ) {
2014-07-22 14:03:52 +00:00
User . create ( {
username : payload . handle ,
email : payload . email
} , function ( err , uid ) {
2014-01-29 16:42:06 +00:00
if ( err ) {
return callback ( err ) ;
}
success ( uid ) ;
} ) ;
} else {
success ( uid ) ; // Existing account -- merge
}
} ) ;
}
} ) ;
} ;
OAuth . getUidByOAuthid = function ( oAuthid , callback ) {
2014-08-16 02:21:03 +00:00
db . getObjectField ( constants . name + 'Id:uid' , oAuthid , function ( err , uid ) {
2014-01-29 16:42:06 +00:00
if ( err ) {
return callback ( err ) ;
}
callback ( null , uid ) ;
} ) ;
} ;
2014-07-10 07:22:44 +00:00
OAuth . deleteUserData = function ( uid , callback ) {
async . waterfall ( [
2014-08-16 02:21:03 +00:00
async . apply ( User . getUserField , uid , constants . name + 'Id' ) ,
2014-07-10 07:22:44 +00:00
function ( oAuthIdToDelete , next ) {
2014-08-16 02:21:03 +00:00
db . deleteObjectField ( constants . name + 'Id:uid' , oAuthIdToDelete , next ) ;
2014-07-09 07:32:44 +00:00
}
2014-07-10 07:22:44 +00:00
] , function ( err ) {
if ( err ) {
2014-08-16 02:21:03 +00:00
winston . error ( '[sso-oauth] Could not remove OAuthId data for uid ' + uid + '. Error: ' + err ) ;
2014-07-10 07:22:44 +00:00
return callback ( err ) ;
2014-07-09 07:32:44 +00:00
}
2014-07-10 07:22:44 +00:00
callback ( ) ;
2014-07-09 07:32:44 +00:00
} ) ;
} ;
2014-01-29 16:42:06 +00:00
module . exports = OAuth ;
} ( module ) ) ;