nodebb-plugin-sso-oauth/library.js

286 lines
8.3 KiB
JavaScript
Raw Normal View History

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');
2014-01-29 16:42:50 +00:00
2019-01-17 21:51:32 +00:00
const async = require('async');
const passport = module.parent.require('passport');
const nconf = module.parent.require('nconf');
const winston = module.parent.require('winston');
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-09-06 16:03:42 +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',
2019-01-17 21:51:32 +00:00
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: {
2019-09-06 16:03:42 +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'),
2019-01-17 21:51:32 +00:00
},
2019-09-06 16:03:42 +00:00
userRoute: nconf.get('oauth:provider') + '/oauth2/user'
})
2019-01-17 21:51:32 +00:00
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) {
if (configOk) {
passportOAuth = require('passport-oauth')[constants.type === 'oauth' ? 'OAuthStrategy' : 'OAuth2Strategy'];
2014-03-19 00:04:18 +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
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;
2014-08-16 02:41:49 +00:00
done(null, profile);
});
2019-01-17 21:51:32 +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;
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);
}
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;
2014-08-16 02:41:49 +00:00
done(null, profile);
});
2019-01-17 21:51:32 +00:00
} catch (e) {
done(e);
}
});
};
}
2014-03-19 00:04:18 +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) {
OAuth.login({
oAuthid: profile.id,
handle: profile.displayName,
email: profile.emails[0].value,
2017-09-06 21:06:17 +00:00
picture: profile.picture,
isAdmin: profile.isAdmin
}, function(err, user) {
if (err) {
return done(err);
}
authenticationController.onSuccessfulLogin(req, user.uid);
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-03-19 00:04:18 +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) {
// 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
// Find out what is available by uncommenting this line:
// console.log(data);
2014-03-19 00:04:18 +00:00
var profile = {};
profile.id = data.id;
2017-09-06 19:21:59 +00:00
profile.displayName = data.display_name;
profile.emails = [{ value: data.email }];
2017-09-06 19:21:59 +00:00
profile.isAdmin = data.privilege === 5;
2017-09-06 21:06:17 +00:00
profile.picture = 'https://icynet.eu/api/avatar/' + data.id
2014-02-04 17:09:46 +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
// 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
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
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,
2017-09-06 21:06:17 +00:00
email: payload.email,
picture: payload.picture
2014-07-22 14:03:52 +00:00
}, function(err, uid) {
2014-01-29 16:42:06 +00:00
if(err) {
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) {
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) {
db.deleteObjectField(constants.name + 'Id:uid', oAuthIdToDelete, next);
2019-01-17 21:51:32 +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);
return callback(err);
}
2017-01-09 18:48:37 +00:00
callback(null, data);
});
};
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));