new version

This commit is contained in:
Evert Prants 2019-09-06 19:03:42 +03:00
commit 1b15b4f3b7
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
8 changed files with 3320 additions and 67 deletions

137
.eslintrc Normal file
View File

@ -0,0 +1,137 @@
{
"extends": "airbnb-base",
"parserOptions": {
"sourceType": "script"
},
"rules": {
// Customized
"handle-callback-err": [ "error","^(e$|(e|(.*(_e|E)))rr)" ],
"comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "never"
}],
"no-empty": ["error", { "allowEmptyCatch": true }],
"no-underscore-dangle": "off",
"no-console": "off",
"no-mixed-operators": ["error", { "allowSamePrecedence": true }],
"strict": ["error", "global"],
"consistent-return": "off",
"func-names": "off",
"no-tabs": "off",
"indent": ["error", "tab"],
"no-eq-null": "off",
"camelcase": "off",
"no-new": "off",
"no-shadow": "off",
"no-use-before-define": ["error", "nofunc"],
"no-prototype-builtins": "off",
"new-cap": "off",
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
"object-curly-newline": "off",
"no-restricted-globals": "off",
"function-paren-newline": "off",
"import/no-unresolved": "error",
"quotes": ["error", "single", {
"avoidEscape": true,
"allowTemplateLiterals": true
}],
"no-else-return": [ "error", { "allowElseIf": true } ],
"operator-linebreak": [ "error", "after" ],
// ES6
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-arrow-callback": "off",
"prefer-template": "off",
"no-var": "off",
"object-shorthand": "off",
"vars-on-top": "off",
"prefer-destructuring": "off",
// TODO
"import/no-extraneous-dependencies": "off",
"import/no-dynamic-require": "off",
"import/newline-after-import": "off",
"no-bitwise": "off",
"global-require": "off",
"max-len": "off",
"no-param-reassign": "off",
"no-restricted-syntax": "off",
"no-script-url": "off",
"default-case": "off",
"linebreak-style": "off",
// "no-multi-assign": "off",
// "one-var": "off",
// "no-undef": "off",
// "max-nested-callbacks": "off",
// "no-mixed-requires": "off",
// "brace-style": "off",
// "max-statements-per-line": "off",
// "no-unused-vars": "off",
// "no-mixed-spaces-and-tabs": "off",
// "no-useless-concat": "off",
// "require-jsdoc": "off",
// "eqeqeq": "off",
// "no-negated-condition": "off",
// "one-var-declaration-per-line": "off",
// "no-lonely-if": "off",
// "radix": "off",
// "no-else-return": "off",
// "no-useless-escape": "off",
// "block-scoped-var": "off",
// "operator-assignment": "off",
// "yoda": "off",
// "no-loop-func": "off",
// "no-void": "off",
// "valid-jsdoc": "off",
// "no-cond-assign": "off",
// "no-redeclare": "off",
// "no-unreachable": "off",
// "no-nested-ternary": "off",
// "operator-linebreak": "off",
// "guard-for-in": "off",
// "no-unneeded-ternary": "off",
// "no-sequences": "off",
// "no-extend-native": "off",
// "no-shadow-restricted-names": "off",
// "no-extra-boolean-cast": "off",
// "no-path-concat": "off",
// "no-unused-expressions": "off",
// "no-return-assign": "off",
// "no-restricted-modules": "off",
// "object-curly-spacing": "off",
// "indent": "off",
// "padded-blocks": "off",
// "eol-last": "off",
// "lines-around-directive": "off",
// "strict": "off",
// "comma-dangle": "off",
// "no-multi-spaces": "off",
// "quotes": "off",
// "keyword-spacing": "off",
// "no-mixed-operators": "off",
// "comma-spacing": "off",
// "no-trailing-spaces": "off",
// "key-spacing": "off",
// "no-multiple-empty-lines": "off",
// "spaced-comment": "off",
// "space-in-parens": "off",
// "block-spacing": "off",
// "quote-props": "off",
// "space-unary-ops": "off",
// "no-empty": "off",
// "dot-notation": "off",
// "func-call-spacing": "off",
// "array-bracket-spacing": "off",
// "object-property-newline": "off",
// "no-continue": "off",
// "no-extra-semi": "off",
// "no-spaced-func": "off",
// "no-useless-return": "off"
}
}

6
.gitignore vendored
View File

