Merge pull request #458 from coralproject/docker-plugins

Plugin Router/Passport/Deployment
This commit is contained in:
David Erwin
2017-04-04 16:56:02 -04:00
committed by GitHub
25 changed files with 734 additions and 137 deletions
+15
View File
@@ -1,3 +1,18 @@
# excluded because we'll likely need to rebuild this.
node_modules
# scripts are used during development and testing, not
# production.
scripts
# documentation should not be visable in production.
docs
# static assets are rebuild in the docker container.
dist
# tests are not run in the docker container.
test
# we won't use the .git folder in production.
.git
+2
View File
@@ -1,3 +1,5 @@
dist
client/lib
**/*.html
plugins/
plugins/**/node_modules
+1 -9
View File
@@ -1,20 +1,12 @@
node_modules
npm-debug.log*
dist
!dist/coral-admin
dist/coral-admin/bundle.js
test/e2e/reports
.DS_Store
*.iml
*.swp
dump.rdb
.env
*.cfg
.idea/
coverage/
.tags
.tags1
# remove plugin folders
*.swp
plugins
plugins.json
+10 -5
View File
@@ -5,16 +5,21 @@ RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Setup the environment
ENV NODE_ENV production
ENV PATH /usr/src/app/bin:$PATH
ENV TALK_PORT 5000
EXPOSE 5000
# Install app dependencies
COPY package.json yarn.lock /usr/src/app/
RUN yarn install --production
# Bundle app source
COPY . /usr/src/app
# Install app dependencies and build static assets.
RUN yarn install --frozen-lockfile && \
cli plugins reconcile && \
yarn build && \
yarn install --production && \
yarn cache clean
# Ensure the runtime of the container is in production mode.
ENV NODE_ENV production
CMD ["yarn", "start"]
+14
View File
@@ -0,0 +1,14 @@
FROM coralproject/talk:latest
# Bundle app source
ONBUILD COPY . /usr/src/app
# At this stage, we need to install the development dependancies again because
# we need to have webpack available. We then build the new dependancies and
# clear out the development dependancies again. After this we of course need to
# clear out the yarn cache, this saves quite a lot of size.
ONBUILD RUN NODE_ENV=development yarn install --frozen-lockfile && \
NODE_ENV=production cli plugins reconcile && \
NODE_ENV=production yarn build && \
NODE_ENV=production yarn install --production && \
yarn cache clean
+119 -1
View File
@@ -9,7 +9,7 @@ All plugins must be registered in the root file `plugins.json`.
The format for this file is thus:
```js
```json
{
"server": [
"people"
@@ -22,6 +22,42 @@ name in the `plugins/` folder. For example, the above `plugins.json` would
require a plugin from `plugins/people`, which must provide a `index.js` file
that returns an object that matches the Plugin Specification.
If the package is external (available on NPM) you can specify the string for
the version by using an object instead, for example:
```json
{
"server": [
{"people": "^1.2.0"}
]
}
```
External plugins can be resolved by running:
```bash
./bin/cli plugins reconcile
```
This will also traverse into local plugin folders and install their
dependancies. _Note that if the plugin is already installed and available in the
node_modules folder, it will not be fetched again unless there is a version
mismatch._
## Plugin Dependencies
From your plugins you may import any component of server code relative to the
project root. An example could be:
```js
const cache = require('services/cache');
```
You may also include additional external depenancies in your local packages by
specifying a `package.json` at your plugin root which will result in a
`node_modules` folder being generated at the plugin root with your specific
dependencies.
## Server Plugins
### Specification
@@ -170,6 +206,88 @@ If your post function accepts four parameters, then it can modify the field
result. It is *required* that the function resolves a promise (or returns) with
the modified value or simply the original if you didn't modify it.
#### Field: `router`
```js
(router) => {
router.get('/api/v1/people', (req, res) => {
res.json({people: [{name: 'Bob'}]});
});
}
```
The Router hook allows you to create a function that accepts the base express
router where you can mount any amount of middleware/routes to do any form of
action needed by external applications. We also provide the authorization
middleware via:
```js
const authorization = require('middleware/authorization');
module.exports = {
router(router) {
router.get('/api/v1/people', authorization.needed('ADMIN'), (req, res) => {
res.json({people: [{name: 'SECRET PEOPLE'}]});
});
}
}
```
#### Field: `passport`
```js
const FacebookStrategy = require('passport-facebook').Strategy;
const UsersService = require('services/users');
const {ValidateUserLogin, HandleAuthPopupCallback} = require('services/passport');
module.exports = {
passport(passport) {
passport.use(new FacebookStrategy({
clientID: process.env.TALK_FACEBOOK_APP_ID,
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`,
passReqToCallback: true,
profileFields: ['id', 'displayName', 'picture.type(large)']
}, async (req, accessToken, refreshToken, profile, done) => {
let user;
try {
user = await UsersService.findOrCreateExternalUser(profile);
} catch (err) {
return done(err);
}
return ValidateUserLogin(profile, user, done);
}));
},
router(router) {
// Note that we have to import the passport instance here, it is
// instantiated after all the strategies have been mounted.
const {passport} = require('services/passport');
/**
* Facebook auth endpoint, this will redirect the user immediatly to facebook
* for authorization.
*/
router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']}));
/**
* Facebook callback endpoint, this will send the user a html page designed to
* send back the user credentials upon sucesfull login.
*/
router.get('/facebook/callback', (req, res, next) => {
// Perform the facebook login flow and pass the data back through the opener.
passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next);
});
}
};
```
This is a full example including the routes hook to add the required components
to the application router to support a different auth strategy.
### Full Example
Contents of `plugins.json`:
+1 -1
View File
@@ -3,7 +3,7 @@ const bodyParser = require('body-parser');
const morgan = require('morgan');
const path = require('path');
const helmet = require('helmet');
const passport = require('./services/passport');
const {passport} = require('./services/passport');
const session = require('express-session');
const enabled = require('debug').enabled;
const RedisStore = require('connect-redis')(session);
+1
View File
@@ -13,6 +13,7 @@ program
.command('setup', 'setup the application')
.command('jobs', 'work with the job queues')
.command('users', 'work with the application auth')
.command('plugins', 'provides utilities for interacting with the plugin system')
.parse(process.argv);
/**
+297
View File
@@ -0,0 +1,297 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
// Interface heavily inspired by the yarn package manager:
// https://yarnpkg.com/
const program = require('./commander');
// Make things colorful!
require('colors');
const emoji = require('node-emoji');
const dir = process.cwd();
const fs = require('fs');
const path = require('path');
const spawn = require('cross-spawn');
const semver = require('semver');
const resolve = require('resolve');
const {plugins, itteratePlugins, isInternal} = require('../plugins');
function existsInNodeModules(name) {
try {
resolve.sync(name, {basedir: dir});
return true;
} catch (e) {
return false;
}
}
function versionMatch(name, version) {
try {
let matched = false;
resolve.sync(name, {
basedir: dir,
packageFilter: (pkg) => {
if (pkg && pkg.version && semver.satisfies(pkg.version, version)) {
matched = true;
}
return pkg;
}
});
return matched;
} catch (e) {
return false;
}
}
const EXTERNAL = /^\w[a-z\-0-9\.]+$/; // Match "react", "path", "fs", "lodash.random", etc.
function reconcilePackages({quiet = false, upgradeRemote = false}) {
const fetchable = [];
const local = [];
const upgradable = [];
if (!quiet) {
console.log();
console.log(' +local (l) packages in your project');
console.log(' +external (e) packages are external');
console.log(' +outofdate (oe) packages are external but are out of date');
console.log(' +missing (m) packages are not found');
console.log();
}
for (let i in plugins) {
let section = itteratePlugins(plugins[i]);
for (let j in section) {
let {name, version} = section[j];
let namespaced = name.charAt(0) === '@';
let dep = name.split('/')
.slice(0, namespaced ? 2 : 1)
.join('/');
// Ignore relative modules, which aren't installed by NPM
if (!dep.match(EXTERNAL) && !namespaced) {
return;
}
if (isInternal(dep)) {
if (!quiet) {
console.log(` l ${name}`);
}
local.push({name, version});
continue;
}
if (!existsInNodeModules(dep)) {
if (!quiet) {
console.log(` m ${name}`);
}
fetchable.push({name, version});
} else if (!versionMatch(dep, version)) {
// A plugin was found, yet the current version does not match the
// current version installed. We should warn if upgradeRemote is
// not enabled that it is currently not supported.
if (!upgradeRemote) {
if (!quiet) {
console.warn(` oe ${name} (package upgrade may be required)`.bgRed);
}
continue;
}
console.log(` oe ${name} (package upgrade may be required)`);
upgradable.push({name, version});
} else {
if (!quiet) {
console.log(` e ${name}`);
}
if (upgradeRemote) {
upgradable.push({name, version});
}
}
}
}
if (!quiet) {
console.log();
}
return {local, fetchable, upgradable};
}
async function reconcileRemotePlugins({skipLocal, dryRun, upgradeRemote}) {
console.log(`\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get('mag')} Reconciling plugins...`.yellow);
const {fetchable, upgradable} = reconcilePackages({upgradeRemote});
console.log(`[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n`.yellow);
if (fetchable.length > 0) {
console.log(`$ yarn add ${fetchable.map(({name, version}) => `${name}@${version}`.cyan)}`);
if (!dryRun) {
let args = [
'add',
...fetchable.map(({name, version}) => `${name}@${version}`)
];
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit']
});
if (output.status) {
throw new Error('Could not install external plugins, errors occured during install');
}
console.log(output.stdout.toString());
}
}
if (upgradable.length > 0) {
console.log(`$ yarn upgrade ${upgradable.map(({name, version}) => `${name}@${version}`.cyan)}`);
if (!dryRun) {
let args = [
'upgrade',
...upgradable.map(({name, version}) => `${name}@${version}`)
];
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit']
});
if (output.status) {
throw new Error('Could not install external plugins, errors occured during install');
}
console.log(output.stdout.toString());
}
}
return {upgradable, fetchable};
}
async function reconcileLocalPlugins({skipRemote, dryRun}) {
console.log(`\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get('pick')} Installing local plugin dependencies...\n`.yellow);
const {local} = reconcilePackages({quiet: true});
for (let i in local) {
let {name} = local[i];
if (!fs.existsSync(path.join(dir, 'plugins', name, 'package.json'))) {
continue;
}
let wd = path.join(dir, 'plugins', name);
console.log(`$ cd ${wd.cyan} && yarn`);
if (!dryRun) {
let args = [];
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit'],
cwd: wd
});
if (output.status) {
throw new Error('Could not install local plugin dependencies, errors occured during install');
}
console.log(output.stdout.toString());
}
}
}
// This traverses the local plugins and installs any dependencies listed there,
// this only is really needed for plugins that are installed via docker because
// core plugins will have their dependencies already included in core.
async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote}) {
let startTime = new Date();
// We don't need to do anything if we skip everything....
if (skipLocal && skipRemote) {
return;
}
// Traverse local plugins and install dependencies if enabled.
if (!skipLocal) {
await reconcileLocalPlugins({skipRemote, dryRun});
}
// Locate any external plugins and install them.
if (!skipRemote) {
let results = [];
try {
results = await reconcileRemotePlugins({skipLocal, skipRemote, dryRun, upgradeRemote});
} catch (e) {
throw e;
}
let status;
if (dryRun) {
status = '[dry-run] success'.green;
} else {
status = 'success'.green;
}
let message;
if (results.upgradable.length === 0 && results.fetchable.length === 0) {
message = 'Already up-to-date.';
} else if (results.upgradable.length === 0) {
message = `Fetched ${results.fetchable.length} new plugins.`;
} else if (results.fetchable.length === 0) {
message = `Upgraded ${results.upgradable.length} new plugins.`;
} else {
message = `Fetched ${results.fetchable.length} new plugins, upgraded ${results.upgradable.length} plugins.`;
}
console.log(`\n${status} ${message}`);
}
let endTime = new Date();
let totalTime = ((endTime.getTime() - startTime.getTime()) / 1000).toFixed(2);
console.log(`✨ Done in ${totalTime}s.`);
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('list')
.description('')
.action(reconcilePackages);
program
.command('reconcile')
.description('reconciles local plugin dependencies and downloads external plugins')
.option('-u, --upgrade-remote', 'upgrades remote dependencies')
.option('-d, --dry-run', 'does not actually change anything on the filesystem acts only as a simulation')
.option('--skip-local', 'skips the local dependancy reconciliation')
.option('--skip-remote', 'skips the remote plugin reconciliation')
.action(reconcilePluginDeps);
program.parse(process.argv);
// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
program.outputHelp();
}
+4 -3
View File
@@ -1,6 +1,6 @@
machine:
node:
version: 7.6
version: 7
services:
- docker
- redis
@@ -20,6 +20,7 @@ dependencies:
# - sudo service mongod restart
# Install node dependencies.
- yarn --version
- yarn
cache_directories:
- ~/.cache/yarn
@@ -47,9 +48,9 @@ deployment:
release:
tag: /v[0-9]+(\.[0-9]+)*/
commands:
- bash ./scripts/deploy.sh
- bash ./scripts/docker.sh deploy
latest:
branch: master
commands:
- bash ./scripts/deploy.sh
- bash ./scripts/docker.sh deploy
-23
View File
@@ -1,23 +0,0 @@
# Coral Admin
This app handles moderation for Talk (and maybe more later on)
## Installation
$ yarn install
$ cp config.sample.json config.json
Then change `config.json` to adjust it to your project
## Building for production
$ yarn build
The public folder has everything you need for deployment. You can just copy that folder to your favorite static web server
## Development
$ yarn start
A development server will be running at http://localhost:4132
+1 -1
View File
@@ -1,7 +1,7 @@
const loaders = require('./loaders');
const mutators = require('./mutators');
const plugins = require('../plugins');
const plugins = require('../services/plugins');
const debug = require('debug')('talk:graph:context');
/**
+1 -1
View File
@@ -8,7 +8,7 @@ const Metrics = require('./metrics');
const Settings = require('./settings');
const Users = require('./users');
const plugins = require('../../plugins');
const plugins = require('../../services/plugins');
let loaders = [
+1 -1
View File
@@ -5,7 +5,7 @@ const Comment = require('./comment');
const Action = require('./action');
const User = require('./user');
const plugins = require('../../plugins');
const plugins = require('../../services/plugins');
let mutators = [
+1 -1
View File
@@ -20,7 +20,7 @@ const UserError = require('./user_error');
const User = require('./user');
const ValidationUserError = require('./validation_user_error');
const plugins = require('../../plugins');
const plugins = require('../../services/plugins');
// Provide the core resolvers.
let resolvers = {
+1 -1
View File
@@ -2,7 +2,7 @@ const {makeExecutableSchema} = require('graphql-tools');
const {maskErrors} = require('graphql-errors');
const {decorateWithHooks} = require('./hooks');
const plugins = require('../plugins');
const plugins = require('../services/plugins');
const resolvers = require('./resolvers');
const typeDefs = require('./typeDefs');
+1 -1
View File
@@ -6,7 +6,7 @@ const fs = require('fs');
const path = require('path');
const {mergeStrings} = require('gql-merge');
const debug = require('debug')('talk:graph:typeDefs');
const plugins = require('../plugins');
const plugins = require('../services/plugins');
/**
* Plugin support requires us to merge the type definitions from the loaded
+6
View File
@@ -49,11 +49,14 @@
},
"homepage": "https://github.com/coralproject/talk#readme",
"dependencies": {
"app-module-path": "^2.2.0",
"bcrypt": "^1.0.2",
"body-parser": "^1.17.1",
"cli-table": "^0.3.1",
"colors": "^1.1.2",
"commander": "^2.9.0",
"connect-redis": "^3.1.0",
"cross-spawn": "^5.1.0",
"csurf": "^1.9.0",
"dataloader": "^1.3.0",
"debug": "^2.6.3",
@@ -80,6 +83,7 @@
"mongoose": "^4.9.1",
"morgan": "^1.8.1",
"natural": "^0.4.0",
"node-emoji": "^1.5.1",
"node-fetch": "^1.6.3",
"nodemailer": "^2.6.4",
"parse-duration": "^0.1.1",
@@ -89,6 +93,8 @@
"react-apollo": "^0.10.0",
"react-recaptcha": "^2.2.6",
"redis": "^2.7.1",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"simplemde": "^1.11.2",
"uuid": "^2.0.3"
},
+101 -9
View File
@@ -1,5 +1,10 @@
const fs = require('fs');
const path = require('path');
const resolve = require('resolve');
const debug = require('debug')('talk:plugins');
// Add support for require rewriting.
require('app-module-path').addPath(__dirname);
let plugins = {};
@@ -16,17 +21,95 @@ try {
}
}
/**
* isInternal checks to see if a given plugin is internal, and returns true
* if it is.
*
* @param {String} name
* @returns {Boolean}
*/
function isInternal(name) {
const internalPluginPath = path.join(__dirname, 'plugins', name);
// Check to see if this plugin exists internally, because if it doesn't, it is
// external.
return fs.existsSync(internalPluginPath);
}
/**
* Returns the plugin path for the given plugin name.
*
* @param {any} name
* @returns
*/
function pluginPath(name) {
if (isInternal(name)) {
return path.join(__dirname, 'plugins', name);
}
try {
return resolve.sync(name, {basedir: process.cwd()});
} catch (e) {
return undefined;
}
}
function itteratePlugins(plugins) {
return plugins.map((p) => {
let plugin = {};
// This checks to see if the structure for this entry is an object:
//
// {"people": "^1.2.0"}
//
// otherwise it's checked whether it matches the local version:
//
// "people"
//
if (typeof p === 'object') {
plugin.name = Object.keys(p).find((name) => name !== null);
plugin.version = p[plugin.name];
} else if (typeof p === 'string') {
plugin.name = p;
plugin.version = `file:./plugins/${plugin.name}`;
} else {
throw new Error(`plugins.json is malformed, refer to PLUGINS.md for formatting, expected a string or an object for a plugin entry, found a ${typeof p}`);
}
// Get the path for the plugin.
plugin.path = pluginPath(plugin.name);
return plugin;
});
}
/**
* Stores a reference to a section for a section of Plugins.
*/
class PluginSection {
constructor(plugin_names) {
this.plugins = plugin_names.map((plugin_name) => {
let plugin = require(`./plugins/${plugin_name}`);
constructor(plugins) {
this.plugins = itteratePlugins(plugins).map((plugin) => {
if (typeof plugin.path === 'undefined') {
throw new Error(`plugin '${plugin.name}' is not local and is not resolvable, plugin reconsiliation may be required`);
}
// Ensure we have a default plugin name, but allow the name to be
// overrided by the plugin.
plugin.name = plugin.name || plugin_name;
try {
plugin.module = require(plugin.path);
} catch (e) {
if (e && e.code && e.code === 'MODULE_NOT_FOUND' && isInternal(plugin.name)) {
console.error(new Error(`plugin '${plugin.name}' could not be loaded due to missing dependencies, plugin reconsiliation may be required`));
throw e;
}
console.error(new Error(`plugin '${plugin.name}' could not be required from '${plugin.path}': ${e.message}`));
throw e;
}
if (isInternal(plugin.name)) {
debug(`loading internal plugin '${plugin.name}' from '${plugin.path}'`);
} else {
debug(`loading external plugin '${plugin.name}' from '${plugin.path}'`);
}
return plugin;
});
@@ -38,8 +121,11 @@ class PluginSection {
*/
hook(hook) {
return this.plugins
.filter((plugin) => hook in plugin)
.map((plugin) => ({plugin, [hook]: plugin[hook]}));
.filter(({module}) => hook in module)
.map((plugin) => ({
plugin,
[hook]: plugin.module[hook]
}));
}
}
@@ -78,4 +164,10 @@ class PluginManager {
}
}
module.exports = new PluginManager(plugins);
module.exports = {
plugins,
PluginManager,
isInternal,
pluginPath,
itteratePlugins
};
+1 -49
View File
@@ -1,7 +1,6 @@
const express = require('express');
const passport = require('../../../services/passport');
const {passport, HandleAuthCallback, HandleAuthPopupCallback} = require('../../../services/passport');
const authorization = require('../../../middleware/authorization');
const errors = require('../../../errors');
const router = express.Router();
@@ -34,53 +33,6 @@ router.delete('/', authorization.needed(), (req, res) => {
// PASSPORT ROUTES
//==============================================================================
/**
* This sends back the user data as JSON.
*/
const HandleAuthCallback = (req, res, next) => (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return next(errors.ErrNotAuthorized);
}
// Perform the login of the user!
req.logIn(user, (err) => {
if (err) {
return next(err);
}
// We logged in the user! Let's send back the user data and the CSRF token.
res.json({user});
});
};
/**
* Returns the response to the login attempt via a popup callback with some JS.
*/
const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
if (err) {
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
}
if (!user) {
return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null});
}
// Perform the login of the user!
req.logIn(user, (err) => {
if (err) {
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
}
// We logged in the user! Let's send back the user data.
res.render('auth-callback', {err: null, data: JSON.stringify(user)});
});
};
/**
* Local auth endpoint, will recieve a email and password
*/
+10
View File
@@ -1,5 +1,7 @@
const express = require('express');
const path = require('path');
const plugins = require('../services/plugins');
const debug = require('debug')('talk:routes');
const router = express.Router();
@@ -22,4 +24,12 @@ if (process.env.NODE_ENV !== 'production') {
});
}
// Inject server route plugins.
plugins.get('server', 'router').forEach((plugin) => {
debug(`added plugin '${plugin.plugin.name}'`);
// Pass the root router to the plugin to mount it's routes.
plugin.router(router);
});
module.exports = router;
+24 -11
View File
@@ -2,9 +2,7 @@
set -e
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
# Sourced from https://segment.com/blog/ci-at-segment/
# Inspired by https://segment.com/blog/ci-at-segment/
deploy_tag() {
# Find our individual versions from the tags
@@ -28,6 +26,7 @@ deploy_tag() {
do
echo "==> tagging $version"
docker tag coralproject/talk:latest coralproject/talk:$version
docker tag coralproject/talk:latest-onbuild coralproject/talk:$version-onbuild
done
# Push each of the tags to docker hub, including latest
@@ -35,21 +34,35 @@ deploy_tag() {
do
echo "==> pushing $version"
docker push coralproject/talk:$version
docker push coralproject/talk:$version-onbuild
done
}
deploy_latest() {
echo "==> pushing latest"
docker push coralproject/talk:latest
docker push coralproject/talk:latest-onbuild
}
# build the repo
docker build -t coralproject/talk .
# build the repo, including the onbuild tagged versions.
docker build -t coralproject/talk:latest -f Dockerfile .
docker build -t coralproject/talk:latest-onbuild -f Dockerfile.onbuild .
# deploy based on the env
if [ -n "$CIRCLE_TAG" ]
if [ "$1" = "deploy" ]
then
deploy_tag
else
deploy_latest
fi
if [[ -n "$DOCKER_EMAIL" && -n "$DOCKER_USER" && -n "$DOCKER_PASS" ]]
then
# Log the Docker Daemon in
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
fi
# deploy based on the env
if [ -n "$CIRCLE_TAG" ]
then
deploy_tag
else
deploy_latest
fi
fi
+63 -1
View File
@@ -7,6 +7,7 @@ const LocalStrategy = require('passport-local').Strategy;
const FacebookStrategy = require('passport-facebook').Strategy;
const errors = require('../errors');
const debug = require('debug')('talk:passport');
const plugins = require('./plugins');
//==============================================================================
// SESSION SERIALIZATION
@@ -27,6 +28,52 @@ passport.deserializeUser((id, done) => {
});
});
/**
* This sends back the user data as JSON.
*/
const HandleAuthCallback = (req, res, next) => (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return next(errors.ErrNotAuthorized);
}
// Perform the login of the user!
req.logIn(user, (err) => {
if (err) {
return next(err);
}
// We logged in the user! Let's send back the user data and the CSRF token.
res.json({user});
});
};
/**
* Returns the response to the login attempt via a popup callback with some JS.
*/
const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
if (err) {
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
}
if (!user) {
return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null});
}
// Perform the login of the user!
req.logIn(user, (err) => {
if (err) {
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
}
// We logged in the user! Let's send back the user data.
res.render('auth-callback', {err: null, data: JSON.stringify(user)});
});
};
/**
* Validates that a user is allowed to login.
* @param {User} user the user to be validated
@@ -329,4 +376,19 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET &&
console.error('Facebook cannot be enabled, missing one of TALK_FACEBOOK_APP_ID, TALK_FACEBOOK_APP_SECRET, TALK_ROOT_URL');
}
module.exports = passport;
// Inject server route plugins.
plugins.get('server', 'passport').forEach((plugin) => {
debug(`added plugin '${plugin.plugin.name}'`);
// Pass the passport.js instance to the plugin to allow it to inject it's
// functionality.
plugin.passport(passport);
});
module.exports = {
passport,
ValidateUserLogin,
HandleFailedAttempt,
HandleAuthCallback,
HandleAuthPopupCallback
};
+3
View File
@@ -0,0 +1,3 @@
const {plugins, PluginManager} = require('../plugins');
module.exports = new PluginManager(plugins);
+56 -19
View File
@@ -189,6 +189,10 @@ apollo-client@^0.8.3:
"@types/graphql" "^0.8.0"
"@types/isomorphic-fetch" "0.0.30"
app-module-path@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5"
"apparatus@>= 0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/apparatus/-/apparatus-0.0.9.tgz#37dcd25834ad0b651076596291db823eeb1908bd"
@@ -1641,7 +1645,7 @@ colors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
colors@1.1.2, colors@~1.1.2:
colors@1.1.2, colors@^1.1.2, colors@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
@@ -1883,6 +1887,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2:
create-hash "^1.1.0"
inherits "^2.0.1"
cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
dependencies:
lru-cache "^4.0.1"
shebang-command "^1.2.0"
which "^1.2.9"
crypt@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
@@ -4763,6 +4775,13 @@ lru-cache@2, lru-cache@^2.5.0:
version "2.7.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
lru-cache@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
dependencies:
pseudomap "^1.0.1"
yallist "^2.0.0"
lru-cache@~2.6.5:
version "2.6.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"
@@ -5151,6 +5170,12 @@ node-dir@^0.1.10:
dependencies:
minimatch "^3.0.2"
node-emoji@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.5.1.tgz#fd918e412769bf8c448051238233840b2aff16a1"
dependencies:
string.prototype.codepointat "^0.2.0"
node-fetch@^1.0.1, node-fetch@^1.3.3, node-fetch@^1.6.3:
version "1.6.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
@@ -6263,6 +6288,10 @@ ps-tree@^1.0.1:
dependencies:
event-stream "~3.3.0"
pseudomap@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
public-encrypt@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
@@ -6487,11 +6516,7 @@ react-apollo@^0.10.0:
optionalDependencies:
react-dom "0.14.x || 15.* || ^15.0.0"
"react-dom@0.14.x || 15.* || ^15.0.0", react-dom@^15.3.1:
version "15.3.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.3.2.tgz#c46b0aa5380d7b838e7a59c4a7beff2ed315531f"
react-dom@^15.4.2:
"react-dom@0.14.x || 15.* || ^15.0.0", react-dom@^15.3.1, react-dom@^15.4.2:
version "15.4.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.4.2.tgz#015363f05b0a1fd52ae9efdd3a0060d90695208f"
dependencies:
@@ -6561,15 +6586,7 @@ react-tagsinput@^3.14.0:
version "3.14.0"
resolved "https://registry.yarnpkg.com/react-tagsinput/-/react-tagsinput-3.14.0.tgz#6729f8d5b313ed8fbb35496205ec2a97654af6bc"
react@^15.3.1:
version "15.3.2"
resolved "https://registry.yarnpkg.com/react/-/react-15.3.2.tgz#a7bccd2fee8af126b0317e222c28d1d54528d09e"
dependencies:
fbjs "^0.8.4"
loose-envify "^1.1.0"
object-assign "^4.1.0"
react@^15.4.2:
react@^15.3.1, react@^15.4.2:
version "15.4.2"
resolved "https://registry.yarnpkg.com/react/-/react-15.4.2.tgz#41f7991b26185392ba9bae96c8889e7e018397ef"
dependencies:
@@ -6914,9 +6931,11 @@ resolve-from@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57"
resolve@^1.1.6, resolve@^1.1.7:
version "1.2.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c"
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235"
dependencies:
path-parse "^1.0.5"
restore-cursor@^1.0.1:
version "1.0.1"
@@ -7083,6 +7102,16 @@ sha.js@^2.3.6:
dependencies:
inherits "^2.0.1"
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
dependencies:
shebang-regex "^1.0.0"
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
shelljs@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.0.tgz#ce1ed837b4b0e55b5ec3dab84251ab9dbdc0c7ec"
@@ -7351,6 +7380,10 @@ string-width@^2.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^3.0.0"
string.prototype.codepointat@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78"
string_decoder@^0.10.25, string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
@@ -7971,7 +8004,7 @@ which@1.1.1:
dependencies:
is-absolute "^0.1.7"
which@^1.1.1:
which@^1.1.1, which@^1.2.9:
version "1.2.12"
resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192"
dependencies:
@@ -8073,6 +8106,10 @@ y18n@^3.2.0, y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
yallist@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
yargs-parser@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4"