chore(server): Move api-server in to it's own DIR
This commit is contained in:
committed by
mrugesh mohapatra
parent
9fba6bce4c
commit
46a217d0a5
43
api-server/server/middlewares/add-return-to.js
Normal file
43
api-server/server/middlewares/add-return-to.js
Normal file
@ -0,0 +1,43 @@
|
||||
const pathsOfNoReturn = [
|
||||
'link',
|
||||
'auth',
|
||||
'login',
|
||||
'logout',
|
||||
'signin',
|
||||
'signup',
|
||||
'fonts',
|
||||
'favicon',
|
||||
'js',
|
||||
'css'
|
||||
];
|
||||
|
||||
const pathsWhiteList = [
|
||||
'news',
|
||||
'challenges',
|
||||
'map',
|
||||
'news',
|
||||
'commit'
|
||||
];
|
||||
|
||||
const pathsOfNoReturnRegex = new RegExp(pathsOfNoReturn.join('|'), 'i');
|
||||
const whiteListRegex = new RegExp(pathsWhiteList.join('|'), 'i');
|
||||
|
||||
export default function addReturnToUrl() {
|
||||
return function(req, res, next) {
|
||||
// Remember original destination before login.
|
||||
var path = req.path.split('/')[1];
|
||||
|
||||
if (
|
||||
req.method !== 'GET' ||
|
||||
pathsOfNoReturnRegex.test(path) ||
|
||||
!whiteListRegex.test(path) ||
|
||||
(/hot/i).test(req.path)
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
req.session.returnTo = req.originalUrl.includes('/map') ?
|
||||
'/' :
|
||||
req.originalUrl;
|
||||
return next();
|
||||
};
|
||||
}
|
9
api-server/server/middlewares/constant-headers.js
Normal file
9
api-server/server/middlewares/constant-headers.js
Normal file
@ -0,0 +1,9 @@
|
||||
export default function constantHeaders() {
|
||||
return function(req, res, next) {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Headers',
|
||||
'Origin, X-Requested-With, Content-Type, Accept'
|
||||
);
|
||||
next();
|
||||
};
|
||||
}
|
4
api-server/server/middlewares/cookie-parser.js
Normal file
4
api-server/server/middlewares/cookie-parser.js
Normal file
@ -0,0 +1,4 @@
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
const cookieSecret = process.env.COOKIE_SECRET;
|
||||
export default cookieParser.bind(cookieParser, cookieSecret);
|
98
api-server/server/middlewares/csp.js
Normal file
98
api-server/server/middlewares/csp.js
Normal file
@ -0,0 +1,98 @@
|
||||
import helmet from 'helmet';
|
||||
|
||||
let trusted = [
|
||||
"'self'",
|
||||
'https://search.freecodecamp.org',
|
||||
'https://www.freecodecamp.rocks',
|
||||
'https://api.freecodecamp.rocks',
|
||||
'https://' + process.env.AUTH0_DOMAIN
|
||||
];
|
||||
|
||||
const host = process.env.HOST || 'localhost';
|
||||
const port = process.env.SYNC_PORT || '3000';
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
trusted = trusted.concat([
|
||||
`ws://${host}:${port}`
|
||||
]);
|
||||
}
|
||||
|
||||
export default function csp() {
|
||||
return helmet.contentSecurityPolicy({
|
||||
directives: {
|
||||
defaultSrc: trusted.concat([
|
||||
'https://*.cloudflare.com',
|
||||
'*.cloudflare.com'
|
||||
]),
|
||||
connectSrc: trusted.concat([
|
||||
'https://glitch.com',
|
||||
'https://*.glitch.com',
|
||||
'https://*.glitch.me',
|
||||
'https://*.cloudflare.com',
|
||||
'https://*.algolia.net'
|
||||
]),
|
||||
scriptSrc: [
|
||||
"'unsafe-eval'",
|
||||
"'unsafe-inline'",
|
||||
'*.google-analytics.com',
|
||||
'*.gstatic.com',
|
||||
'https://*.cloudflare.com',
|
||||
'*.cloudflare.com',
|
||||
'https://*.gitter.im',
|
||||
'https://*.cdnjs.com',
|
||||
'*.cdnjs.com',
|
||||
'https://*.jsdelivr.com',
|
||||
'*.jsdelivr.com',
|
||||
'*.twimg.com',
|
||||
'https://*.twimg.com',
|
||||
'*.youtube.com',
|
||||
'*.ytimg.com'
|
||||
].concat(trusted),
|
||||
styleSrc: [
|
||||
"'unsafe-inline'",
|
||||
'*.gstatic.com',
|
||||
'*.googleapis.com',
|
||||
'*.bootstrapcdn.com',
|
||||
'https://*.bootstrapcdn.com',
|
||||
'*.cloudflare.com',
|
||||
'https://*.cloudflare.com',
|
||||
'https://use.fontawesome.com'
|
||||
].concat(trusted),
|
||||
fontSrc: [
|
||||
'*.cloudflare.com',
|
||||
'https://*.cloudflare.com',
|
||||
'*.bootstrapcdn.com',
|
||||
'*.googleapis.com',
|
||||
'*.gstatic.com',
|
||||
'https://*.bootstrapcdn.com',
|
||||
'https://use.fontawesome.com'
|
||||
].concat(trusted),
|
||||
imgSrc: [
|
||||
// allow all input since we have user submitted images for
|
||||
// public profile
|
||||
'*',
|
||||
'data:'
|
||||
],
|
||||
mediaSrc: [
|
||||
'*.bitly.com',
|
||||
'*.amazonaws.com',
|
||||
'*.twitter.com'
|
||||
].concat(trusted),
|
||||
frameSrc: [
|
||||
'*.gitter.im',
|
||||
'*.gitter.im https:',
|
||||
'*.youtube.com',
|
||||
'*.twitter.com',
|
||||
'*.ghbtns.com',
|
||||
'*.freecatphotoapp.com',
|
||||
'freecodecamp.github.io'
|
||||
].concat(trusted)
|
||||
},
|
||||
// set to true if you only want to report errors
|
||||
reportOnly: false,
|
||||
// set to true if you want to set all headers
|
||||
setAllHeaders: false,
|
||||
// set to true if you want to force buggy CSP in Safari 5
|
||||
safari5: false
|
||||
});
|
||||
}
|
19
api-server/server/middlewares/csurf.js
Normal file
19
api-server/server/middlewares/csurf.js
Normal file
@ -0,0 +1,19 @@
|
||||
import csurf from 'csurf';
|
||||
|
||||
export default function() {
|
||||
const protection = csurf(
|
||||
{
|
||||
cookie: {
|
||||
domain: process.env.COOKIE_DOMAIN || 'localhost'
|
||||
}
|
||||
}
|
||||
);
|
||||
return function csrf(req, res, next) {
|
||||
|
||||
const path = req.path.split('/')[1];
|
||||
if (/(^api$|^external$|^internal$|^p$)/.test(path)) {
|
||||
return next();
|
||||
}
|
||||
return protection(req, res, next);
|
||||
};
|
||||
}
|
34
api-server/server/middlewares/email-not-verified-notice.js
Normal file
34
api-server/server/middlewares/email-not-verified-notice.js
Normal file
@ -0,0 +1,34 @@
|
||||
import dedent from 'dedent';
|
||||
|
||||
const ALLOWED_METHODS = ['GET'];
|
||||
const EXCLUDED_PATHS = [
|
||||
'/api/flyers/findOne',
|
||||
'/signout',
|
||||
'/accept-privacy-terms',
|
||||
'/update-email',
|
||||
'/confirm-email',
|
||||
'/passwordless-change',
|
||||
'/external/services/user'
|
||||
];
|
||||
|
||||
export default function emailNotVerifiedNotice() {
|
||||
return function(req, res, next) {
|
||||
if (
|
||||
ALLOWED_METHODS.indexOf(req.method) !== -1 &&
|
||||
EXCLUDED_PATHS.indexOf(req.path) === -1
|
||||
) {
|
||||
const { user } = req;
|
||||
if (user && (!user.email || user.email === '' || !user.emailVerified)) {
|
||||
req.flash(
|
||||
'info',
|
||||
dedent`
|
||||
New privacy laws now require that we have an email address where we can reach
|
||||
you. Please update your email address in the <a href='/settings'>settings</a>
|
||||
and click the link we send you to confirm.
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
return next();
|
||||
};
|
||||
}
|
90
api-server/server/middlewares/error-handlers.js
Normal file
90
api-server/server/middlewares/error-handlers.js
Normal file
@ -0,0 +1,90 @@
|
||||
// import { inspect } from 'util';
|
||||
// import _ from 'lodash/fp';
|
||||
import accepts from 'accepts';
|
||||
|
||||
import { homeLocation } from '../../../config/env';
|
||||
|
||||
import { unwrapHandledError } from '../utils/create-handled-error.js';
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
// const toString = Object.prototype.toString;
|
||||
// is full error or just trace
|
||||
// _.toString(new Error('foo')) => "Error: foo
|
||||
// Object.prototype.toString.call(new Error('foo')) => "[object Error]"
|
||||
// const isInspect = val => !val.stack && _.toString(val) === toString.call(val);
|
||||
// const stringifyErr = val => {
|
||||
// if (val.stack) {
|
||||
// return String(val.stack);
|
||||
// }
|
||||
|
||||
// const str = String(val);
|
||||
|
||||
// return isInspect(val) ?
|
||||
// inspect(val) :
|
||||
// str;
|
||||
// };
|
||||
|
||||
// const createStackHtml = _.flow(
|
||||
// _.cond([
|
||||
// [isInspect, err => [err]],
|
||||
// // may be stack or just err.msg
|
||||
// [_.stubTrue, _.flow(stringifyErr, _.split('\n'), _.tail) ]
|
||||
// ]),
|
||||
// _.map(_.escape),
|
||||
// _.map(line => `<li>${line}</lin>`),
|
||||
// _.join('')
|
||||
// );
|
||||
|
||||
// const createErrorTitle = _.cond([
|
||||
// [
|
||||
// _.negate(isInspect),
|
||||
// _.flow(stringifyErr, _.split('\n'), _.head, _.defaultTo('Error'))
|
||||
// ],
|
||||
// [_.stubTrue, _.constant('Error')]
|
||||
// ]);
|
||||
|
||||
export default function prodErrorHandler() {
|
||||
// error handling in production.
|
||||
// disabling eslint due to express parity rules for error handlers
|
||||
return function(err, req, res, next) {
|
||||
// eslint-disable-line
|
||||
const handled = unwrapHandledError(err);
|
||||
// respect handled error status
|
||||
let status = handled.status || err.status || res.statusCode;
|
||||
if (!handled.status && status < 400) {
|
||||
status = 500;
|
||||
}
|
||||
res.status(status);
|
||||
|
||||
// parse res type
|
||||
const accept = accepts(req);
|
||||
const type = accept.type('html', 'json', 'text');
|
||||
|
||||
const redirectTo = handled.redirectTo || `${homeLocation}/`;
|
||||
const message =
|
||||
handled.message || 'Oops! Something went wrong. Please try again later';
|
||||
|
||||
if (isDev) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (type === 'html') {
|
||||
if (typeof req.flash === 'function') {
|
||||
req.flash(handled.type || 'danger', message);
|
||||
}
|
||||
return res.redirectWithFlash(redirectTo);
|
||||
// json
|
||||
} else if (type === 'json') {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.send({
|
||||
type: handled.type || 'errors',
|
||||
message
|
||||
});
|
||||
// plain text
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
return res.send(message);
|
||||
}
|
||||
};
|
||||
}
|
49
api-server/server/middlewares/error-reporter.js
Normal file
49
api-server/server/middlewares/error-reporter.js
Normal file
@ -0,0 +1,49 @@
|
||||
import debug from 'debug';
|
||||
import Rollbar from 'rollbar';
|
||||
import {
|
||||
isHandledError,
|
||||
unwrapHandledError
|
||||
} from '../utils/create-handled-error.js';
|
||||
|
||||
const { ROLLBAR_APP_ID } = process.env;
|
||||
|
||||
const rollbar = new Rollbar(ROLLBAR_APP_ID);
|
||||
const log = debug('fcc:middlewares:error-reporter');
|
||||
|
||||
const errTemplate = ({message, ...restError}, req) => `
|
||||
Time: ${new Date(Date.now()).toISOString()}
|
||||
Error: ${message}
|
||||
Is authenticated user: ${!!req.user}
|
||||
Route: ${JSON.stringify(req.route, null, 2)}
|
||||
|
||||
${JSON.stringify(restError, null, 2)}
|
||||
|
||||
`;
|
||||
|
||||
export default function errrorReporter() {
|
||||
if (process.env.NODE_ENV !== 'production' && process.env.ERROR_REPORTER) {
|
||||
return (err, req, res, next) => {
|
||||
console.error(errTemplate(err, req));
|
||||
|
||||
if (isHandledError(err)) {
|
||||
// log out user messages in development
|
||||
const handled = unwrapHandledError(err);
|
||||
log(handled.message);
|
||||
}
|
||||
next(err);
|
||||
};
|
||||
}
|
||||
return (err, req, res, next) => {
|
||||
// handled errors do not need to be reported,
|
||||
// they report a message and maybe redirect the user
|
||||
// errors with status codes shouldn't be reported
|
||||
// as they are usually user messages
|
||||
if (isHandledError(err) || err.statusCode || err.status) {
|
||||
return next(err);
|
||||
}
|
||||
// logging the error provides us with more information,
|
||||
// i.e isAuthenticatedUser, req.route
|
||||
console.error(errTemplate(err, req));
|
||||
return rollbar.error(err.message, err);
|
||||
};
|
||||
}
|
23
api-server/server/middlewares/express-extensions.js
Normal file
23
api-server/server/middlewares/express-extensions.js
Normal file
@ -0,0 +1,23 @@
|
||||
import qs from 'query-string';
|
||||
|
||||
// add rx methods to express
|
||||
export default function() {
|
||||
return function expressExtensions(req, res, next) {
|
||||
res.redirectWithFlash = uri => {
|
||||
const flash = req.flash();
|
||||
res.redirect(
|
||||
`${uri}?${qs.stringify(
|
||||
{ messages: qs.stringify(flash, { arrayFormat: 'index' }) },
|
||||
{ arrayFormat: 'index' }
|
||||
)}`
|
||||
);
|
||||
};
|
||||
res.sendFlash = (type, message) => {
|
||||
if (type && message) {
|
||||
req.flash(type, message);
|
||||
}
|
||||
return res.json(req.flash());
|
||||
};
|
||||
next();
|
||||
};
|
||||
}
|
30
api-server/server/middlewares/flash-cheaters.js
Normal file
30
api-server/server/middlewares/flash-cheaters.js
Normal file
@ -0,0 +1,30 @@
|
||||
import dedent from 'dedent';
|
||||
|
||||
const ALLOWED_METHODS = ['GET'];
|
||||
const EXCLUDED_PATHS = [
|
||||
'/api/flyers/findOne',
|
||||
'/challenges/current-challenge',
|
||||
'/challenges/next-challenge',
|
||||
'/map-aside',
|
||||
'/signout'
|
||||
];
|
||||
|
||||
export default function flashCheaters() {
|
||||
return function(req, res, next) {
|
||||
if (
|
||||
ALLOWED_METHODS.indexOf(req.method) !== -1 &&
|
||||
EXCLUDED_PATHS.indexOf(req.path) === -1 &&
|
||||
req.user && req.url !== '/' && req.user.isCheater
|
||||
) {
|
||||
req.flash(
|
||||
'danger',
|
||||
dedent`
|
||||
Upon review, this account has been flagged for academic
|
||||
dishonesty. If you’re the owner of this account contact
|
||||
team@freecodecamp.org for details.
|
||||
`
|
||||
);
|
||||
}
|
||||
return next();
|
||||
};
|
||||
}
|
59
api-server/server/middlewares/jade-helpers.js
Normal file
59
api-server/server/middlewares/jade-helpers.js
Normal file
@ -0,0 +1,59 @@
|
||||
import _ from 'lodash';
|
||||
import manifest from '../rev-manifest';
|
||||
|
||||
let chunkManifest;
|
||||
try {
|
||||
chunkManifest = require('../manifests/chunk-manifest.json');
|
||||
} catch (err) {
|
||||
chunkManifest = {};
|
||||
}
|
||||
|
||||
chunkManifest = Object.keys(chunkManifest).reduce((manifest, key) => {
|
||||
manifest[key] = '/' + chunkManifest[key];
|
||||
return manifest;
|
||||
}, {});
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
const challengesRegex = /^(bonfire|waypoint|zipline|basejump|checkpoint):\s/i;
|
||||
|
||||
function rev(scopedPrepend, asset) {
|
||||
if (isDev) {
|
||||
// do not use revision in dev mode
|
||||
return `${scopedPrepend}/${asset}`;
|
||||
}
|
||||
return `${scopedPrepend}/${ manifest[asset] || asset }`;
|
||||
}
|
||||
|
||||
function removeOldTerms(str = '') {
|
||||
return str.replace(challengesRegex, '');
|
||||
}
|
||||
|
||||
const cacheBreaker = isDev ?
|
||||
// add cacheBreaker in dev instead of rev manifest
|
||||
asset => `${asset}?cacheBreaker=${Math.random()}` :
|
||||
_.identity;
|
||||
|
||||
export default function jadeHelpers() {
|
||||
return function jadeHelpersMiddleware(req, res, next) {
|
||||
Object.assign(
|
||||
res.locals,
|
||||
{
|
||||
removeOldTerms,
|
||||
rev,
|
||||
cacheBreaker,
|
||||
// static data
|
||||
user: req.user,
|
||||
chunkManifest,
|
||||
_csrf: req.csrfToken ? req.csrfToken() : null,
|
||||
theme: req.user &&
|
||||
req.user.theme ||
|
||||
req.cookies.theme ||
|
||||
'default'
|
||||
}
|
||||
);
|
||||
if (req.csrfToken) {
|
||||
res.expose({ token: res.locals._csrf }, 'csrf');
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
69
api-server/server/middlewares/jwt-authorization.js
Normal file
69
api-server/server/middlewares/jwt-authorization.js
Normal file
@ -0,0 +1,69 @@
|
||||
import loopback from 'loopback';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { isBefore } from 'date-fns';
|
||||
|
||||
import { homeLocation } from '../../../config/env';
|
||||
|
||||
import { wrapHandledError } from '../utils/create-handled-error';
|
||||
|
||||
export default () => function authorizeByJWT(req, res, next) {
|
||||
const path = req.path.split('/')[1];
|
||||
if (/^external$|^internal$/.test(path)) {
|
||||
const cookie = req.signedCookies && req.signedCookies['jwt_access_token'] ||
|
||||
req.cookie && req.cookie['jwt_access_token'];
|
||||
if (!cookie) {
|
||||
throw wrapHandledError(
|
||||
new Error('Access token is required for this request'),
|
||||
{
|
||||
type: 'info',
|
||||
redirect: `${homeLocation}/signin`,
|
||||
message: 'Access token is required for this request',
|
||||
status: 403
|
||||
}
|
||||
);
|
||||
}
|
||||
let token;
|
||||
try {
|
||||
token = jwt.verify(cookie, process.env.JWT_SECRET);
|
||||
} catch (err) {
|
||||
throw wrapHandledError(
|
||||
new Error(err.message),
|
||||
{
|
||||
type: 'info',
|
||||
redirect: `${homeLocation}/signin`,
|
||||
message: 'Your access token is invalid',
|
||||
status: 403
|
||||
}
|
||||
);
|
||||
}
|
||||
const { accessToken: {created, ttl, userId }} = token;
|
||||
const valid = isBefore(Date.now(), Date.parse(created) + ttl);
|
||||
if (!valid) {
|
||||
throw wrapHandledError(
|
||||
new Error('Access token is no longer vaild'),
|
||||
{
|
||||
type: 'info',
|
||||
redirect: `${homeLocation}/signin`,
|
||||
message: 'Access token is no longer vaild',
|
||||
status: 403
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!req.user) {
|
||||
const User = loopback.getModelByType('User');
|
||||
return User.findById(userId)
|
||||
.then(user => {
|
||||
if (user) {
|
||||
user.points = user.progressTimestamps.length;
|
||||
req.user = user;
|
||||
}
|
||||
return;
|
||||
})
|
||||
.then(next)
|
||||
.catch(next);
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
return next();
|
||||
};
|
21
api-server/server/middlewares/passport-login.js
Normal file
21
api-server/server/middlewares/passport-login.js
Normal file
@ -0,0 +1,21 @@
|
||||
import _ from 'lodash';
|
||||
import { Observable } from 'rx';
|
||||
import { login } from 'passport/lib/http/request';
|
||||
|
||||
// make login polymorphic
|
||||
// if supplied callback it works as normal
|
||||
// if called without callback it returns an observable
|
||||
// login(user, options?, cb?) => Void|Observable
|
||||
function login$(...args) {
|
||||
console.log('args');
|
||||
if (_.isFunction(_.last(args))) {
|
||||
return login.apply(this, args);
|
||||
}
|
||||
return Observable.fromNodeCallback(login).apply(this, args);
|
||||
}
|
||||
export default function passportLogin() {
|
||||
return (req, res, next) => {
|
||||
req.login = req.logIn = login$;
|
||||
next();
|
||||
};
|
||||
}
|
26
api-server/server/middlewares/privacy-terms-notice.js
Normal file
26
api-server/server/middlewares/privacy-terms-notice.js
Normal file
@ -0,0 +1,26 @@
|
||||
const ALLOWED_METHODS = ['GET'];
|
||||
const EXCLUDED_PATHS = [
|
||||
'/api/flyers/findOne',
|
||||
'/signout',
|
||||
'/accept-privacy-terms',
|
||||
'/update-email',
|
||||
'/confirm-email',
|
||||
'/passwordless-change',
|
||||
'/external/services/user'
|
||||
];
|
||||
|
||||
export default function privacyTermsNotAcceptedNotice() {
|
||||
return function(req, res, next) {
|
||||
if (
|
||||
ALLOWED_METHODS.indexOf(req.method) !== -1 &&
|
||||
EXCLUDED_PATHS.indexOf(req.path) === -1
|
||||
) {
|
||||
const { user } = req;
|
||||
if (user && user.acceptedPrivacyTerms !== true) {
|
||||
res.redirect('/accept-privacy-terms');
|
||||
return next;
|
||||
}
|
||||
}
|
||||
return next();
|
||||
};
|
||||
}
|
20
api-server/server/middlewares/sessions.js
Normal file
20
api-server/server/middlewares/sessions.js
Normal file
@ -0,0 +1,20 @@
|
||||
import session from 'express-session';
|
||||
import MongoStoreFactory from 'connect-mongo';
|
||||
|
||||
const MongoStore = MongoStoreFactory(session);
|
||||
const sessionSecret = process.env.SESSION_SECRET;
|
||||
const url = process.env.MONGODB || process.env.MONGOHQ_URL;
|
||||
|
||||
export default function sessionsMiddleware() {
|
||||
return session({
|
||||
// 900 day session cookie
|
||||
cookie: { maxAge: 900 * 24 * 60 * 60 * 1000 },
|
||||
// resave forces session to be resaved
|
||||
// regardless of whether it was modified
|
||||
// this causes race conditions during parallel req
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
secret: sessionSecret,
|
||||
store: new MongoStore({ url })
|
||||
});
|
||||
}
|
57
api-server/server/middlewares/validator.js
Normal file
57
api-server/server/middlewares/validator.js
Normal file
@ -0,0 +1,57 @@
|
||||
import validator from 'express-validator';
|
||||
import { isPoly } from '../../common/utils/polyvinyl';
|
||||
|
||||
const isObject = val => !!val && typeof val === 'object';
|
||||
|
||||
export default function() {
|
||||
return validator({
|
||||
customValidators: {
|
||||
matchRegex(param, regex) {
|
||||
return regex.test(param);
|
||||
},
|
||||
isString(value) {
|
||||
return typeof value === 'string';
|
||||
},
|
||||
isNumber(value) {
|
||||
return typeof value === 'number';
|
||||
},
|
||||
isFiles(value) {
|
||||
if (!isObject(value)) {
|
||||
return false;
|
||||
}
|
||||
const keys = Object.keys(value);
|
||||
return !!keys.length &&
|
||||
// every key is a file
|
||||
keys.every(key => isObject(value[key])) &&
|
||||
// every file has contents
|
||||
keys.map(key => value[key]).every(file => isPoly(file));
|
||||
}
|
||||
},
|
||||
customSanitizers: {
|
||||
// Refer : http://stackoverflow.com/a/430240/1932901
|
||||
trimTags(value) {
|
||||
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
|
||||
const tagOrComment = new RegExp(
|
||||
'<(?:'
|
||||
// Comment body.
|
||||
+ '!--(?:(?:-*[^->])*--+|-?)'
|
||||
// Special "raw text" elements whose content should be elided.
|
||||
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
|
||||
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
|
||||
// Regular name
|
||||
+ '|/?[a-z]'
|
||||
+ tagBody
|
||||
+ ')>',
|
||||
'gi'
|
||||
);
|
||||
let rawValue;
|
||||
do {
|
||||
rawValue = value;
|
||||
value = value.replace(tagOrComment, '');
|
||||
} while (value !== rawValue);
|
||||
|
||||
return value.replace(/</g, '<');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user