updated sso-oauth plugin for 0.4.0

This commit is contained in:
Julian Lam 2014-03-18 20:04:18 -04:00
parent d755a10b89
commit 843b0c734c
5 changed files with 227 additions and 195 deletions

View File

@ -1,7 +1,18 @@
# 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)!

View File

@ -11,118 +11,139 @@
winston = module.parent.require('winston'),
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({
'name': "Generic OAuth",
'admin': {
'route': '/oauth',
'route': '/plugins/sso-oauth',
'icon': 'fa-key'
}
});
var OAuth = {};
OAuth.getStrategy = function(strategies) {
var oAuthKeys = ['social:oauth:reqTokenUrl', 'social:oauth:accessTokenUrl', 'social:oauth:authUrl', 'social:oauth:key', 'social:oauth:secret'],
oAuth2Keys = ['social:oauth2:authUrl', 'social:oauth2:tokenUrl', 'social:oauth2:id', 'social:oauth2:secret'],
configOk = oAuthKeys.every(function(key) {
return meta.config[key];
}) || oAuth2Keys.every(function(key) {
return meta.config[key];
}),
opts;
if (passportOAuth && configOk) {
if (meta.config['social:oauth:type'] === '1') {
// OAuth options
opts = {
requestTokenURL: meta.config['social:oauth:reqTokenUrl'],
accessTokenURL: meta.config['social:oauth:accessTokenUrl'],
userAuthorizationURL: meta.config['social:oauth:authUrl'],
consumerKey: meta.config['social:oauth:key'],
consumerSecret: meta.config['social:oauth:secret'],
callbackURL: nconf.get('url') + '/auth/generic/callback'
};
passportOAuth.Strategy.prototype.userProfile = function(token, secret, params, done) {
this._oauth.get(meta.config['social:oauth:userProfileUrl'], token, secret, function(err, body, res) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
try {
var json = JSON.parse(body);
var profile = { provider: 'generic' };
// Alter this section to include whatever data is necessary
// NodeBB requires the following: id, displayName, emails, e.g.:
// profile.id = json.id;
// profile.displayName = json.name;
// profile.emails = [{ value: json.email }];
done(null, profile);
} catch(e) {
done(e);
}
});
};
} else if (meta.config['social:oauth:type'] === '2') {
// OAuth 2 options
opts = {
authorizationURL: meta.config['social:oauth2:authUrl'],
tokenURL: meta.config['social:oauth2:tokenUrl'],
clientID: meta.config['social:oauth2:id'],
clientSecret: meta.config['social:oauth2:secret'],
callbackURL: nconf.get('url') + '/auth/generic/callback'
};
passportOAuth.Strategy.prototype.userProfile = function(accessToken, done) {
this._oauth2.get(meta.config['social:oauth:userProfileUrl'], accessToken, function(err, body, res) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
try {
var json = JSON.parse(body);
var profile = { provider: 'generic' };
// Alter this section to include whatever data is necessary
// NodeBB requires the following: id, displayName, emails, e.g.:
// profile.id = json.id;
// profile.displayName = json.name;
// profile.emails = [{ value: json.email }];
done(null, profile);
} catch(e) {
done(e);
}
});
};
}
passport.use('Generic OAuth', new passportOAuth(opts, function(token, secret, profile, done) {
OAuth.login(profile.id, profile.displayName, profile.emails[0].value, function(err, user) {
if (err) {
return done(err);
}
done(null, user);
});
}));
strategies.push({
name: 'Generic OAuth',
url: '/auth/oauth',
callbackURL: '/auth/generic/callback',
icon: 'check',
scope: (meta.config['social:oauth:scope'] || '').split(',')
});
return strategies;
} else {
winston.info('[plugins/sso-oauth] OAuth Disabled or misconfigured. Proceeding without Generic OAuth Login');
return strategies;
OAuth.init = function(app, middleware, controllers) {
function render(req, res, next) {
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) {
return settings[key];
}) || oAuth2Keys.every(function(key) {
return settings[key];
}),
opts;
if (passportOAuth && configOk) {
if (settings['oauth:type'] === '1') {
// OAuth options
opts = {
requestTokenURL: settings['oauth:reqTokenUrl'],
accessTokenURL: settings['oauth:accessTokenUrl'],
userAuthorizationURL: settings['oauth:authUrl'],
consumerKey: settings['oauth:key'],
consumerSecret: settings['oauth:secret'],
callbackURL: nconf.get('url') + '/auth/generic/callback'
};
passportOAuth.Strategy.prototype.userProfile = function(token, secret, params, done) {
this._oauth.get(settings['oauth:userProfileUrl'], token, secret, function(err, body, res) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
try {
var json = JSON.parse(body);
var profile = { provider: 'generic' };
// Uncomment the following lines to include whatever data is necessary
// NodeBB requires the following: id, displayName, emails, e.g.:
// profile.id = json.id;
// profile.displayName = json.name;
// profile.emails = [{ value: json.email }];
done(null, profile);
} catch(e) {
done(e);
}
});
};
} else if (settings['oauth:type'] === '2') {
// OAuth 2 options
opts = {
authorizationURL: settings['oauth2:authUrl'],
tokenURL: settings['oauth2:tokenUrl'],
clientID: settings['oauth2:id'],
clientSecret: settings['oauth2:secret'],
callbackURL: nconf.get('url') + '/auth/generic/callback'
};
passportOAuth.Strategy.prototype.userProfile = function(accessToken, done) {
this._oauth2.get(settings['oauth:userProfileUrl'], accessToken, function(err, body, res) {
if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }
try {
var json = JSON.parse(body);
var profile = { provider: 'generic' };
// Alter this section to include whatever data is necessary
// NodeBB requires the following: id, displayName, emails, e.g.:
// profile.id = json.id;
// profile.displayName = json.name;
// 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);
} catch(e) {
done(e);
}
});
};
}
passport.use('Generic OAuth', new passportOAuth(opts, function(token, secret, profile, done) {
OAuth.login(profile.id, profile.displayName, profile.emails[0].value, function(err, user) {
if (err) {
return done(err);
}
done(null, user);
});
}));
strategies.push({
name: 'Generic OAuth',
url: '/auth/oauth',
callbackURL: '/auth/generic/callback',
icon: 'check',
scope: (settings['oauth:scope'] || '').split(',')
});
callback(null, strategies);
} else {
winston.info('[plugins/sso-oauth] OAuth Disabled or misconfigured. Proceeding without Generic OAuth Login');
callback(null, strategies);
}
});
};
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({
"route": constants.admin.route,
"icon": constants.admin.icon,
"name": constants.name
});
return custom_header;
}
callback(null, custom_header);
};
OAuth.addAdminRoute = function(custom_routes, callback) {
fs.readFile(path.resolve(__dirname, './static/admin.tpl'), function (err, template) {

View File

@ -5,14 +5,10 @@
"url": "https://github.com/julianlam/nodebb-plugin-sso-oauth",
"library": "./library.js",
"hooks": [
{
"hook": "filter:auth.init", "method": "getStrategy", "callbacked": false
},
{
"hook": "filter:admin.header.build", "method": "addMenuItem", "callbacked": false
},
{
"hook": "filter:admin.create_routes", "method": "addAdminRoute", "callbacked": true
}
]
{ "hook": "action:app.load", "method": "init" },
{ "hook": "filter:auth.init", "method": "getStrategy" },
{ "hook": "filter:admin.header.build", "method": "addMenuItem" }
],
"templates": "./templates",
"minver": "0.4.0"
}

View File

@ -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&apos;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>

View 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&apos;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>