updated sso-oauth plugin for 0.4.0
This commit is contained in:
parent
d755a10b89
commit
843b0c734c
17
README.md
17
README.md
@ -1,7 +1,18 @@
|
|||||||
# NodeBB OAuth SSO
|
# NodeBB OAuth SSO
|
||||||
|
|
||||||
NodeBB Plugin that allows users to login/register via any configured OAuth provider.
|
NodeBB Plugin that allows users to login/register via any configured OAuth provider. **Please note** that this is not a complete plugin, but merely a skeleton with which you can create your own OAuth SSO plugin for NodeBB (and hopefully share it with others!)
|
||||||
|
|
||||||
## Installation
|
## How to Adapt
|
||||||
|
|
||||||
npm install nodebb-plugin-sso-oauth
|
1. Fork this plugin
|
||||||
|
* ![](http://i.imgur.com/APWHJsa.png)
|
||||||
|
1. Activate it in the plugins page
|
||||||
|
1. Restart your NodeBB
|
||||||
|
1. Fill in the proper information in the "Generic OAuth" page
|
||||||
|
1. Hit "Save" and try to log in with the new OAuth Provider (from `/login`)
|
||||||
|
1. Update profile information (around line 100 of `library.js`) with information from the user API call
|
||||||
|
1. Let NodeBB take care of the rest
|
||||||
|
|
||||||
|
## Trouble?
|
||||||
|
|
||||||
|
Find us on [the community forums](http://community.nodebb.org)!
|
85
library.js
85
library.js
@ -11,53 +11,65 @@
|
|||||||
winston = module.parent.require('winston'),
|
winston = module.parent.require('winston'),
|
||||||
passportOAuth;
|
passportOAuth;
|
||||||
|
|
||||||
if (meta.config['social:oauth:type'] === '2') {
|
|
||||||
passportOAuth = require('passport-oauth').OAuth2Strategy;
|
|
||||||
} else if (meta.config['social:oauth:type'] === '1') {
|
|
||||||
passportOAuth = require('passport-oauth').OAuthStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
var constants = Object.freeze({
|
var constants = Object.freeze({
|
||||||
'name': "Generic OAuth",
|
'name': "Generic OAuth",
|
||||||
'admin': {
|
'admin': {
|
||||||
'route': '/oauth',
|
'route': '/plugins/sso-oauth',
|
||||||
'icon': 'fa-key'
|
'icon': 'fa-key'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var OAuth = {};
|
var OAuth = {};
|
||||||
|
|
||||||
OAuth.getStrategy = function(strategies) {
|
OAuth.init = function(app, middleware, controllers) {
|
||||||
var oAuthKeys = ['social:oauth:reqTokenUrl', 'social:oauth:accessTokenUrl', 'social:oauth:authUrl', 'social:oauth:key', 'social:oauth:secret'],
|
function render(req, res, next) {
|
||||||
oAuth2Keys = ['social:oauth2:authUrl', 'social:oauth2:tokenUrl', 'social:oauth2:id', 'social:oauth2:secret'],
|
res.render('admin/plugins/sso-oauth', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/admin/plugins/sso-oauth', middleware.admin.buildHeader, render);
|
||||||
|
app.get('/api/admin/plugins/sso-oauth', render);
|
||||||
|
|
||||||
|
meta.settings.get('sso-oauth', function(err, settings) {
|
||||||
|
if (settings['oauth:type'] === '2') {
|
||||||
|
passportOAuth = require('passport-oauth').OAuth2Strategy;
|
||||||
|
} else if (settings['oauth:type'] === '1') {
|
||||||
|
passportOAuth = require('passport-oauth').OAuthStrategy;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
OAuth.getStrategy = function(strategies, callback) {
|
||||||
|
meta.settings.get('sso-oauth', function(err, settings) {
|
||||||
|
var oAuthKeys = ['oauth:reqTokenUrl', 'oauth:accessTokenUrl', 'oauth:authUrl', 'oauth:key', 'oauth:secret'],
|
||||||
|
oAuth2Keys = ['oauth2:authUrl', 'oauth2:tokenUrl', 'oauth2:id', 'oauth2:secret'],
|
||||||
configOk = oAuthKeys.every(function(key) {
|
configOk = oAuthKeys.every(function(key) {
|
||||||
return meta.config[key];
|
return settings[key];
|
||||||
}) || oAuth2Keys.every(function(key) {
|
}) || oAuth2Keys.every(function(key) {
|
||||||
return meta.config[key];
|
return settings[key];
|
||||||
}),
|
}),
|
||||||
opts;
|
opts;
|
||||||
|
|
||||||
if (passportOAuth && configOk) {
|
if (passportOAuth && configOk) {
|
||||||
if (meta.config['social:oauth:type'] === '1') {
|
if (settings['oauth:type'] === '1') {
|
||||||
// OAuth options
|
// OAuth options
|
||||||
opts = {
|
opts = {
|
||||||
requestTokenURL: meta.config['social:oauth:reqTokenUrl'],
|
requestTokenURL: settings['oauth:reqTokenUrl'],
|
||||||
accessTokenURL: meta.config['social:oauth:accessTokenUrl'],
|
accessTokenURL: settings['oauth:accessTokenUrl'],
|
||||||
userAuthorizationURL: meta.config['social:oauth:authUrl'],
|
userAuthorizationURL: settings['oauth:authUrl'],
|
||||||
consumerKey: meta.config['social:oauth:key'],
|
consumerKey: settings['oauth:key'],
|
||||||
consumerSecret: meta.config['social:oauth:secret'],
|
consumerSecret: settings['oauth:secret'],
|
||||||
callbackURL: nconf.get('url') + '/auth/generic/callback'
|
callbackURL: nconf.get('url') + '/auth/generic/callback'
|
||||||
};
|
};
|
||||||
|
|
||||||
passportOAuth.Strategy.prototype.userProfile = function(token, secret, params, done) {
|
passportOAuth.Strategy.prototype.userProfile = function(token, secret, params, done) {
|
||||||
this._oauth.get(meta.config['social:oauth:userProfileUrl'], token, secret, function(err, body, res) {
|
this._oauth.get(settings['oauth:userProfileUrl'], token, secret, function(err, body, res) {
|
||||||
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
|
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var json = JSON.parse(body);
|
var json = JSON.parse(body);
|
||||||
|
|
||||||
var profile = { provider: 'generic' };
|
var profile = { provider: 'generic' };
|
||||||
// Alter this section to include whatever data is necessary
|
// Uncomment the following lines to include whatever data is necessary
|
||||||
// NodeBB requires the following: id, displayName, emails, e.g.:
|
// NodeBB requires the following: id, displayName, emails, e.g.:
|
||||||
// profile.id = json.id;
|
// profile.id = json.id;
|
||||||
// profile.displayName = json.name;
|
// profile.displayName = json.name;
|
||||||
@ -69,18 +81,18 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
} else if (meta.config['social:oauth:type'] === '2') {
|
} else if (settings['oauth:type'] === '2') {
|
||||||
// OAuth 2 options
|
// OAuth 2 options
|
||||||
opts = {
|
opts = {
|
||||||
authorizationURL: meta.config['social:oauth2:authUrl'],
|
authorizationURL: settings['oauth2:authUrl'],
|
||||||
tokenURL: meta.config['social:oauth2:tokenUrl'],
|
tokenURL: settings['oauth2:tokenUrl'],
|
||||||
clientID: meta.config['social:oauth2:id'],
|
clientID: settings['oauth2:id'],
|
||||||
clientSecret: meta.config['social:oauth2:secret'],
|
clientSecret: settings['oauth2:secret'],
|
||||||
callbackURL: nconf.get('url') + '/auth/generic/callback'
|
callbackURL: nconf.get('url') + '/auth/generic/callback'
|
||||||
};
|
};
|
||||||
|
|
||||||
passportOAuth.Strategy.prototype.userProfile = function(accessToken, done) {
|
passportOAuth.Strategy.prototype.userProfile = function(accessToken, done) {
|
||||||
this._oauth2.get(meta.config['social:oauth:userProfileUrl'], accessToken, function(err, body, res) {
|
this._oauth2.get(settings['oauth:userProfileUrl'], accessToken, function(err, body, res) {
|
||||||
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
|
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -89,10 +101,18 @@
|
|||||||
var profile = { provider: 'generic' };
|
var profile = { provider: 'generic' };
|
||||||
// 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, e.g.:
|
// NodeBB requires the following: id, displayName, emails, e.g.:
|
||||||
|
|
||||||
// profile.id = json.id;
|
// profile.id = json.id;
|
||||||
// profile.displayName = json.name;
|
// profile.displayName = json.name;
|
||||||
// profile.emails = [{ value: json.email }];
|
// profile.emails = [{ value: json.email }];
|
||||||
|
|
||||||
|
// Find out what is available by uncommenting this line:
|
||||||
|
// console.log(json);
|
||||||
|
|
||||||
|
// Delete or comment out the next TWO (2) lines when you are ready to proceed
|
||||||
|
console.log('===\nAt this point, you\'ll need to customise the above section to id, displayName, and emails into the "profile" object.\n===');
|
||||||
|
return done(new Error('Congrats! So far so good -- please see server log for details'));
|
||||||
|
|
||||||
done(null, profile);
|
done(null, profile);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
done(e);
|
done(e);
|
||||||
@ -115,14 +135,15 @@
|
|||||||
url: '/auth/oauth',
|
url: '/auth/oauth',
|
||||||
callbackURL: '/auth/generic/callback',
|
callbackURL: '/auth/generic/callback',
|
||||||
icon: 'check',
|
icon: 'check',
|
||||||
scope: (meta.config['social:oauth:scope'] || '').split(',')
|
scope: (settings['oauth:scope'] || '').split(',')
|
||||||
});
|
});
|
||||||
|
|
||||||
return strategies;
|
callback(null, strategies);
|
||||||
} else {
|
} else {
|
||||||
winston.info('[plugins/sso-oauth] OAuth Disabled or misconfigured. Proceeding without Generic OAuth Login');
|
winston.info('[plugins/sso-oauth] OAuth Disabled or misconfigured. Proceeding without Generic OAuth Login');
|
||||||
return strategies;
|
callback(null, strategies);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
OAuth.login = function(oAuthid, handle, email, callback) {
|
OAuth.login = function(oAuthid, handle, email, callback) {
|
||||||
@ -177,15 +198,15 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
OAuth.addMenuItem = function(custom_header) {
|
OAuth.addMenuItem = function(custom_header, callback) {
|
||||||
custom_header.authentication.push({
|
custom_header.authentication.push({
|
||||||
"route": constants.admin.route,
|
"route": constants.admin.route,
|
||||||
"icon": constants.admin.icon,
|
"icon": constants.admin.icon,
|
||||||
"name": constants.name
|
"name": constants.name
|
||||||
});
|
});
|
||||||
|
|
||||||
return custom_header;
|
callback(null, custom_header);
|
||||||
}
|
};
|
||||||
|
|
||||||
OAuth.addAdminRoute = function(custom_routes, callback) {
|
OAuth.addAdminRoute = function(custom_routes, callback) {
|
||||||
fs.readFile(path.resolve(__dirname, './static/admin.tpl'), function (err, template) {
|
fs.readFile(path.resolve(__dirname, './static/admin.tpl'), function (err, template) {
|
||||||
|
16
plugin.json
16
plugin.json
@ -5,14 +5,10 @@
|
|||||||
"url": "https://github.com/julianlam/nodebb-plugin-sso-oauth",
|
"url": "https://github.com/julianlam/nodebb-plugin-sso-oauth",
|
||||||
"library": "./library.js",
|
"library": "./library.js",
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{ "hook": "action:app.load", "method": "init" },
|
||||||
"hook": "filter:auth.init", "method": "getStrategy", "callbacked": false
|
{ "hook": "filter:auth.init", "method": "getStrategy" },
|
||||||
},
|
{ "hook": "filter:admin.header.build", "method": "addMenuItem" }
|
||||||
{
|
],
|
||||||
"hook": "filter:admin.header.build", "method": "addMenuItem", "callbacked": false
|
"templates": "./templates",
|
||||||
},
|
"minver": "0.4.0"
|
||||||
{
|
|
||||||
"hook": "filter:admin.create_routes", "method": "addAdminRoute", "callbacked": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
<h1><i class="fa fa-key"></i> Generic OAuth Authentication</h1>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<form>
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<p>
|
|
||||||
Please refer to your OAuth provider's documentation for appropriate values. All fields are mandatory.
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
<select data-field="social:oauth:type" title="OAuth Strategy" class="form-control">
|
|
||||||
<option value="x">Disabled</option>
|
|
||||||
<option value="1">OAuth</option>
|
|
||||||
<option value="2">OAuth2</option>
|
|
||||||
</select>
|
|
||||||
<hr />
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="1" data-field="social:oauth:key" title="OAuth Key" class="form-control input-lg" placeholder="OAuth Key">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="1" data-field="social:oauth:secret" title="OAuth Secret" class="form-control" placeholder="OAuth Secret">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="1" data-field="social:oauth:reqTokenUrl" title="Token Request URL" class="form-control" placeholder="Token Request URL">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="1" data-field="social:oauth:accessTokenUrl" title="Access Token URL" class="form-control" placeholder="Access Token URL">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="1" data-field="social:oauth:authUrl" title="Authorization URL" class="form-control" placeholder="Authorization URL">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="2" data-field="social:oauth2:id" title="Client ID" class="form-control input-lg" placeholder="Client ID">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="2" data-field="social:oauth2:secret" title="Client Secret" class="form-control" placeholder="Client Secret">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="2" data-field="social:oauth2:authUrl" title="Authorization URL" class="form-control" placeholder="Authorization URL">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-strategy="2" data-field="social:oauth2:tokenUrl" title="Token URL" class="form-control" placeholder="Token URL">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="text" data-field="social:oauth:userProfileUrl" title="User Profile URL" class="form-control" placeholder="User Profile URL">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<button class="btn btn-lg btn-primary" id="save">Save</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
require(['forum/admin/settings'], function(Settings) {
|
|
||||||
Settings.prepare(function() {
|
|
||||||
var OAuthType = $('[data-field="social:oauth:type"]').val();
|
|
||||||
toggleFields(OAuthType);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var toggleFields = function(value) {
|
|
||||||
if (value === '1') {
|
|
||||||
$('[data-strategy="2"]').hide();
|
|
||||||
$('[data-strategy="1"]').show();
|
|
||||||
} else if (value === '2') {
|
|
||||||
$('[data-strategy="1"]').hide();
|
|
||||||
$('[data-strategy="2"]').show();
|
|
||||||
} else {
|
|
||||||
$('[data-strategy]').hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleFields(false);
|
|
||||||
$('[data-field="social:oauth:type"]').on('change', function() {
|
|
||||||
toggleFields(this.value);
|
|
||||||
})
|
|
||||||
</script>
|
|
81
templates/admin/plugins/sso-oauth.tpl
Normal file
81
templates/admin/plugins/sso-oauth.tpl
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<h1><i class="fa fa-key"></i> Generic OAuth Authentication</h1>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<form class="sso-oauth-settings">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<p>
|
||||||
|
Please refer to your OAuth provider's documentation for appropriate values. All fields are mandatory.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<select name="oauth:type" title="OAuth Strategy" class="form-control">
|
||||||
|
<option value="x">Disabled</option>
|
||||||
|
<option value="1">OAuth</option>
|
||||||
|
<option value="2">OAuth2</option>
|
||||||
|
</select>
|
||||||
|
<hr />
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="1" name="oauth:key" title="OAuth Key" class="form-control input-lg" placeholder="OAuth Key">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="1" name="oauth:secret" title="OAuth Secret" class="form-control" placeholder="OAuth Secret">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="1" name="oauth:reqTokenUrl" title="Token Request URL" class="form-control" placeholder="Token Request URL">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="1" name="oauth:accessTokenUrl" title="Access Token URL" class="form-control" placeholder="Access Token URL">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="1" name="oauth:authUrl" title="Authorization URL" class="form-control" placeholder="Authorization URL">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="2" name="oauth2:id" title="Client ID" class="form-control input-lg" placeholder="Client ID">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="2" name="oauth2:secret" title="Client Secret" class="form-control" placeholder="Client Secret">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="2" name="oauth2:authUrl" title="Authorization URL" class="form-control" placeholder="Authorization URL">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" data-strategy="2" name="oauth2:tokenUrl" title="Token URL" class="form-control" placeholder="Token URL">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="oauth:userProfileUrl" title="User Profile URL" class="form-control" placeholder="User Profile URL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<button class="btn btn-lg btn-primary" type="button" id="save">Save</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
require(['settings'], function(Settings) {
|
||||||
|
Settings.load('sso-oauth', $('.sso-oauth-settings'), function() {
|
||||||
|
var OAuthType = $('[name="oauth:type"]').val();
|
||||||
|
toggleFields(OAuthType);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#save').on('click', function() {
|
||||||
|
Settings.save('sso-oauth', $('.sso-oauth-settings'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var toggleFields = function(value) {
|
||||||
|
if (value === '1') {
|
||||||
|
$('[data-strategy="2"]').hide();
|
||||||
|
$('[data-strategy="1"]').show();
|
||||||
|
} else if (value === '2') {
|
||||||
|
$('[data-strategy="1"]').hide();
|
||||||
|
$('[data-strategy="2"]').show();
|
||||||
|
} else {
|
||||||
|
$('[data-strategy]').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFields(false);
|
||||||
|
$('[name="oauth:type"]').on('change', function() {
|
||||||
|
toggleFields(this.value);
|
||||||
|
})
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user