@ -216,3 +216,9 @@ pip-log.txt
sftp-config.json sftp-config.json
node_modules/ node_modules/
####################
# JetBrains
####################
.idea

View File

@ -14,4 +14,6 @@ NodeBB Plugin that allows users to login/register via any configured OAuth provi
## Trouble? ## Trouble?
The NodeBB team builds out SSO plugins for a nominal fee. [Reach out to us for a quote.](mailto:sales@nodebb.org)
Find us on [the community forums](http://community.nodebb.org)! Find us on [the community forums](http://community.nodebb.org)!

3
commitlint.config.js Normal file
View File

@ -0,0 +1,3 @@
'use strict';
module.exports = { extends: ['@commitlint/config-angular'] };

View File

@ -1,6 +1,6 @@
(function(module) { 'use strict';
"use strict";
(function (module) {
/* /*
Welcome to the SSO OAuth plugin! If you're inspecting this code, you're probably looking to 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. hook up NodeBB with your existing OAuth endpoint.
@ -16,18 +16,16 @@
Step 4: If all goes well, you'll be able to login/register via your OAuth endpoint credentials. Step 4: If all goes well, you'll be able to login/register via your OAuth endpoint credentials.
*/ */
var User = module.parent.require('./user'), const User = require.main.require('./src/user');
Groups = module.parent.require('./groups'), const Groups = require.main.require('./src/groups');
meta = module.parent.require('./meta'), const db = require.main.require('./src/database');
db = module.parent.require('../src/database'), const authenticationController = require.main.require('./src/controllers/authentication');
passport = module.parent.require('passport'),
fs = module.parent.require('fs'),
path = module.parent.require('path'),
nconf = module.parent.require('nconf'),
winston = module.parent.require('winston'),
async = module.parent.require('async');
var authenticationController = module.parent.require('./controllers/authentication'); const async = require('async');
const passport = module.parent.require('passport');
const nconf = module.parent.require('nconf');
const winston = module.parent.require('winston');
/** /**
* REMEMBER * REMEMBER
@ -48,27 +46,30 @@
* `OAUTH__ID=someoauthid OAUTH__SECRET=youroauthsecret node app.js` * `OAUTH__ID=someoauthid OAUTH__SECRET=youroauthsecret node app.js`
*/ */
var constants = Object.freeze({ const constants = Object.freeze({
type: 'oauth2', // Either 'oauth' or 'oauth2' type: 'oauth2', // Either 'oauth' or 'oauth2'
name: 'icynet', // Something unique to your OAuth provider in lowercase, like "github", or "nodebb" name: 'icynet', // Something unique to your OAuth provider in lowercase, like "github", or "nodebb"
scope: 'email privilege', scope: 'email privilege',
oauth: { oauth: {
requestTokenURL: '', requestTokenURL: '',
accessTokenURL: '', accessTokenURL: '',
userAuthorizationURL: '', userAuthorizationURL: '',
consumerKey: nconf.get('oauth:key'), // don't change this line consumerKey: nconf.get('oauth:key'), // don't change this line
consumerSecret: nconf.get('oauth:secret'), // don't change this line consumerSecret: nconf.get('oauth:secret'), // don't change this line
}, },
oauth2: { oauth2: {
authorizationURL: nconf.get('oauth:provider') + '/oauth2/authorize', authorizationURL: nconf.get('oauth:provider') + '/oauth2/authorize',
tokenURL: nconf.get('oauth:provider') + '/oauth2/token', tokenURL: nconf.get('oauth:provider') + '/oauth2/token',
clientID: nconf.get('oauth:id'), clientID: nconf.get('oauth:id'),
clientSecret: nconf.get('oauth:secret'), clientSecret: nconf.get('oauth:secret'),
}, },
userRoute: nconf.get('oauth:provider') + '/oauth2/user' userRoute: nconf.get('oauth:provider') + '/oauth2/user'
}), })
configOk = false,
OAuth = {}, passportOAuth, opts; const OAuth = {};
let configOk = false;
let passportOAuth;
let opts;
if (!constants.name) { if (!constants.name) {
winston.error('[sso-oauth] Please specify a name for your OAuth provider (library.js:32)'); winston.error('[sso-oauth] Please specify a name for your OAuth provider (library.js:32)');
@ -80,7 +81,7 @@
configOk = true; configOk = true;
} }
OAuth.getStrategy = function(strategies, callback) { OAuth.getStrategy = function (strategies, callback) {
if (configOk) { if (configOk) {
passportOAuth = require('passport-oauth')[constants.type === 'oauth' ? 'OAuthStrategy' : 'OAuth2Strategy']; passportOAuth = require('passport-oauth')[constants.type === 'oauth' ? 'OAuthStrategy' : 'OAuth2Strategy'];
@ -89,19 +90,21 @@
opts = constants.oauth; opts = constants.oauth;
opts.callbackURL = nconf.get('url') + '/auth/' + constants.name + '/callback'; opts.callbackURL = nconf.get('url') + '/auth/' + constants.name + '/callback';
passportOAuth.Strategy.prototype.userProfile = function(token, secret, params, done) { passportOAuth.Strategy.prototype.userProfile = function (token, secret, params, done) {
this._oauth.get(constants.userRoute, token, secret, function(err, body, res) { this._oauth.get(constants.userRoute, token, secret, function (err, body/* , res */) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); } if (err) {
return done(err);
}
try { try {
var json = JSON.parse(body); var json = JSON.parse(body);
OAuth.parseUserReturn(json, function(err, profile) { OAuth.parseUserReturn(json, function (err, profile) {
if (err) return done(err); if (err) return done(err);
profile.provider = constants.name; profile.provider = constants.name;
done(null, profile); done(null, profile);
}); });
} catch(e) { } catch (e) {
done(e); done(e);
} }
}); });
@ -111,19 +114,21 @@
opts = constants.oauth2; opts = constants.oauth2;
opts.callbackURL = nconf.get('url') + '/auth/' + constants.name + '/callback'; opts.callbackURL = nconf.get('url') + '/auth/' + constants.name + '/callback';
passportOAuth.Strategy.prototype.userProfile = function(accessToken, done) { passportOAuth.Strategy.prototype.userProfile = function (accessToken, done) {
this._oauth2.get(constants.userRoute, accessToken, function(err, body, res) { this._oauth2.get(constants.userRoute, accessToken, function (err, body/* , res */) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); } if (err) {
return done(err);
}
try { try {
var json = JSON.parse(body); var json = JSON.parse(body);
OAuth.parseUserReturn(json, function(err, profile) { OAuth.parseUserReturn(json, function (err, profile) {
if (err) return done(err); if (err) return done(err);
profile.provider = constants.name; profile.provider = constants.name;
done(null, profile); done(null, profile);
}); });
} catch(e) { } catch (e) {
done(e); done(e);
} }
}); });
@ -132,7 +137,7 @@
opts.passReqToCallback = true; opts.passReqToCallback = true;
passport.use(constants.name, new passportOAuth(opts, function(req, token, secret, profile, done) { passport.use(constants.name, new passportOAuth(opts, function (req, token, secret, profile, done) {
OAuth.login({ OAuth.login({
oAuthid: profile.id, oAuthid: profile.id,
handle: profile.displayName, handle: profile.displayName,
@ -154,7 +159,7 @@
url: '/auth/' + constants.name, url: '/auth/' + constants.name,
callbackURL: '/auth/' + constants.name + '/callback', callbackURL: '/auth/' + constants.name + '/callback',
icon: 'fa-check-square', icon: 'fa-check-square',
scope: (constants.scope || '').split(',') scope: (constants.scope || '').split(','),
}); });
callback(null, strategies); callback(null, strategies);
@ -163,7 +168,7 @@
} }
}; };
OAuth.parseUserReturn = function(data, callback) { OAuth.parseUserReturn = function (data, callback) {
// Alter this section to include whatever data is necessary // Alter this section to include whatever data is necessary
// NodeBB *requires* the following: id, displayName, emails. // NodeBB *requires* the following: id, displayName, emails.
// Everything else is optional. // Everything else is optional.
@ -185,42 +190,43 @@
//process.stdout.write('===\nAt this point, you\'ll need to customise the above section to id, displayName, and emails into the "profile" object.\n==='); //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')); //return callback(new Error('Congrats! So far so good -- please see server log for details'));
// eslint-disable-next-line
callback(null, profile); callback(null, profile);
} };
OAuth.login = function(payload, callback) { OAuth.login = function (payload, callback) {
OAuth.getUidByOAuthid(payload.oAuthid, function(err, uid) { OAuth.getUidByOAuthid(payload.oAuthid, function (err, uid) {
if(err) { if (err) {
return callback(err); return callback(err);
} }
if (uid !== null) { if (uid !== null) {
// Existing User // Existing User
callback(null, { callback(null, {
uid: uid uid: uid,
}); });
} else { } else {
// New User // New User
var success = function(uid) { var success = function (uid) {
// Save provider-specific information to the user // Save provider-specific information to the user
User.setUserField(uid, constants.name + 'Id', payload.oAuthid); User.setUserField(uid, constants.name + 'Id', payload.oAuthid);
db.setObjectField(constants.name + 'Id:uid', payload.oAuthid, uid); db.setObjectField(constants.name + 'Id:uid', payload.oAuthid, uid);
if (payload.isAdmin) { if (payload.isAdmin) {
Groups.join('administrators', uid, function(err) { Groups.join('administrators', uid, function (err) {
callback(null, { callback(err, {
uid: uid uid: uid,
}); });
}); });
} else { } else {
callback(null, { callback(null, {
uid: uid uid: uid,
}); });
} }
}; };
User.getUidByEmail(payload.email, function(err, uid) { User.getUidByEmail(payload.email, function (err, uid) {
if(err) { if (err) {
return callback(err); return callback(err);
} }
@ -244,8 +250,8 @@
}); });
}; };
OAuth.getUidByOAuthid = function(oAuthid, callback) { OAuth.getUidByOAuthid = function (oAuthid, callback) {
db.getObjectField(constants.name + 'Id:uid', oAuthid, function(err, uid) { db.getObjectField(constants.name + 'Id:uid', oAuthid, function (err, uid) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
@ -253,13 +259,13 @@
}); });
}; };
OAuth.deleteUserData = function(data, callback) { OAuth.deleteUserData = function (data, callback) {
async.waterfall([ async.waterfall([
async.apply(User.getUserField, data.uid, constants.name + 'Id'), async.apply(User.getUserField, data.uid, constants.name + 'Id'),
function(oAuthIdToDelete, next) { function (oAuthIdToDelete, next) {
db.deleteObjectField(constants.name + 'Id:uid', oAuthIdToDelete, next); db.deleteObjectField(constants.name + 'Id:uid', oAuthIdToDelete, next);
} },
], function(err) { ], function (err) {
if (err) { if (err) {
winston.error('[sso-oauth] Could not remove OAuthId data for uid ' + data.uid + '. Error: ' + err); winston.error('[sso-oauth] Could not remove OAuthId data for uid ' + data.uid + '. Error: ' + err);
return callback(err); return callback(err);
@ -269,5 +275,11 @@
}); });
}; };
// 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);
};
module.exports = OAuth; module.exports = OAuth;
}(module)); }(module));

View File

@ -1,6 +1,6 @@
{ {
"name": "nodebb-plugin-sso-oauth", "name": "nodebb-plugin-sso-oauth",
"version": "0.3.3", "version": "0.3.4",
"description": "NodeBB Generic OAuth SSO", "description": "NodeBB Generic OAuth SSO",
"main": "library.js", "main": "library.js",
"repository": { "repository": {
@ -28,10 +28,32 @@
"readme": "", "readme": "",
"readmeFilename": "README.md", "readmeFilename": "README.md",
"dependencies": { "dependencies": {
"async": "^2",
"passport-oauth": "~1.0.0" "passport-oauth": "~1.0.0"
}, },
"nbbpm": { "nbbpm": {
"compatibility": "^1.0.1", "compatibility": "^1.0.1",
"index": false "index": false
},
"devDependencies": {
"@commitlint/cli": "^8.0.0",
"@commitlint/config-angular": "^7.1.2",
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0",
"husky": "^2.4.0",
"lint-staged": "^8.2.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.js": [
"eslint --fix",
"git add"
]
} }
} }

View File

@ -6,6 +6,7 @@
"library": "./library.js", "library": "./library.js",
"hooks": [ "hooks": [
{ "hook": "static:user.delete", "method": "deleteUserData" }, { "hook": "static:user.delete", "method": "deleteUserData" },
{ "hook": "filter:user.whitelistFields", "method": "whitelistFields" },
{ "hook": "filter:auth.init", "method": "getStrategy" } { "hook": "filter:auth.init", "method": "getStrategy" }
] ]
} }

3070
yarn.lock Normal file

File diff suppressed because it is too large Load Diff