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
2016-10-25 20:22:14 +00:00
a format accepted by NodeBB . Instructions are provided there . ( Line 146 )
2014-08-16 02:54:24 +00:00
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' ) ,
2016-03-30 16:09:31 +00:00
async = module . parent . require ( 'async' ) ;
2014-01-29 16:42:50 +00:00
2016-03-30 16:09:31 +00:00
var authenticationController = module . parent . require ( './controllers/authentication' ) ;
2017-07-06 15:19:25 +00:00
/ * *
* REMEMBER
* Never save your OAuth Key / Secret or OAuth2 ID / Secret pair in code ! It could be published and leaked accidentally .
* Save it into your config . json file instead :
*
* {
* ...
* "oauth" : {
* "id" : "someoauthid" ,
* "secret" : "youroauthsecret"
* }
* ...
* }
*
* ... or use environment variables instead :
*
* ` OAUTH__ID=someoauthid OAUTH__SECRET=youroauthsecret node app.js `
* /
2016-03-30 16:09:31 +00:00
var constants = Object . freeze ( {
2017-09-06 19:21:59 +00:00
type : 'oauth2' , // Either 'oauth' or 'oauth2'
name : 'icynet' , // Something unique to your OAuth provider in lowercase, like "github", or "nodebb"
scope : 'email privilege' ,
2014-08-16 02:21:03 +00:00
oauth : {
requestTokenURL : '' ,
accessTokenURL : '' ,
userAuthorizationURL : '' ,
2017-07-06 15:19:25 +00:00
consumerKey : nconf . get ( 'oauth:key' ) , // don't change this line
consumerSecret : nconf . get ( 'oauth:secret' ) , // don't change this line
2014-08-16 02:21:03 +00:00
} ,
oauth2 : {
2017-09-06 19:21:59 +00:00
authorizationURL : nconf . get ( 'oauth:provider' ) + '/oauth2/authorize' ,
tokenURL : nconf . get ( 'oauth:provider' ) + '/oauth2/token' ,
clientID : nconf . get ( 'oauth:id' ) ,
clientSecret : nconf . get ( 'oauth:secret' ) ,
2014-08-16 02:41:49 +00:00
} ,
2017-09-06 19:21:59 +00:00
userRoute : nconf . get ( 'oauth:provider' ) + '/oauth2/user'
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 ;
2016-03-30 16:09:31 +00:00
2014-08-16 02:41:49 +00:00
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 ;
2016-03-30 16:09:31 +00:00
2014-08-16 02:41:49 +00:00
done ( null , profile ) ;
2014-08-16 02:21:03 +00:00
} ) ;
} catch ( e ) {
done ( e ) ;
}
} ) ;
} ;
}
2014-03-19 00:04:18 +00:00
2016-04-13 22:33:23 +00:00
opts . passReqToCallback = true ;
passport . use ( constants . name , new passportOAuth ( opts , function ( req , token , secret , profile , done ) {
2014-08-16 02:21:03 +00:00
OAuth . login ( {
oAuthid : profile . id ,
handle : profile . displayName ,
email : profile . emails [ 0 ] . value ,
isAdmin : profile . isAdmin
} , function ( err , user ) {
if ( err ) {
return done ( err ) ;
}
2016-04-13 22:33:23 +00:00
authenticationController . onSuccessfulLogin ( req , user . uid ) ;
2014-08-16 02:21:03 +00:00
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 ;
2017-09-06 19:21:59 +00:00
profile . displayName = data . display _name ;
2014-08-16 02:21:03 +00:00
profile . emails = [ { value : data . email } ] ;
2017-09-06 19:21:59 +00:00
profile . isAdmin = data . privilege === 5 ;
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
2017-09-06 19:21:59 +00:00
//process.stdout.write('===\nAt this point, you\'ll need to customise the above section to id, displayName, and emails into the "profile" object.\n===');
//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 ) ;
} ) ;
} ;
2017-01-09 18:48:37 +00:00
OAuth . deleteUserData = function ( data , callback ) {
2014-07-10 07:22:44 +00:00
async . waterfall ( [
2017-01-09 18:48:37 +00:00
async . apply ( User . getUserField , data . 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 ) {
2017-01-09 18:48:37 +00:00
winston . error ( '[sso-oauth] Could not remove OAuthId data for uid ' + data . uid + '. Error: ' + err ) ;
2014-07-10 07:22:44 +00:00
return callback ( err ) ;
2014-07-09 07:32:44 +00:00
}
2017-01-09 18:48:37 +00:00
callback ( null , data ) ;
2014-07-09 07:32:44 +00:00
} ) ;
} ;
2014-01-29 16:42:06 +00:00
module . exports = OAuth ;
2016-10-25 20:22:14 +00:00
} ( module ) ) ;