2019-01-17 21:51:32 +00:00
'use strict' ;
2014-01-29 16:42:06 +00:00
2019-01-17 21:51:32 +00:00
( function ( module ) {
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 .
* /
2019-01-17 21:51:32 +00:00
const User = require . main . require ( './src/user' ) ;
const Groups = require . main . require ( './src/groups' ) ;
const db = require . main . require ( './src/database' ) ;
const authenticationController = require . main . require ( './src/controllers/authentication' ) ;
const async = require ( 'async' ) ;
const passport = module . parent . require ( 'passport' ) ;
const nconf = module . parent . require ( 'nconf' ) ;
const winston = module . parent . require ( 'winston' ) ;
2014-01-29 16:42:50 +00:00
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 `
* /
2019-01-18 17:09:45 +00:00
const constants = Object . freeze ( {
2019-01-17 21:51:32 +00:00
type : '' , // Either 'oauth' or 'oauth2'
name : '' , // Something unique to your OAuth provider in lowercase, like "github", or "nodebb"
oauth : {
requestTokenURL : '' ,
accessTokenURL : '' ,
userAuthorizationURL : '' ,
consumerKey : nconf . get ( 'oauth:key' ) , // don't change this line
consumerSecret : nconf . get ( 'oauth:secret' ) , // don't change this line
} ,
oauth2 : {
authorizationURL : '' ,
tokenURL : '' ,
clientID : nconf . get ( 'oauth:id' ) , // don't change this line
clientSecret : nconf . get ( 'oauth:secret' ) , // don't change this line
} ,
userRoute : '' , // This is the address to your app's "user profile" API endpoint (expects JSON)
} ) ;
2019-01-18 17:09:45 +00:00
const OAuth = { } ;
let configOk = false ;
let passportOAuth ;
let opts ;
2014-08-16 02:41:49 +00:00
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
2019-01-17 21:51:32 +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
2019-01-17 21:51:32 +00:00
passportOAuth . Strategy . prototype . userProfile = function ( token , secret , params , done ) {
this . _oauth . get ( constants . userRoute , token , secret , function ( err , body /* , res */ ) {
if ( err ) {
return done ( err ) ;
}
2014-03-19 00:04:18 +00:00
2014-08-16 02:21:03 +00:00
try {
var json = JSON . parse ( body ) ;
2019-01-17 21:51:32 +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
} ) ;
2019-01-17 21:51:32 +00:00
} catch ( e ) {
2014-08-16 02:21:03 +00:00
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' ;
2019-01-17 21:51:32 +00:00
passportOAuth . Strategy . prototype . userProfile = function ( accessToken , done ) {
this . _oauth2 . get ( constants . userRoute , accessToken , function ( err , body /* , res */ ) {
if ( err ) {
return done ( err ) ;
}
2014-08-16 02:21:03 +00:00
try {
var json = JSON . parse ( body ) ;
2019-01-17 21:51:32 +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
} ) ;
2019-01-17 21:51:32 +00:00
} catch ( e ) {
2014-08-16 02:21:03 +00:00
done ( e ) ;
}
} ) ;
} ;
}
2014-03-19 00:04:18 +00:00
2016-04-13 22:33:23 +00:00
opts . passReqToCallback = true ;
2019-01-17 21:51:32 +00:00
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 ,
2019-01-17 21:51:32 +00:00
isAdmin : profile . isAdmin ,
} , function ( err , user ) {
2014-08-16 02:21:03 +00:00
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' ,
2019-01-17 21:51:32 +00:00
scope : ( constants . scope || '' ) . split ( ',' ) ,
2014-08-16 02:21:03 +00:00
} ) ;
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
2019-01-17 21:51:32 +00:00
OAuth . parseUserReturn = function ( data , callback ) {
2014-08-16 02:21:03 +00:00
// 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
2019-01-17 21:51:32 +00:00
// eslint-disable-next-line
2014-08-16 02:41:49 +00:00
callback ( null , profile ) ;
2019-01-17 21:51:32 +00:00
} ;
2014-01-29 16:42:06 +00:00
2019-01-17 21:51:32 +00:00
OAuth . login = function ( payload , callback ) {
OAuth . getUidByOAuthid ( payload . oAuthid , function ( err , uid ) {
if ( err ) {
2014-01-29 16:42:06 +00:00
return callback ( err ) ;
}
if ( uid !== null ) {
// Existing User
callback ( null , {
2019-01-17 21:51:32 +00:00
uid : uid ,
2014-01-29 16:42:06 +00:00
} ) ;
} else {
// New User
2019-01-17 21:51:32 +00:00
var success = function ( uid ) {
2014-01-29 16:42:06 +00:00
// 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 ) {
2019-01-17 21:51:32 +00:00
Groups . join ( 'administrators' , uid , function ( err ) {
callback ( err , {
uid : uid ,
2014-07-22 14:03:52 +00:00
} ) ;
} ) ;
} else {
callback ( null , {
2019-01-17 21:51:32 +00:00
uid : uid ,
2014-07-22 14:03:52 +00:00
} ) ;
}
2014-01-29 16:42:06 +00:00
} ;
2019-01-17 21:51:32 +00:00
User . getUidByEmail ( payload . email , function ( err , uid ) {
if ( err ) {
2014-01-29 16:42:06 +00:00
return callback ( err ) ;
}
if ( ! uid ) {
2014-07-22 14:03:52 +00:00
User . create ( {
username : payload . handle ,
2019-01-17 21:51:32 +00:00
email : payload . email ,
} , function ( err , uid ) {
if ( err ) {
2014-01-29 16:42:06 +00:00
return callback ( err ) ;
}
success ( uid ) ;
} ) ;
} else {
success ( uid ) ; // Existing account -- merge
}
} ) ;
}
} ) ;
} ;
2019-01-17 21:51:32 +00:00
OAuth . getUidByOAuthid = function ( oAuthid , callback ) {
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 ) ;
} ) ;
} ;
2019-01-17 21:51:32 +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' ) ,
2019-01-17 21:51:32 +00:00
function ( oAuthIdToDelete , next ) {
2014-08-16 02:21:03 +00:00
db . deleteObjectField ( constants . name + 'Id:uid' , oAuthIdToDelete , next ) ;
2019-01-17 21:51:32 +00:00
} ,
] , function ( err ) {
2014-07-10 07:22:44 +00:00
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
} ) ;
} ;
2019-01-17 21:51:32 +00:00
// If this filter is not there, the deleteUserData function will fail when getting the oauthId for deletion.
OAuth . whitelistFields = function ( params , callback ) {
params . whitelist . push ( constants . name + 'Id' ) ;
callback ( null , params ) ;
} ;
2018-08-02 09:38:15 +00:00
2014-01-29 16:42:06 +00:00
module . exports = OAuth ;
2016-10-25 20:22:14 +00:00
} ( module ) ) ;