@@ -87,4 +89,11 @@ class Comment extends React.Component {
}
}
+Comment.propTypes = {
+ viewComment: PropTypes.func,
+ comment: PropTypes.object,
+ asset: PropTypes.object,
+ root: PropTypes.object,
+};
+
export default Comment;
diff --git a/plugins/talk-plugin-notifications/server/mutators.js b/plugins/talk-plugin-notifications/server/mutators.js
index 5c0db9c8d..46954faa5 100644
--- a/plugins/talk-plugin-notifications/server/mutators.js
+++ b/plugins/talk-plugin-notifications/server/mutators.js
@@ -29,10 +29,11 @@ async function updateNotificationSettings(ctx, settings) {
}
module.exports = ctx => {
+ const { connectors: { errors: ErrNotAuthorized } } = ctx;
+
let mutators = {
User: {
- updateNotificationSettings: () =>
- Promise.reject(ctx.connectors.errors.ErrNotAuthorized),
+ updateNotificationSettings: () => Promise.reject(new ErrNotAuthorized()),
},
};
diff --git a/plugins/talk-plugin-rich-text/client/components/AdminCommentContent.js b/plugins/talk-plugin-rich-text/client/components/AdminCommentContent.js
index 1b7e53ebf..f49d89055 100644
--- a/plugins/talk-plugin-rich-text/client/components/AdminCommentContent.js
+++ b/plugins/talk-plugin-rich-text/client/components/AdminCommentContent.js
@@ -1,12 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
+import Linkify from 'react-linkify';
import styles from './AdminCommentContent.css';
import { AdminCommentContent as Content } from 'plugin-api/beta/client/components';
class AdminCommentContent extends React.Component {
render() {
const { comment, suspectWords, bannedWords } = this.props;
- return (
+ const content = (
);
+
+ if (!!comment.richTextBody) {
+ return content;
+ }
+
+ return
{content};
}
}
diff --git a/plugins/talk-plugin-rich-text/client/components/CommentContent.js b/plugins/talk-plugin-rich-text/client/components/CommentContent.js
index 2cf481830..64aa96c82 100644
--- a/plugins/talk-plugin-rich-text/client/components/CommentContent.js
+++ b/plugins/talk-plugin-rich-text/client/components/CommentContent.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { PLUGIN_NAME } from '../constants';
import cn from 'classnames';
import styles from './CommentContent.css';
+import Linkify from 'react-linkify';
class CommentContent extends React.Component {
render() {
@@ -14,7 +15,9 @@ class CommentContent extends React.Component {
dangerouslySetInnerHTML={{ __html: comment.richTextBody }}
/>
) : (
-
{comment.body}
+
+ {comment.body}
+
);
}
}
diff --git a/plugins/talk-plugin-toxic-comments/server/errors.js b/plugins/talk-plugin-toxic-comments/server/errors.js
index 60135a8f8..a60bd549b 100644
--- a/plugins/talk-plugin-toxic-comments/server/errors.js
+++ b/plugins/talk-plugin-toxic-comments/server/errors.js
@@ -1,12 +1,16 @@
-const { APIError } = require('errors');
+const { TalkError } = require('errors');
// ErrToxic is sent during a `CreateComment` mutation where
// `input.checkToxicity` is set to true and the comment contains
// toxic language as determined by the perspective service.
-const ErrToxic = new APIError('Comment is toxic', {
- status: 400,
- translation_key: 'COMMENT_IS_TOXIC',
-});
+class ErrToxic extends TalkError {
+ constructor() {
+ super('Comment is toxic', {
+ status: 400,
+ translation_key: 'COMMENT_IS_TOXIC',
+ });
+ }
+}
module.exports = {
ErrToxic,
diff --git a/plugins/talk-plugin-toxic-comments/server/hooks.js b/plugins/talk-plugin-toxic-comments/server/hooks.js
index 7b9c93dad..0be363ea1 100644
--- a/plugins/talk-plugin-toxic-comments/server/hooks.js
+++ b/plugins/talk-plugin-toxic-comments/server/hooks.js
@@ -27,7 +27,7 @@ module.exports = {
if (isToxic(scores)) {
if (input.checkToxicity) {
- throw ErrToxic;
+ throw new ErrToxic();
}
input.status = 'SYSTEM_WITHHELD';
diff --git a/routes/api/v1/users.js b/routes/api/v1/users.js
index e0de3b5db..481e5650c 100644
--- a/routes/api/v1/users.js
+++ b/routes/api/v1/users.js
@@ -1,7 +1,7 @@
const express = require('express');
const router = express.Router();
const UsersService = require('../../../services/users');
-const errors = require('../../../errors');
+const { ErrMissingEmail, ErrNotFound } = require('../../../errors');
const authorization = require('../../../middleware/authorization');
const Limit = require('../../../services/limit');
@@ -40,17 +40,12 @@ router.post('/resend-verify', async (req, res, next) => {
// Clean up and validate the email.
email = email.toLowerCase().trim();
if (email.length < 5) {
- return next(errors.ErrMissingEmail);
+ return next(new ErrMissingEmail());
}
// Check if we're past the rate limit, if we are, stop now. Otherwise, record
// this as an attempt to send a verification email.
try {
- const tries = await resendRateLimiter.get(email);
- if (tries > 0) {
- throw errors.ErrMaxRateLimit;
- }
-
await resendRateLimiter.test(email);
} catch (err) {
return next(err);
@@ -59,7 +54,7 @@ router.post('/resend-verify', async (req, res, next) => {
try {
const user = await UsersService.findLocalUser(email);
if (!user) {
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
await UsersService.sendEmailConfirmation(user, email, redirectUri);
@@ -81,13 +76,13 @@ router.post(
try {
let user = await UsersService.findById(user_id);
if (!user) {
- return next(errors.ErrNotFound);
+ return next(new ErrNotFound());
}
// Find the first local profile.
const email = user.firstEmail;
if (!email) {
- return next(errors.ErrMissingEmail);
+ return next(new ErrMissingEmail());
}
// Send the email to the first local profile that was found.
diff --git a/routes/index.js b/routes/index.js
index 489a5e8b5..b6e46791e 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -1,8 +1,8 @@
const SetupService = require('../services/setup');
const authentication = require('../middleware/authentication');
+const logging = require('../middleware/logging');
const cookieParser = require('cookie-parser');
-const enabled = require('debug').enabled;
-const errors = require('../errors');
+const { TalkError, ErrNotFound } = require('../errors');
const express = require('express');
const i18n = require('../middleware/i18n');
const path = require('path');
@@ -149,19 +149,16 @@ router.use(require('./plugins'));
// Catch 404 and forward to error handler.
router.use((req, res, next) => {
- next(errors.ErrNotFound);
+ next(new ErrNotFound());
});
+// Add logging for errors.
+router.use(logging.error);
+
// General API error handler. Respond with the message and error if we have it
// while returning a status code that makes sense.
router.use('/api', (err, req, res, next) => {
- if (err !== errors.ErrNotFound) {
- if (process.env.NODE_ENV !== 'test' || enabled('talk:errors')) {
- console.error(err);
- }
- }
-
- if (err instanceof errors.APIError) {
+ if (err instanceof TalkError) {
res.status(err.status).json({
message: res.locals.t(`error.${err.translation_key}`),
error: err,
@@ -172,11 +169,7 @@ router.use('/api', (err, req, res, next) => {
});
router.use('/', (err, req, res, next) => {
- if (err !== errors.ErrNotFound) {
- console.error(err);
- }
-
- if (err instanceof errors.APIError) {
+ if (err instanceof TalkError) {
res.status(err.status);
res.render('error', {
message: res.locals.t(`error.${err.translation_key}`),
diff --git a/serve.js b/serve.js
index ea06a6342..8b4d4556c 100644
--- a/serve.js
+++ b/serve.js
@@ -1,5 +1,5 @@
const app = require('./app');
-const errors = require('./errors');
+const { ErrSettingsInit, ErrInstallLock } = require('./errors');
const { createServer } = require('http');
const jobs = require('./jobs');
const MigrationService = require('./services/migration');
@@ -95,20 +95,16 @@ async function serve({
await SetupService.isAvailable();
logger.info('Setup is currently available, migrations not being checked');
- } catch (e) {
+ } catch (err) {
// Check the error.
- switch (e) {
- case errors.ErrInstallLock:
- case errors.ErrSettingsInit:
- logger.info(
- 'Setup is not currently available, migrations now being checked'
- );
-
- // The error was expected, just continue.
- break;
- default:
- // The error was not expected, throw the error!
- throw e;
+ if (err instanceof ErrInstallLock || err instanceof ErrSettingsInit) {
+ // The error was expected, just continue.
+ logger.info(
+ 'Setup is not currently available, migrations now being checked'
+ );
+ } else {
+ // The error was not expected, throw the error!
+ throw err;
}
// Now try and check the migration status.
diff --git a/services/assets.js b/services/assets.js
index f229bb353..fbed22287 100644
--- a/services/assets.js
+++ b/services/assets.js
@@ -2,9 +2,12 @@ const CommentModel = require('../models/comment');
const AssetModel = require('../models/asset');
const SettingsService = require('./settings');
const DomainList = require('./domain_list');
-const errors = require('../errors');
-const merge = require('lodash/merge');
-const isEmpty = require('lodash/isEmpty');
+const {
+ ErrAssetURLAlreadyExists,
+ ErrNotFound,
+ ErrInvalidAssetURL,
+} = require('../errors');
+const { merge, isEmpty } = require('lodash');
const { dotize } = require('./utils');
module.exports = class AssetsService {
@@ -73,7 +76,7 @@ module.exports = class AssetsService {
}
if (!whitelisted) {
- return Promise.reject(errors.ErrInvalidAssetURL);
+ throw new ErrInvalidAssetURL(url);
} else {
return AssetModel.findOneAndUpdate({ url }, update, {
// Ensure that if it's new, we return the new object created.
@@ -211,7 +214,7 @@ module.exports = class AssetsService {
// Try to see if an asset already exists with the given url.
let asset = await AssetsService.findByUrl(url);
if (asset !== null) {
- throw errors.ErrAssetURLAlreadyExists;
+ throw new ErrAssetURLAlreadyExists();
}
// Seems that there was no other asset with the same url, try and perform
@@ -227,7 +230,7 @@ module.exports = class AssetsService {
dstAssetID,
]);
if (!srcAsset || !dstAsset) {
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
// Resolve the merge operation, this invloves moving all resources attached
diff --git a/services/comments.js b/services/comments.js
index a1f59c1e1..b9d73d91c 100644
--- a/services/comments.js
+++ b/services/comments.js
@@ -2,10 +2,13 @@ const CommentModel = require('../models/comment');
const { dotize } = require('./utils');
const debug = require('debug')('talk:services:comments');
const SettingsService = require('./settings');
-
-const cloneDeep = require('lodash/cloneDeep');
-const errors = require('../errors');
-const merge = require('lodash/merge');
+const { merge, cloneDeep } = require('lodash');
+const {
+ ErrParentDoesNotVisible,
+ ErrNotFound,
+ ErrNotAuthorized,
+ ErrEditWindowHasEnded,
+} = require('../errors');
const incrReplyCount = async (comment, value) => {
try {
@@ -40,7 +43,7 @@ module.exports = {
if (parent_id !== null) {
const parent = await CommentModel.findOne({ id: parent_id });
if (parent === null || !parent.visible) {
- throw errors.ErrParentDoesNotVisible;
+ throw new ErrParentDoesNotVisible();
}
}
@@ -126,7 +129,7 @@ module.exports = {
const comment = await CommentModel.findOne({ id });
if (comment == null) {
debug('rejecting comment edit because comment was not found');
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
// Check to see if the user was't allowed to edit it.
@@ -134,7 +137,7 @@ module.exports = {
debug(
'rejecting comment edit because author id does not match editing user'
);
- throw errors.ErrNotAuthorized;
+ throw new ErrNotAuthorized();
}
// Check to see if the comment had a status that was editable.
@@ -142,13 +145,13 @@ module.exports = {
debug(
'rejecting comment edit because original comment has a non-editable status'
);
- throw errors.ErrNotAuthorized;
+ throw new ErrNotAuthorized();
}
// Check to see if the edit window expired.
if (comment.created_at <= lastEditableCommentCreatedAt) {
debug('rejecting comment edit because outside edit time window');
- throw errors.ErrEditWindowHasEnded;
+ throw new ErrEditWindowHasEnded();
}
throw new Error('comment edit failed for an unexpected reason');
@@ -198,7 +201,7 @@ module.exports = {
);
if (originalComment == null) {
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
const editedComment = new CommentModel(originalComment.toObject());
diff --git a/services/limit.js b/services/limit.js
index 6d46f3715..573d7a815 100644
--- a/services/limit.js
+++ b/services/limit.js
@@ -1,5 +1,5 @@
const ms = require('ms');
-const errors = require('../errors');
+const { ErrMaxRateLimit } = require('../errors');
const { createClientFactory } = require('./redis');
const client = createClientFactory();
@@ -60,7 +60,7 @@ class Limit {
}
if (tries > this.max) {
- throw errors.ErrMaxRateLimit;
+ throw new ErrMaxRateLimit(this.max, tries);
}
return tries;
diff --git a/services/logging.js b/services/logging.js
index 47ef93af8..3e2a6d731 100644
--- a/services/logging.js
+++ b/services/logging.js
@@ -1,18 +1,46 @@
const { version } = require('../package.json');
-const Logger = require('bunyan');
+const path = require('path');
+const { createLogger: createBunyanLogger, stdSerializers } = require('bunyan');
const { LOGGING_LEVEL, REVISION_HASH } = require('../config');
-const logger = new Logger({
+
+// Streams enables the ability for development logs to be readable to a human,
+// but will send JSON logs in production that's parsable by a system like ELK.
+const streams = (() => {
+ // In development, use the debug stream printer.
+ if (process.env.NODE_ENV === 'development') {
+ const debug = require('bunyan-debug-stream');
+ return [
+ {
+ level: 'debug',
+ type: 'raw',
+ stream: debug({
+ basepath: path.resolve(__dirname, '..'),
+ forceColor: true,
+ }),
+ },
+ ];
+ }
+
+ // In production, emit JSON.
+ return [{ stream: process.stdout, level: 'info' }];
+})();
+
+// logger is the base logger used by all logging systems in Talk.
+const logger = createBunyanLogger({
src: true,
name: 'talk',
version,
revision: REVISION_HASH,
level: LOGGING_LEVEL,
- serializers: Logger.stdSerializers,
+ streams,
+ serializers: stdSerializers,
});
-// Create the logging instance that all logger's are branched from.
-function createLogger(name, traceID) {
- return logger.child({ origin: name, traceID });
-}
+/**
+ *
+ * @param {String} origin the origin name used by the logger
+ * @param {String} traceID the id of the request being made
+ */
+const createLogger = (origin, traceID) => logger.child({ origin, traceID });
module.exports = { logger, createLogger };
diff --git a/services/moderation/index.js b/services/moderation/index.js
index 4d87e190f..530d6f2a6 100644
--- a/services/moderation/index.js
+++ b/services/moderation/index.js
@@ -1,4 +1,4 @@
-const errors = require('../../errors');
+const { ErrNotFound } = require('../../errors');
const get = require('lodash/get');
// Load in the phases to use.
@@ -92,14 +92,14 @@ const fetchOptions = async (ctx, comment) => {
const assetID = get(comment, 'asset_id', null);
if (assetID === null) {
// And leave now if this asset wasn't found.
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
// Load the asset.
const asset = await Assets.getByID.load(assetID);
if (!asset) {
// And leave now if this asset wasn't found.
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
// Combine the asset and the settings to get the asset settings.
diff --git a/services/moderation/phases/commentLength.js b/services/moderation/phases/commentLength.js
index 925115326..e19198ef1 100644
--- a/services/moderation/phases/commentLength.js
+++ b/services/moderation/phases/commentLength.js
@@ -8,7 +8,7 @@ module.exports = (
) => {
// Check to see if the body is too short, if it is, then complain about it!
if (comment.body.length < 2) {
- throw ErrCommentTooShort;
+ throw new ErrCommentTooShort(comment.body.length);
}
// Reject if the comment is too long
diff --git a/services/mongoose.js b/services/mongoose.js
index 20765293a..5e498cee8 100644
--- a/services/mongoose.js
+++ b/services/mongoose.js
@@ -1,48 +1,40 @@
-const { MONGO_URL, WEBPACK, CREATE_MONGO_INDEXES } = require('../config');
-
+const {
+ MONGO_URL,
+ WEBPACK,
+ CREATE_MONGO_INDEXES,
+ LOGGING_LEVEL,
+} = require('../config');
+const { logger } = require('./logging');
const mongoose = require('mongoose');
-const debug = require('debug')('talk:db');
-const enabled = require('debug').enabled;
-const queryDebugger = require('debug')('talk:db:query');
-
-// Loading the formatter from Mongoose:
-//
-// https://github.com/Automattic/mongoose/blob/1a93d1f4d12e441e17ddf451e96fbc5f6e8f54b8/lib/drivers/node-mongodb-native/collection.js#L182
-//
-// so we can wrap parameters.
-const formatter = require('mongoose').Collection.prototype.$format;
// Provide a newly wrapped debugQuery function which wraps the `debug` package.
-function debugQuery(name, i, ...args) {
- let functionCall = ['db', name, i].join('.');
- let _args = [];
- for (let j = args.length - 1; j >= 0; --j) {
- if (formatter(args[j]) || _args.length) {
- _args.unshift(formatter(args[j]));
- }
- }
-
- let params = `(${_args.join(', ')})`;
-
- queryDebugger(functionCall + params);
+function debugQuery(name, operation, ...args) {
+ logger.debug(
+ {
+ query: `db.${name}.${operation}(${args
+ .map(arg => JSON.stringify(arg))
+ .join(', ')})`,
+ },
+ 'mongodb query'
+ );
}
// Use native promises
mongoose.Promise = global.Promise;
-// Check if debugging is enabled on the talk:db prefix.
-if (enabled('talk:db:query')) {
+// Check if verbose logging is enabled.
+if (['debug', 'trace'].includes(LOGGING_LEVEL)) {
// Enable the mongoose debugger, here we wrap the similar print function
// provided by setting the debug parameter.
mongoose.set('debug', debugQuery);
}
if (WEBPACK) {
- debug('Not connecting to mongodb during webpack build');
+ logger.debug('Not connecting to mongodb during webpack build');
- // @wyattjoh: We didn't call connect, but because we include mongoose, it will hold the socket ready,
- // preventing node from exiting. Calling disconnect here just ensures that the application
- // can quit correctly.
+ // @wyattjoh: We didn't call connect, but because we include mongoose, it will
+ // hold the socket ready, preventing node from exiting. Calling disconnect
+ // here just ensures that the application can quit correctly.
mongoose.disconnect();
} else {
// Connect to the Mongo instance.
@@ -54,7 +46,7 @@ if (WEBPACK) {
},
})
.then(() => {
- debug('connection established');
+ logger.debug('mongodb connection established');
})
.catch(err => {
console.error(err);
@@ -66,10 +58,13 @@ module.exports = mongoose;
// Here we include all the models that mongoose is used for, this ensures that
// when we import mongoose that we also start up all the indexing operations
-// here.
-require('../models/action');
-require('../models/asset');
-require('../models/comment');
-require('../models/setting');
-require('../models/user');
-require('./migration');
+// here. No point also in importing this if we're not actually doing any
+// indexing now.
+if (CREATE_MONGO_INDEXES) {
+ require('../models/action');
+ require('../models/asset');
+ require('../models/comment');
+ require('../models/setting');
+ require('../models/user');
+ require('./migration');
+}
diff --git a/services/passport.js b/services/passport.js
index 5748c911b..0ae06afb8 100644
--- a/services/passport.js
+++ b/services/passport.js
@@ -6,7 +6,12 @@ const TokensService = require('./tokens');
const fetch = require('node-fetch');
const FormData = require('form-data');
const LocalStrategy = require('passport-local').Strategy;
-const errors = require('../errors');
+const {
+ ErrLoginAttemptMaximumExceeded,
+ ErrNotAuthorized,
+ ErrAuthentication,
+ ErrNotVerified,
+} = require('../errors');
const uuid = require('uuid');
const debug = require('debug')('talk:services:passport');
const bowser = require('bowser');
@@ -75,7 +80,7 @@ const HandleGenerateCredentials = (req, res, next) => (err, user) => {
}
if (!user) {
- return next(errors.ErrNotAuthorized);
+ return next(new ErrNotAuthorized());
}
// Generate the token to re-issue to the frontend.
@@ -117,7 +122,7 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
if (!user) {
return res.render('auth-callback', {
- auth: { err: errors.ErrNotAuthorized, data: null },
+ auth: { err: new ErrNotAuthorized(), data: null },
});
}
@@ -143,7 +148,7 @@ async function ValidateUserLogin(loginProfile, user, done) {
}
if (user.disabled) {
- return done(new errors.ErrAuthentication('Account disabled'));
+ return done(new ErrAuthentication('Account disabled'));
}
// If the user isn't a local user (i.e., a social user).
@@ -169,7 +174,7 @@ async function ValidateUserLogin(loginProfile, user, done) {
// If the profile doesn't have a metadata field, or it does not have a
// confirmed_at field, or that field is null, then send them back.
if (_.get(profile, 'metadata.confirmed_at', null) === null) {
- return done(errors.ErrNotVerified);
+ return done(new ErrNotVerified());
}
}
@@ -209,7 +214,7 @@ const checkGeneralTokenBlacklist = jwt =>
.get(`jtir[${jwt.jti}]`)
.then(expiry => {
if (expiry != null) {
- throw new errors.ErrAuthentication('token was revoked');
+ throw new ErrAuthentication('token was revoked');
}
});
@@ -392,7 +397,7 @@ const HandleFailedAttempt = async (email, userNeedsRecaptcha) => {
await UsersService.recordLoginAttempt(email);
} catch (err) {
if (
- err === errors.ErrLoginAttemptMaximumExceeded &&
+ err instanceof ErrLoginAttemptMaximumExceeded &&
!userNeedsRecaptcha &&
RECAPTCHA_ENABLED
) {
@@ -448,7 +453,7 @@ passport.use(
try {
await UsersService.checkLoginAttempts(email);
} catch (err) {
- if (err === errors.ErrLoginAttemptMaximumExceeded) {
+ if (err instanceof ErrLoginAttemptMaximumExceeded) {
// This says, we didn't have a recaptcha, yet we needed one.. Reject
// here.
diff --git a/services/redis.js b/services/redis.js
index 2576d2c13..0a25b34d6 100644
--- a/services/redis.js
+++ b/services/redis.js
@@ -1,7 +1,5 @@
const Redis = require('ioredis');
const merge = require('lodash/merge');
-const debug = require('debug')('talk:services:redis');
-const enabled = require('debug').enabled('talk:services:redis');
const {
REDIS_URL,
REDIS_RECONNECTION_BACKOFF_FACTOR,
@@ -9,29 +7,32 @@ const {
REDIS_CLIENT_CONFIG,
REDIS_CLUSTER_MODE,
REDIS_CLUSTER_CONFIGURATION,
+ LOGGING_LEVEL,
} = require('../config');
+const { createLogger } = require('./logging');
+const logger = createLogger('redis');
const attachMonitors = client => {
- debug('client created');
+ logger.debug('client created');
// Debug events.
- if (enabled) {
- client.on('connect', () => debug('client connected'));
- client.on('ready', () => debug('client ready'));
- client.on('close', () => debug('client closed the connection'));
+ if (['debug', 'trace'].includes(LOGGING_LEVEL)) {
+ client.on('connect', () => logger.info('client connected'));
+ client.on('ready', () => logger.debug('client ready'));
+ client.on('close', () => logger.debug('client closed the connection'));
client.on('reconnecting', () =>
- debug('client connection lost, attempting to reconnect')
+ logger.debug('client connection lost, attempting to reconnect')
);
- client.on('end', () => debug('client ended'));
+ client.on('end', () => logger.debug('client ended'));
}
// Error events.
client.on('error', err => {
if (err) {
- console.error('Error connecting to redis:', err);
+ logger.error({ err }, 'cannot connect to redis');
}
});
- client.on('node error', err => debug('node error', err));
+ client.on('node error', err => logger.error({ err }, 'node error'));
};
function retryStrategy(times) {
@@ -40,7 +41,7 @@ function retryStrategy(times) {
REDIS_RECONNECTION_BACKOFF_MINIMUM_TIME
);
- debug(`retry strategy: try to reconnect ${delay} ms from now`);
+ logger.debug(`retry strategy: try to reconnect ${delay} ms from now`);
return delay;
}
diff --git a/services/settings.js b/services/settings.js
index f918f9d01..ee5125183 100644
--- a/services/settings.js
+++ b/services/settings.js
@@ -1,6 +1,6 @@
const SettingModel = require('../models/setting');
const cache = require('./cache');
-const errors = require('../errors');
+const { ErrSettingsNotInit } = require('../errors');
const { dotize } = require('./utils');
const { SETTINGS_CACHE_TIME } = require('../config');
@@ -17,7 +17,7 @@ const retrieve = async fields => {
settings = await SettingModel.findOne(selector);
}
if (!settings) {
- throw errors.ErrSettingsNotInit;
+ throw new ErrSettingsNotInit();
}
return settings;
diff --git a/services/setup.js b/services/setup.js
index 84f915ff2..2517d376b 100644
--- a/services/setup.js
+++ b/services/setup.js
@@ -2,7 +2,12 @@ const UsersService = require('./users');
const SettingsService = require('./settings');
const MigrationService = require('./migration');
const SettingsModel = require('../models/setting');
-const errors = require('../errors');
+const {
+ ErrMissingEmail,
+ ErrInstallLock,
+ ErrSettingsInit,
+ ErrSettingsNotInit,
+} = require('../errors');
const { INSTALL_LOCK } = require('../config');
/**
@@ -16,25 +21,25 @@ module.exports = class SetupService {
static async isAvailable() {
// Check if we have an install lock present.
if (INSTALL_LOCK) {
- throw errors.ErrInstallLock;
+ throw new ErrInstallLock();
}
try {
- // Get the current settings, we are expecing an error here.
+ // Get the current settings, we are expecting an error here.
await SettingsService.retrieve();
// We should NOT have gotten a settings object, this means that the
// application is already setup. Error out here.
- throw errors.ErrSettingsInit;
- } catch (e) {
- // If the error is `not init`, then we're good, otherwise, it's something
- // else.
- if (e !== errors.ErrSettingsNotInit) {
- throw e;
+ throw new ErrSettingsInit();
+ } catch (err) {
+ // Allow the request to keep going here.
+ if (err instanceof ErrSettingsNotInit) {
+ return;
}
- // Allow the request to keep going here.
- return;
+ // If the error is `not init`, then we're good, otherwise, it's something
+ // else.
+ throw err;
}
}
@@ -44,7 +49,7 @@ module.exports = class SetupService {
static validate({ settings, user: { email, username, password } }) {
// Verify the email address of the user.
if (!email) {
- return Promise.reject(errors.ErrMissingEmail);
+ throw new ErrMissingEmail();
}
// Create a settings model to use for validation.
diff --git a/services/tags.js b/services/tags.js
index 8183d0165..cc8934e0a 100644
--- a/services/tags.js
+++ b/services/tags.js
@@ -1,12 +1,10 @@
const CommentModel = require('../models/comment');
const AssetModel = require('../models/asset');
const UserModel = require('../models/user');
-
const AssetsService = require('./assets');
const SettingsService = require('./settings');
const { ADD_COMMENT_TAG } = require('../perms/constants');
-
-const errors = require('../errors');
+const { ErrNotAuthorized } = require('../errors');
const updateModel = async (item_type, query, update) => {
// Get the model to update with.
@@ -120,13 +118,13 @@ class TagsService {
return { tagLink, ownership: true };
}
- throw errors.ErrNotAuthorized;
+ throw new ErrNotAuthorized();
}
// Only admin/moderators can modify unique tags, these are tags that are not
// in the global list.
if (!user.can(ADD_COMMENT_TAG)) {
- throw errors.ErrNotAuthorized;
+ throw new ErrNotAuthorized();
}
// Generate the tag in the event now that we have to create the tag for this
diff --git a/services/users.js b/services/users.js
index 0617d5158..d2dd60b0b 100644
--- a/services/users.js
+++ b/services/users.js
@@ -1,6 +1,21 @@
const uuid = require('uuid');
const bcrypt = require('bcryptjs');
-const errors = require('../errors');
+const {
+ ErrMaxRateLimit,
+ ErrLoginAttemptMaximumExceeded,
+ ErrNotFound,
+ ErrPermissionUpdateUsername,
+ ErrSameUsernameProvided,
+ ErrUsernameTaken,
+ ErrMissingUsername,
+ ErrSpecialChars,
+ ErrMissingPassword,
+ ErrPasswordTooShort,
+ ErrMissingEmail,
+ ErrEmailTaken,
+ ErrEmailAlreadyVerified,
+ ErrCannotIgnoreStaff,
+} = require('../errors');
const { difference, sample, some, merge, random } = require('lodash');
const { ROOT_URL } = require('../config');
const { jwt: JWT_SECRET } = require('../secrets');
@@ -59,8 +74,8 @@ class UsersService {
try {
await loginRateLimiter.test(email.toLowerCase().trim());
} catch (err) {
- if (err === errors.ErrMaxRateLimit) {
- throw errors.ErrLoginAttemptMaximumExceeded;
+ if (err instanceof ErrMaxRateLimit) {
+ throw new ErrLoginAttemptMaximumExceeded();
}
throw err;
@@ -91,7 +106,7 @@ class UsersService {
if (user === null) {
user = await UserModel.findOne({ id });
if (user === null) {
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
// Date comparisons are difficult when using MongoDB. Javascript will
@@ -150,10 +165,10 @@ class UsersService {
runValidators: true,
}
);
- if (user === null) {
+ if (!user) {
user = await UserModel.findOne({ id });
- if (user === null) {
- throw errors.ErrNotFound;
+ if (!user) {
+ throw new ErrNotFound();
}
if (user.status.banned.status === status) {
@@ -204,7 +219,7 @@ class UsersService {
if (user === null) {
user = await UserModel.findOne({ id });
if (user === null) {
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
if (user.status.username.status === status) {
@@ -259,15 +274,15 @@ class UsersService {
if (!user) {
user = await UsersService.findById(id);
if (user === null) {
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
if (user.status.username.status !== fromStatus) {
- throw errors.ErrPermissionUpdateUsername;
+ throw new ErrPermissionUpdateUsername();
}
if (!resetAllowed && user.username === username) {
- throw errors.ErrSameUsernameProvided;
+ throw new ErrSameUsernameProvided();
}
throw new Error('edit username failed for an unexpected reason');
@@ -276,7 +291,7 @@ class UsersService {
return user;
} catch (err) {
if (err.code === 11000) {
- throw errors.ErrUsernameTaken;
+ throw new ErrUsernameTaken();
}
throw err;
@@ -317,7 +332,7 @@ class UsersService {
}
if (attempts >= RECAPTCHA_INCORRECT_TRIGGER) {
- throw errors.ErrLoginAttemptMaximumExceeded;
+ throw new ErrLoginAttemptMaximumExceeded();
}
}
@@ -515,11 +530,11 @@ class UsersService {
const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/;
if (!username) {
- throw errors.ErrMissingUsername;
+ throw new ErrMissingUsername();
}
if (!onlyLettersNumbersUnderscore.test(username)) {
- throw errors.ErrSpecialChars;
+ throw new ErrSpecialChars();
}
if (checkAgainstWordlist) {
@@ -539,11 +554,11 @@ class UsersService {
*/
static isValidPassword(password) {
if (!password) {
- throw errors.ErrMissingPassword;
+ throw new ErrMissingPassword();
}
if (password.length < 8) {
- throw errors.ErrPasswordTooShort;
+ throw new ErrPasswordTooShort();
}
return password;
@@ -558,7 +573,7 @@ class UsersService {
*/
static async createLocalUser(ctx, email, password, username) {
if (!email) {
- throw errors.ErrMissingEmail;
+ throw new ErrMissingEmail();
}
email = email.toLowerCase().trim();
@@ -596,9 +611,9 @@ class UsersService {
} catch (err) {
if (err.code === 11000) {
if (err.message.match('Username')) {
- throw errors.ErrUsernameTaken;
+ throw new ErrUsernameTaken();
}
- throw errors.ErrEmailTaken;
+ throw new ErrEmailTaken();
}
throw err;
}
@@ -678,9 +693,7 @@ class UsersService {
*/
static async createPasswordResetToken(email, loc) {
if (!email || typeof email !== 'string') {
- throw new Error(
- 'email is required when creating a JWT for resetting passord'
- );
+ throw new ErrMissingEmail();
}
email = email.toLowerCase();
@@ -837,7 +850,7 @@ class UsersService {
// Ensure that the user email hasn't already been verified.
if (profile && profile.metadata && profile.metadata.confirmed_at) {
- throw errors.ErrEmailAlreadyVerified;
+ throw new ErrEmailAlreadyVerified();
}
return JWT_SECRET.sign(
@@ -875,16 +888,16 @@ class UsersService {
},
});
if (!user) {
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
const profile = user.profiles.find(({ id }) => id === decoded.email);
if (!profile) {
- throw errors.ErrNotFound;
+ throw new ErrNotFound();
}
if (profile.metadata && profile.metadata.confirmed_at !== null) {
- throw errors.ErrEmailAlreadyVerified;
+ throw new ErrEmailAlreadyVerified();
}
return decoded;
@@ -943,7 +956,7 @@ class UsersService {
const users = await UsersService.findByIdArray(usersToIgnore);
if (some(users, user => user.isStaff())) {
- throw errors.ErrCannotIgnoreStaff;
+ throw new ErrCannotIgnoreStaff();
}
return UserModel.update(
diff --git a/services/wordlist.js b/services/wordlist.js
index 04012adcb..9e7e1581e 100644
--- a/services/wordlist.js
+++ b/services/wordlist.js
@@ -1,7 +1,7 @@
const debug = require('debug')('talk:services:wordlist');
const _ = require('lodash');
const SettingsService = require('./settings');
-const Errors = require('../errors');
+const { ErrContainsProfanity } = require('../errors');
const memoize = require('lodash/memoize');
const { escapeRegExp } = require('./regex');
@@ -96,7 +96,7 @@ class Wordlist {
`the field "${fieldName}" contained a phrase "${phrase}" which contained a banned word/phrase`
);
- errors.banned = Errors.ErrContainsProfanity;
+ errors.banned = new ErrContainsProfanity(phrase);
// Stop looping through the fields now, we discovered the worst possible
// situation (a banned word).
@@ -109,7 +109,7 @@ class Wordlist {
`the field "${fieldName}" contained a phrase "${phrase}" which contained a suspected word/phrase`
);
- errors.suspect = Errors.ErrContainsProfanity;
+ errors.suspect = new ErrContainsProfanity(phrase);
// Continue looping through the fields now, we discovered a possible bad
// word (suspect).
@@ -167,7 +167,7 @@ class Wordlist {
return wl.load().then(() => {
if (wl.regexp.banned.test(username)) {
- return Errors.ErrContainsProfanity;
+ throw new ErrContainsProfanity(username);
}
});
}
diff --git a/test/server/graph/context.js b/test/server/graph/context.js
index a788f509b..b3319d5c4 100644
--- a/test/server/graph/context.js
+++ b/test/server/graph/context.js
@@ -1,6 +1,6 @@
const User = require('../../../models/user');
const Context = require('../../../graph/context');
-const errors = require('../../../errors');
+const { ErrNotAuthorized } = require('../../../errors');
const SettingsService = require('../../../services/settings');
const { expect } = require('chai');
@@ -54,7 +54,7 @@ describe('graph.Context', () => {
throw new Error('should not reach this point');
})
.catch(err => {
- expect(err).to.be.equal(errors.ErrNotAuthorized);
+ expect(err).to.be.an.instanceof(ErrNotAuthorized);
});
});
});
diff --git a/test/server/services/wordlist.js b/test/server/services/wordlist.js
index 7a11dbf80..313dde49f 100644
--- a/test/server/services/wordlist.js
+++ b/test/server/services/wordlist.js
@@ -1,4 +1,4 @@
-const Errors = require('../../../errors');
+const { ErrContainsProfanity } = require('../../../errors');
const Wordlist = require('../../../services/wordlist');
const SettingsService = require('../../../services/settings');
@@ -103,7 +103,8 @@ describe('services.Wordlist', () => {
'content'
);
- expect(errors).to.have.property('banned', Errors.ErrContainsProfanity);
+ expect(errors).to.have.property('banned');
+ expect(errors.banned).to.be.an.instanceof(ErrContainsProfanity);
});
it('does not match on bodies not containing bad words', () => {
diff --git a/yarn.lock b/yarn.lock
index 07a395260..b5d130ab0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -68,7 +68,7 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
-"@coralproject/eslint-config-talk@^0.1.0":
+"@coralproject/eslint-config-talk@^0.1.0", "@coralproject/eslint-config-talk@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@coralproject/eslint-config-talk/-/eslint-config-talk-0.1.1.tgz#71991b4937a3ffe657128d7f1170da4b5fb75c9e"
dependencies:
@@ -126,6 +126,12 @@
to-title-case "~1.0.0"
url-regex "~4.1.1"
+"@types/form-data@*":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e"
+ dependencies:
+ "@types/node" "*"
+
"@types/graphql@0.10.2":
version "0.10.2"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.10.2.tgz#d7c79acbaa17453b6681c80c34b38fcb10c4c08c"
@@ -138,10 +144,25 @@
version "0.9.4"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.4.tgz#cdeb6bcbef9b6c584374b81aa7f48ecf3da404fa"
+"@types/lodash@^4.14.50":
+ version "4.14.106"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd"
+
"@types/node@*":
version "8.0.53"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
+"@types/node@^7.0.0":
+ version "7.0.59"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.59.tgz#fd7dceba9521c2d62c3e0eda8c5d704bf88b261d"
+
+"@types/request@^0.0.39":
+ version "0.0.39"
+ resolved "https://registry.yarnpkg.com/@types/request/-/request-0.0.39.tgz#168b96cf4253c5d54d403f746f82ee7aed47ce2c"
+ dependencies:
+ "@types/form-data" "*"
+ "@types/node" "*"
+
"@types/ws@^3.0.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-3.2.0.tgz#988ff690e6ed10068a86aa0e9f842d0a03c09e21"
@@ -174,6 +195,13 @@ accepts@^1.3.4, accepts@~1.3.4:
mime-types "~2.1.16"
negotiator "0.6.1"
+accepts@~1.3.5:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
+ dependencies:
+ mime-types "~2.1.18"
+ negotiator "0.6.1"
+
acorn-dynamic-import@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4"
@@ -228,6 +256,10 @@ acorn@^5.3.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
+acorn@^5.5.0:
+ version "5.5.3"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
+
addressparser@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746"
@@ -1652,6 +1684,13 @@ builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
+bunyan-debug-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/bunyan-debug-stream/-/bunyan-debug-stream-1.0.8.tgz#df612852d5d0b6d6df3f30214d8a7e4ee925106d"
+ dependencies:
+ colors "^1.0.3"
+ exception-formatter "^1.0.4"
+
bunyan@^1.8.12:
version "1.8.12"
resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797"
@@ -1770,6 +1809,13 @@ caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+casual@^1.5.19:
+ version "1.5.19"
+ resolved "https://registry.yarnpkg.com/casual/-/casual-1.5.19.tgz#66fac46f7ae463f468f5913eb139f9c41c58bbf2"
+ dependencies:
+ mersenne-twister "^1.0.1"
+ moment "^2.15.2"
+
center-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
@@ -2135,6 +2181,10 @@ colors@1.0.3, colors@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
+colors@^1.0.3:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794"
+
colors@^1.1.2, colors@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
@@ -2427,6 +2477,10 @@ cosmiconfig@^4.0.0, cosmiconfig@~4.0.0:
parse-json "^4.0.0"
require-from-string "^2.0.1"
+crc@3.4.4:
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b"
+
create-ecdh@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
@@ -2916,7 +2970,7 @@ dns-prefetch-control@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2"
-doctrine@^2.0.0:
+doctrine@^2.0.0, doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
dependencies:
@@ -3057,6 +3111,10 @@ ejs@2.5.7, ejs@^2.5.7:
version "2.5.7"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
+ejs@^2.5.8:
+ version "2.5.8"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.8.tgz#2ab6954619f225e6193b7ac5f7c39c48fefe4380"
+
electron-to-chromium@^1.2.7:
version "1.3.26"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.26.tgz#996427294861a74d9c7c82b9260ea301e8c02d66"
@@ -3352,6 +3410,49 @@ eslint-visitor-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
+eslint@^4.19.1:
+ version "4.19.1"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300"
+ dependencies:
+ ajv "^5.3.0"
+ babel-code-frame "^6.22.0"
+ chalk "^2.1.0"
+ concat-stream "^1.6.0"
+ cross-spawn "^5.1.0"
+ debug "^3.1.0"
+ doctrine "^2.1.0"
+ eslint-scope "^3.7.1"
+ eslint-visitor-keys "^1.0.0"
+ espree "^3.5.4"
+ esquery "^1.0.0"
+ esutils "^2.0.2"
+ file-entry-cache "^2.0.0"
+ functional-red-black-tree "^1.0.1"
+ glob "^7.1.2"
+ globals "^11.0.1"
+ ignore "^3.3.3"
+ imurmurhash "^0.1.4"
+ inquirer "^3.0.6"
+ is-resolvable "^1.0.0"
+ js-yaml "^3.9.1"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.3.0"
+ lodash "^4.17.4"
+ minimatch "^3.0.2"
+ mkdirp "^0.5.1"
+ natural-compare "^1.4.0"
+ optionator "^0.8.2"
+ path-is-inside "^1.0.2"
+ pluralize "^7.0.0"
+ progress "^2.0.0"
+ regexpp "^1.0.1"
+ require-uncached "^1.0.3"
+ semver "^5.3.0"
+ strip-ansi "^4.0.0"
+ strip-json-comments "~2.0.1"
+ table "4.0.2"
+ text-table "~0.2.0"
+
eslint@^4.5.0:
version "4.13.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.13.1.tgz#0055e0014464c7eb7878caf549ef2941992b444f"
@@ -3401,6 +3502,13 @@ espree@^3.5.2:
acorn "^5.2.1"
acorn-jsx "^3.0.0"
+espree@^3.5.4:
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
+ dependencies:
+ acorn "^5.5.0"
+ acorn-jsx "^3.0.0"
+
esprima@3.x.x, esprima@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
@@ -3480,6 +3588,12 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
+exception-formatter@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/exception-formatter/-/exception-formatter-1.0.5.tgz#bda957319789cbabdf36848fb5288c59634b73a5"
+ dependencies:
+ colors "^1.0.3"
+
exec-sh@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38"
@@ -3575,6 +3689,20 @@ exports-loader@^0.6.4:
loader-utils "^1.0.2"
source-map "0.5.x"
+express-session@^1.15.6:
+ version "1.15.6"
+ resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.6.tgz#47b4160c88f42ab70fe8a508e31cbff76757ab0a"
+ dependencies:
+ cookie "0.3.1"
+ cookie-signature "1.0.6"
+ crc "3.4.4"
+ debug "2.6.9"
+ depd "~1.1.1"
+ on-headers "~1.0.1"
+ parseurl "~1.3.2"
+ uid-safe "~2.1.5"
+ utils-merge "1.0.1"
+
express-static-gzip@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/express-static-gzip/-/express-static-gzip-0.3.2.tgz#89ede84547a5717de3146315f62dc996c071a88d"
@@ -3616,6 +3744,41 @@ express@4.16.0, express@^4.12.2:
utils-merge "1.0.1"
vary "~1.1.2"
+express@^4.16.3:
+ version "4.16.3"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
+ dependencies:
+ accepts "~1.3.5"
+ array-flatten "1.1.1"
+ body-parser "1.18.2"
+ content-disposition "0.5.2"
+ content-type "~1.0.4"
+ cookie "0.3.1"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "~1.1.2"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.1.1"
+ fresh "0.5.2"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "~2.3.0"
+ parseurl "~1.3.2"
+ path-to-regexp "0.1.7"
+ proxy-addr "~2.0.3"
+ qs "6.5.1"
+ range-parser "~1.2.0"
+ safe-buffer "5.1.1"
+ send "0.16.2"
+ serve-static "1.13.2"
+ setprototypeof "1.1.0"
+ statuses "~1.4.0"
+ type-is "~1.6.16"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -3813,6 +3976,18 @@ finalhandler@1.1.0:
statuses "~1.3.1"
unpipe "~1.0.0"
+finalhandler@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ parseurl "~1.3.2"
+ statuses "~1.4.0"
+ unpipe "~1.0.0"
+
find-cache-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
@@ -4123,6 +4298,16 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
+gigya@2.0.33:
+ version "2.0.33"
+ resolved "https://registry.yarnpkg.com/gigya/-/gigya-2.0.33.tgz#c5845cd16fac8ebcfb5e727e1ebe9e51352482fb"
+ dependencies:
+ "@types/lodash" "^4.14.50"
+ "@types/node" "^7.0.0"
+ "@types/request" "^0.0.39"
+ lodash "^4.17.4"
+ request "^2.79.0"
+
git-up@^2.0.0:
version "2.0.9"
resolved "https://registry.yarnpkg.com/git-up/-/git-up-2.0.9.tgz#219bfd27c82daeead8495beb386dc18eae63636d"
@@ -5119,6 +5304,10 @@ ipaddr.js@1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
+ipaddr.js@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b"
+
is-absolute-url@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
@@ -6975,6 +7164,10 @@ merge@^1.1.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
+mersenne-twister@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a"
+
metascraper-author@^3.9.2:
version "3.9.2"
resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-3.9.2.tgz#ff2020ac428f59a875d655df3b0d4bea171fde19"
@@ -7115,7 +7308,7 @@ miller-rabin@^4.0.0:
version "1.30.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
-"mime-db@>= 1.33.0 < 2":
+"mime-db@>= 1.33.0 < 2", mime-db@~1.33.0:
version "1.33.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
@@ -7125,6 +7318,12 @@ mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16,
dependencies:
mime-db "~1.30.0"
+mime-types@~2.1.18:
+ version "2.1.18"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
+ dependencies:
+ mime-db "~1.33.0"
+
mime@1.4.1, mime@^1.3.4, mime@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
@@ -7257,6 +7456,10 @@ moment@^2.10.3:
version "2.19.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167"
+moment@^2.15.2:
+ version "2.22.0"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.0.tgz#7921ade01017dd45186e7fee5f424f0b8663a730"
+
mongodb-core@2.1.17:
version "2.1.17"
resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.17.tgz#a418b337a14a14990fb510b923dee6a813173df8"
@@ -7294,7 +7497,7 @@ moo-server@*, moo-server@1.3.x:
version "1.3.0"
resolved "https://registry.yarnpkg.com/moo-server/-/moo-server-1.3.0.tgz#5dc79569565a10d6efed5439491e69d2392e58f1"
-morgan@^1.6.1, morgan@^1.9.0:
+morgan@^1.6.1:
version "1.9.0"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051"
dependencies:
@@ -8214,6 +8417,15 @@ passport-oauth2@1.x.x, passport-oauth2@^1.1.2:
uid2 "0.0.x"
utils-merge "1.x.x"
+passport-openidconnect@^0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/passport-openidconnect/-/passport-openidconnect-0.0.2.tgz#e488f8bdb386c9a9fd39c91d5ab8c880156e8153"
+ dependencies:
+ oauth "0.9.x"
+ passport-strategy "1.x.x"
+ request "^2.75.0"
+ webfinger "0.4.x"
+
passport-strategy@1.x.x, passport-strategy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
@@ -8938,6 +9150,13 @@ proxy-addr@~2.0.2:
forwarded "~0.1.2"
ipaddr.js "1.5.2"
+proxy-addr@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
+ dependencies:
+ forwarded "~0.1.2"
+ ipaddr.js "1.6.0"
+
proxy-agent@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.0.0.tgz#57eb5347aa805d74ec681cb25649dba39c933499"
@@ -9188,6 +9407,10 @@ randexp@^0.4.2:
discontinuous-range "1.0.0"
ret "~0.1.10"
+random-bytes@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
+
randomatic@^1.1.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
@@ -9658,6 +9881,10 @@ regexp-clone@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-0.0.1.tgz#a7c2e09891fdbf38fbb10d376fb73003e68ac589"
+regexpp@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab"
+
regexpu-core@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
@@ -9808,6 +10035,33 @@ request@2.81.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
+request@^2.75.0:
+ version "2.85.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
+ dependencies:
+ aws-sign2 "~0.7.0"
+ aws4 "^1.6.0"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.1"
+ forever-agent "~0.6.1"
+ form-data "~2.3.1"
+ har-validator "~5.0.3"
+ hawk "~6.0.2"
+ http-signature "~1.2.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.17"
+ oauth-sign "~0.8.2"
+ performance-now "^2.1.0"
+ qs "~6.5.1"
+ safe-buffer "^5.1.1"
+ stringstream "~0.0.5"
+ tough-cookie "~2.3.3"
+ tunnel-agent "^0.6.0"
+ uuid "^3.1.0"
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -10022,7 +10276,7 @@ sax@0.5.x:
version "0.5.8"
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
-sax@^1.1.4, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1:
+sax@>=0.1.1, sax@^1.1.4, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -10161,7 +10415,7 @@ serve-static@1.13.0:
parseurl "~1.3.2"
send "0.16.0"
-serve-static@^1.10.0:
+serve-static@1.13.2, serve-static@^1.10.0:
version "1.13.2"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
dependencies:
@@ -10578,6 +10832,10 @@ stealthy-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
+step@0.0.x:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/step/-/step-0.0.6.tgz#143e7849a5d7d3f4a088fe29af94915216eeede2"
+
stream-browserify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
@@ -10875,7 +11133,7 @@ symbol-observable@^1.0.2, symbol-observable@^1.0.3, symbol-observable@^1.0.4:
version "3.2.2"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
-table@^4.0.1:
+table@4.0.2, table@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
dependencies:
@@ -11195,6 +11453,13 @@ type-is@~1.6.15:
media-typer "0.3.0"
mime-types "~2.1.15"
+type-is@~1.6.16:
+ version "1.6.16"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.18"
+
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -11269,6 +11534,12 @@ uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
+uid-safe@~2.1.5:
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
+ dependencies:
+ random-bytes "~1.0.0"
+
uid2@0.0.x:
version "0.0.3"
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
@@ -11559,6 +11830,13 @@ watchpack@^1.4.0:
chokidar "^1.7.0"
graceful-fs "^4.1.2"
+webfinger@0.4.x:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/webfinger/-/webfinger-0.4.2.tgz#3477a6d97799461896039fcffc650b73468ee76d"
+ dependencies:
+ step "0.0.x"
+ xml2js "0.1.x"
+
webidl-conversions@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506"
@@ -11795,6 +12073,12 @@ xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
+xml2js@0.1.x:
+ version "0.1.14"
+ resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c"
+ dependencies:
+ sax ">=0.1.1"
+
xml@^1.0.0, xml@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"