diff --git a/common/config.global.js b/common/config.global.js new file mode 100644 index 00000000000..fa435086c24 --- /dev/null +++ b/common/config.global.js @@ -0,0 +1,7 @@ +// The path where to mount the REST API app +exports.restApiRoot = '/api'; +// +// The URL where the browser client can access the REST API is available +// Replace with a full url (including hostname) if your client is being +// served from a different server than your REST API. +exports.restApiUrl = exports.restApiRoot; diff --git a/common/models/User-Credential.json b/common/models/User-Credential.json new file mode 100644 index 00000000000..15a271faa44 --- /dev/null +++ b/common/models/User-Credential.json @@ -0,0 +1,16 @@ +{ + "name": "userCredential", + "plural": "userCredentials", + "base": "UserCredential", + "properties": {}, + "validations": [], + "relations": { + "user": { + "type": "belongsTo", + "model": "user", + "foreignKey": "userId" + } + }, + "acls": [], + "methods": [] +} diff --git a/common/models/User-Identity.js b/common/models/User-Identity.js new file mode 100644 index 00000000000..9879ac1f293 --- /dev/null +++ b/common/models/User-Identity.js @@ -0,0 +1,27 @@ +//var debug = require('debug')('freecc:models:userIdent'); +// +//module.exports = function(UserIdent) { +// +// UserIdent.observe('before save', function(ctx, next) { +// +// var userIdent = ctx.instance; +// userIdent.user(function(err, user) { +// if (err) { return next(err); } +// debug('got user', user.username); +// +// // check if user has picture +// // set user.picture from twitter +// if (!user.picture) { +// debug('use has no pic'); +// user.picture = userIdent.profile.photos[0].value; +// user.save(function(err) { +// if (err) { return next(err); } +// next(); +// }); +// } else { +// debug('exiting after user ident'); +// next(); +// } +// }); +// }); +//}; diff --git a/common/models/User-Identity.json b/common/models/User-Identity.json new file mode 100644 index 00000000000..d63e5c0af34 --- /dev/null +++ b/common/models/User-Identity.json @@ -0,0 +1,16 @@ +{ + "name": "userIdentity", + "plural": "userIdentities", + "base": "UserIdentity", + "properties": {}, + "validations": [], + "relations": { + "user": { + "type": "belongsTo", + "model": "user", + "foreignKey": "userId" + } + }, + "acls": [], + "methods": [] +} diff --git a/common/models/User.js b/common/models/User.js new file mode 100644 index 00000000000..83273683f5d --- /dev/null +++ b/common/models/User.js @@ -0,0 +1,19 @@ +var debug = require('debug')('freecc:models:user'); + +module.exports = function(User) { + debug('setting up user hooks'); + /* + * NOTE(berks): not sure if this is still needed + User.observe('before save', function setUsername(ctx, next) { + // set username from twitter + if (ctx.instance.username && ctx.instance.username.match(/twitter/g)) { + ctx.instance.username = + ctx.instance.username.match(/twitter/g) ? + ctx.instance.username.split('.').pop().toLowerCase() : + ctx.instance.username; + debug('username set', ctx.instance.username); + } + next(); + }); + */ +}; diff --git a/package.json b/package.json index 853c3e9c88c..0560e3c89fb 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "lodash": "~2.4.1", "loopback": "^2.18.0", "loopback-boot": "^2.8.0", + "loopback-component-passport": "^1.3.1", "loopback-connector-mongodb": "^1.10.0", "lusca": "~1.0.2", "method-override": "~2.3.0", diff --git a/server/config.development.js b/server/config.development.js new file mode 100644 index 00000000000..d7df882ace9 --- /dev/null +++ b/server/config.development.js @@ -0,0 +1,18 @@ +module.exports = { + host: '127.0.0.1', + sessionSecret: process.env.SESSION_SECRET, + + trello: { + key: process.env.TRELLO_KEY, + secret: process.env.TRELLO_SECRET + }, + + blogger: { + key: process.env.BLOGGER_KEY + }, + + github: { + clientID: process.env.GITHUB_ID, + clientSecret: process.env.GITHUB_SECRET + } +}; diff --git a/server/config.local.js b/server/config.local.js new file mode 100644 index 00000000000..e5058ced89b --- /dev/null +++ b/server/config.local.js @@ -0,0 +1,5 @@ +var globalConfig = require('../common/config.global'); + +module.exports = { + restApiRoot: globalConfig.restApi +}; diff --git a/server/datasources.development.js b/server/datasources.development.js new file mode 100644 index 00000000000..f7777c5e9ff --- /dev/null +++ b/server/datasources.development.js @@ -0,0 +1,6 @@ +module.exports = { + db: { + connector: 'mongodb', + url: process.env.MONGOHQ_URL + } +}; diff --git a/server/model-config.json b/server/model-config.json index dd5f43e0c65..23f21c295ba 100644 --- a/server/model-config.json +++ b/server/model-config.json @@ -57,5 +57,13 @@ "user": { "dataSource": "db", "public": true + }, + "userCredential": { + "dataSource": "db", + "public": true + }, + "userIdentity": { + "dataSource": "db", + "public": true } } diff --git a/server/passport-providers.js b/server/passport-providers.js new file mode 100644 index 00000000000..36ae6108341 --- /dev/null +++ b/server/passport-providers.js @@ -0,0 +1,149 @@ +var successRedirect = '/'; +var failureRedirect = '/login'; +module.exports = { + local: { + provider: 'local', + module: 'passport-local', + usernameField: 'email', + passwordField: 'password', + authPath: '/auth/local', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + failureFlash: true + }, + 'facebook-login': { + provider: 'facebook', + module: 'passport-facebook', + clientID: process.env.FACEBOOK_ID, + clientSecret: process.env.FACEBOOK_SECRET, + authPath: '/auth/facebook', + callbackURL: '/auth/facebook/callback', + callbackPath: '/auth/facebook/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + scope: ['email'], + failureFlash: true + }, + 'facebook-link': { + provider: 'facebook', + module: 'passport-facebook', + clientID: process.env.FACEBOOK_ID, + clientSecret: process.env.FACEBOOK_SECRET, + authPath: '/link/facebook', + callbackURL: '/link/facebook/callback', + callbackPath: '/link/facebook/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + scope: ['email', 'user_likes'], + link: true, + failureFlash: true + }, + 'github-login': { + provider: 'github', + module: 'passport-github', + clientID: process.env.GITHUB_ID, + clientSecret: process.env.GITHUB_SECRET, + authPath: '/auth/github', + callbackURL: '/auth/github/callback', + callbackPath: '/auth/github/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + scope: ['email'], + failureFlash: true + }, + 'github-link': { + provider: 'github', + module: 'passport-github', + clientID: process.env.GITHUB_ID, + clientSecret: process.env.GITHUB_SECRET, + authPath: '/link/github', + callbackURL: '/link/github/callback', + callbackPath: '/link/github/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + scope: ['email', 'user_likes'], + link: true, + failureFlash: true + }, + 'google-login': { + provider: 'google', + module: 'passport-google-oauth2', + clientID: process.env.GOOGLE_ID, + clientSecret: process.env.GOOGLE_SECRET, + authPath: '/auth/google', + callbackURL: '/auth/google/callback', + callbackPath: '/auth/google/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + scope: ['email', 'profile'], + failureFlash: true + }, + 'google-link': { + provider: 'google', + module: 'passport-google-oauth2', + clientID: process.env.GOOGLE_ID, + clientSecret: process.env.GOOGLE_SECRET, + authPath: '/link/google', + callbackURL: '/link/google/callback', + callbackPath: '/link/google/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + scope: ['email', 'profile'], + link: true, + failureFlash: true + }, + 'twitter-login': { + provider: 'twitter', + authScheme: 'oauth', + module: 'passport-twitter', + authPath: '/auth/twitter', + callbackURL: '/auth/twitter/callback', + callbackPath: '/auth/twitter/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + consumerKey: process.env.TWITTER_KEY, + consumerSecret: process.env.TWITTER_SECRET, + failureFlash: true + }, + 'twitter-link': { + provider: 'twitter', + authScheme: 'oauth', + module: 'passport-twitter', + authPath: '/link/twitter', + callbackURL: '/link/twitter/callback', + callbackPath: '/link/twitter/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + consumerKey: process.env.TWITTER_KEY, + consumerSecret: process.env.TWITTER_SECRET, + failureFlash: true + }, + 'linkedin-login': { + provider: 'linkedin', + authScheme: 'oauth', + module: 'passport-linkedin-oauth2', + authPath: '/auth/linkedin', + callbackURL: '/auth/linkedin/callback', + callbackPath: '/auth/linkedin/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + clientID: process.env.LINKEDIN_ID, + clientSecret: process.env.LINKEDIN_SECRET, + scope: ['r_fullprofile', 'r_emailaddress'], + failureFlash: true + }, + 'linkedin-link': { + provider: 'linkedin', + authScheme: 'oauth', + module: 'passport-linkedin-oauth2', + authPath: '/link/linkedin', + callbackURL: '/link/linkedin/callback', + callbackPath: '/link/linkedin/callback', + successRedirect: successRedirect, + failureRedirect: failureRedirect, + clientID: process.env.LINKEDIN_ID, + clientSecret: process.env.LINKEDIN_SECRET, + scope: ['r_fullprofile', 'r_emailaddress'], + failureFlash: true + } +}; diff --git a/server/server.js b/server/server.js index 7c5581cc01e..06fd5d00c12 100755 --- a/server/server.js +++ b/server/server.js @@ -9,35 +9,39 @@ process.on('uncaughtException', function (err) { process.exit(1); // eslint-disable-line }); -var loopback = require('loopback'), - boot = require('loopback-boot'), - accepts = require('accepts'), - cookieParser = require('cookie-parser'), - compress = require('compression'), - session = require('express-session'), - logger = require('morgan'), - errorHandler = require('errorhandler'), - methodOverride = require('method-override'), - bodyParser = require('body-parser'), - helmet = require('helmet'), - MongoStore = require('connect-mongo')(session), - flash = require('express-flash'), - path = require('path'), - passport = require('passport'), - expressValidator = require('express-validator'), - // request = require('request'), - forceDomain = require('forcedomain'), - lessMiddleware = require('less-middleware'), +var R = require('ramda'), + loopback = require('loopback'), + boot = require('loopback-boot'), + accepts = require('accepts'), + cookieParser = require('cookie-parser'), + compress = require('compression'), + session = require('express-session'), + logger = require('morgan'), + errorHandler = require('errorhandler'), + methodOverride = require('method-override'), + bodyParser = require('body-parser'), + helmet = require('helmet'), + MongoStore = require('connect-mongo')(session), + flash = require('express-flash'), + path = require('path'), + passport = require('passport'), + expressValidator = require('express-validator'), + forceDomain = require('forcedomain'), + lessMiddleware = require('less-middleware'), - /** - * API keys and Passport configuration. - */ - secrets = require('./../config/secrets'); + passportProviders = require('./passport-providers'), + /** + * API keys and Passport configuration. + */ + secrets = require('./../config/secrets'); /** * Create Express server. */ var app = loopback(); +var PassportConfigurator = + require('loopback-component-passport').PassportConfigurator; +var passportConfigurator = new PassportConfigurator(app); app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); @@ -162,6 +166,7 @@ app.use(helmet.csp({ safari5: false })); +passportConfigurator.init(); app.use(function (req, res, next) { // Make user object available in templates. @@ -173,7 +178,11 @@ app.use( loopback.static(path.join(__dirname, '../public'), { maxAge: 86400000 }) ); -boot(app, __dirname); +boot(app, { + appRootDir: __dirname, + dev: process.env.NODE_ENV +}); + app.use(function (req, res, next) { // Remember original destination before login. var path = req.path.split('/')[1]; @@ -186,6 +195,18 @@ app.use(function (req, res, next) { next(); }); +passportConfigurator.setupModels({ + userModel: app.models.user, + userIdentityModel: app.models.userIdentity, + userCredentialModel: app.models.userCredential +}); + +R.keys(passportProviders).map(function(strategy) { + var config = passportProviders[strategy]; + config.session = config.session !== false; + passportConfigurator.configureProvider(strategy, config); +}); + /** * OAuth sign-in routes. */