diff --git a/app.js b/app.js
index d230596c0..aca80a4b5 100644
--- a/app.js
+++ b/app.js
@@ -7,7 +7,7 @@ const passport = require('./services/passport');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('./services/redis');
-const cookieParser = require('cookie-parser');
+const csrf = require('csurf');
const app = express();
@@ -43,6 +43,7 @@ const session_opts = {
rolling: true,
saveUninitialized: false,
resave: false,
+ unset: 'destroy',
name: 'talk.sid',
cookie: {
secure: false,
@@ -66,12 +67,6 @@ if (app.get('env') === 'production') {
app.use(session(session_opts));
-//==============================================================================
-// CSRF MIDDLEWARE
-//==============================================================================
-
-app.use(cookieParser());
-
//==============================================================================
// PASSPORT MIDDLEWARE
//==============================================================================
@@ -80,6 +75,29 @@ app.use(cookieParser());
app.use(passport.initialize());
app.use(passport.session());
+//==============================================================================
+// CSRF MIDDLEWARE
+//==============================================================================
+
+if (process.env.TEST_MODE === 'unit') {
+
+ // Add this fake test token in the event we are in unit test mode, and don't
+ // include the CSRF protection.
+ app.locals.csrfToken = 'UNIT_TESTS';
+
+} else {
+
+ // Setup route middlewares for CSRF protection.
+ // Default ignore methods are GET, HEAD, OPTIONS
+ app.use(csrf({}));
+ app.use((req, res, next) => {
+ res.locals.csrfToken = req.csrfToken();
+
+ next();
+ });
+
+}
+
//==============================================================================
// ROUTES
//==============================================================================
diff --git a/bin/cli-jobs b/bin/cli-jobs
index 60a7b8efb..879927197 100755
--- a/bin/cli-jobs
+++ b/bin/cli-jobs
@@ -6,6 +6,7 @@
const program = require('commander');
const scraper = require('../services/scraper');
+const mailer = require('../services/mailer');
const util = require('../util');
const mongoose = require('../services/mongoose');
const kue = require('../services/kue');
@@ -19,13 +20,16 @@ util.onshutdown([
*/
function processJobs() {
- // Start the processor.
+ // Start the scraper processor.
scraper.process();
+ // Start the mail processor.
+ mailer.process();
+
// The scraper only needs to shutdown when the scraper has actually been
// started.
util.onshutdown([
- () => scraper.shutdown()
+ () => kue.Task.shutdown()
]);
}
diff --git a/bin/cli-serve b/bin/cli-serve
index 05fe6efc7..c8cf37a47 100755
--- a/bin/cli-serve
+++ b/bin/cli-serve
@@ -5,6 +5,8 @@ const debug = require('debug')('talk:server');
const http = require('http');
const init = require('../init');
const scraper = require('../services/scraper');
+const mailer = require('../services/mailer');
+const kue = require('../services/kue');
const mongoose = require('../services/mongoose');
const util = require('../util');
@@ -12,7 +14,7 @@ const util = require('../util');
* Get port from environment and store in Express.
*/
-const port = normalizePort(process.env.TALK_PORT || (process.env.NODE_ENV === 'test' ? '3011' : '3000'));
+const port = normalizePort(process.env.TALK_PORT || '3000');
app.set('port', port);
@@ -119,15 +121,18 @@ startApp();
// Enable job processing on the thread if enabled.
if (program.jobs) {
- // Start the processor.
+ // Start the scraper processor.
scraper.process();
+
+ // Start the mail processor.
+ mailer.process();
}
// Define a safe shutdown function to call in the event we need to shutdown
// because the node hooks are below which will interrupt the shutdown process.
// Shutdown the mongoose connection, the app server, and the scraper.
util.onshutdown([
- () => program.jobs ? scraper.shutdown() : null,
+ () => program.jobs ? kue.Task.shutdown() : null,
() => mongoose.disconnect(),
() => server.close()
]);
diff --git a/bin/cli-users b/bin/cli-users
index ae688e122..fd9e30c7f 100755
--- a/bin/cli-users
+++ b/bin/cli-users
@@ -80,12 +80,16 @@ function createUser(options) {
.then((user) => {
console.log(`Created user ${user.id}.`);
- return User
- .addRoleToUser(user.id, result.role.trim())
- .then(() => {
- console.log(`Added the admin ${result.role.trim()} to User ${user.id}.`);
- util.shutdown();
- });
+ if (result.role && result.role.length > 0) {
+ return User
+ .addRoleToUser(user.id, result.role.trim())
+ .then(() => {
+ console.log(`Added the admin ${result.role.trim()} to User ${user.id}.`);
+ util.shutdown();
+ });
+ } else {
+ util.shutdown();
+ }
})
.catch((err) => {
console.error(err);
diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js
index 8cc094d5f..fb469588b 100644
--- a/client/coral-admin/src/actions/auth.js
+++ b/client/coral-admin/src/actions/auth.js
@@ -11,19 +11,12 @@ export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
coralApi('/auth')
.then(result => {
- if (result.csrfToken !== null) {
- dispatch(check_csrf(result.csrfToken));
- }
-
const isAdmin = !!result.user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
};
-// Set CSRF Token
-export const check_csrf = (_csrf) => ({type: actions.CHECK_CSRF_TOKEN, _csrf});
-
// LogOut Actions
const logOutRequest = () => ({type: actions.LOGOUT_REQUEST});
diff --git a/client/coral-admin/src/actions/comments.js b/client/coral-admin/src/actions/comments.js
index 98efae60d..e8276a9da 100644
--- a/client/coral-admin/src/actions/comments.js
+++ b/client/coral-admin/src/actions/comments.js
@@ -34,9 +34,8 @@ export const fetchModerationQueueComments = () => {
// Create a new comment
export const createComment = (name, body) => {
- return (dispatch, getState) => {
- const _csrf = getState().auth.get('_csrf');
- const formData = {body, name, _csrf};
+ return (dispatch) => {
+ const formData = {body, name};
return coralApi('/comments', {method: 'POST', body: formData})
.then(res => dispatch({type: commentTypes.COMMENT_CREATE_SUCCESS, comment: res}))
.catch(error => dispatch({type: commentTypes.COMMENT_CREATE_FAILED, error}));
diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js
index 35ba11fac..c2d738460 100644
--- a/client/coral-admin/src/actions/community.js
+++ b/client/coral-admin/src/actions/community.js
@@ -41,18 +41,16 @@ export const newPage = () => ({
type: COMMENTERS_NEW_PAGE
});
-export const setRole = (id, role) => (dispatch, getState) => {
+export const setRole = (id, role) => (dispatch) => {
- const _csrf = getState().auth.get('_csrf');
- return coralApi(`/users/${id}/role`, {method: 'POST', body: {role}, _csrf: _csrf})
+ return coralApi(`/users/${id}/role`, {method: 'POST', body: {role}})
.then(() => {
return dispatch({type: SET_ROLE, id, role});
});
};
-export const setCommenterStatus = (id, status) => (dispatch, getState) => {
- const _csrf = getState().auth.get('_csrf');
- return coralApi(`/users/${id}/status`, {method: 'POST', body: {status}, _csrf: _csrf})
+export const setCommenterStatus = (id, status) => (dispatch) => {
+ return coralApi(`/users/${id}/status`, {method: 'POST', body: {status}})
.then(() => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
});
diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js
index 30c20290f..30d9cd34b 100644
--- a/client/coral-admin/src/actions/users.js
+++ b/client/coral-admin/src/actions/users.js
@@ -6,10 +6,9 @@ import * as actions from '../constants/user';
*/
// change status of a user
export const userStatusUpdate = (status, userId, commentId) => {
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch({type: actions.UPDATE_STATUS_REQUEST});
- const _csrf = getState().auth.get('_csrf');
- return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}, _csrf})
+ return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then(res => dispatch({type: actions.UPDATE_STATUS_SUCCESS, res}))
.catch(error => dispatch({type: actions.UPDATE_STATUS_FAILURE, error}));
};
diff --git a/client/coral-admin/src/components/CommentBox.js b/client/coral-admin/src/components/CommentBox.js
index fc9e6c9ea..ce3438960 100644
--- a/client/coral-admin/src/components/CommentBox.js
+++ b/client/coral-admin/src/components/CommentBox.js
@@ -7,13 +7,13 @@ import {Button} from 'react-mdl';
export default class CommentBox extends React.Component {
constructor (props) {
super(props);
- this.state = {name: '', body: '', _csrf: props._csrf};
+ this.state = {name: '', body: ''};
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit () {
- const {name, body, _csrf} = this.state;
- this.props.onSubmit({name, body, _csrf});
+ const {name, body} = this.state;
+ this.props.onSubmit({name, body});
this.setState({body: '', name: ''});
}
diff --git a/client/coral-admin/src/reducers/auth.js b/client/coral-admin/src/reducers/auth.js
index 6f3a102de..095ef7aac 100644
--- a/client/coral-admin/src/reducers/auth.js
+++ b/client/coral-admin/src/reducers/auth.js
@@ -4,8 +4,7 @@ import * as actions from '../constants/auth';
const initialState = Map({
loggedIn: false,
user: null,
- isAdmin: false,
- _csrf: ''
+ isAdmin: false
});
export default function auth (state = initialState, action) {
@@ -26,9 +25,6 @@ export default function auth (state = initialState, action) {
.set('user', action.user);
case actions.LOGOUT_SUCCESS:
return initialState;
- case actions.CHECK_CSRF_TOKEN:
- return state
- .set('_csrf', action._csrf);
default :
return state;
}
diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json
index 3397a521b..4142c109f 100644
--- a/client/coral-admin/src/translations.json
+++ b/client/coral-admin/src/translations.json
@@ -146,6 +146,7 @@
"moderate": "Moderar",
"configure": "Configurar",
"community": "Comunidad",
+ "streams": "Streams",
"closed-comments-desc": "Escribe un mensaje para cuando los comentarios se encuentran cerrados",
"closed-comments-label": "Escribe un mensaje...",
"never": "Nunca",
diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js
index 581fb729c..106224c25 100644
--- a/client/coral-framework/actions/auth.js
+++ b/client/coral-framework/actions/auth.js
@@ -23,10 +23,9 @@ const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST});
const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin});
const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error});
-export const fetchSignIn = (formData) => (dispatch, getState) => {
+export const fetchSignIn = (formData) => (dispatch) => {
dispatch(signInRequest());
- const _csrf = getState().auth.get('_csrf');
- coralApi('/auth/local', {method: 'POST', body: formData, _csrf})
+ coralApi('/auth/local', {method: 'POST', body: formData})
.then(({user}) => {
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
dispatch(signInSuccess(user, isAdmin));
@@ -73,11 +72,10 @@ const signUpRequest = () => ({type: actions.FETCH_SIGNUP_REQUEST});
const signUpSuccess = user => ({type: actions.FETCH_SIGNUP_SUCCESS, user});
const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error});
-export const fetchSignUp = formData => (dispatch, getState) => {
+export const fetchSignUp = formData => (dispatch) => {
dispatch(signUpRequest());
- const _csrf = getState().auth.get('_csrf');
- coralApi('/users', {method: 'POST', body: formData, _csrf})
+ coralApi('/users', {method: 'POST', body: formData})
.then(({user}) => {
dispatch(signUpSuccess(user));
setTimeout(() =>{
@@ -93,11 +91,9 @@ const forgotPassowordRequest = () => ({type: actions.FETCH_FORGOT_PASSWORD_REQUE
const forgotPassowordSuccess = () => ({type: actions.FETCH_FORGOT_PASSWORD_SUCCESS});
const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILURE});
-export const fetchForgotPassword = email => (dispatch, getState) => {
+export const fetchForgotPassword = email => (dispatch) => {
dispatch(forgotPassowordRequest(email));
-
- const _csrf = getState().auth.get('_csrf');
- coralApi('/users/request-password-reset', {method: 'POST', body: {email}, _csrf})
+ coralApi('/account/password/reset', {method: 'POST', body: {email}})
.then(() => dispatch(forgotPassowordSuccess()))
.catch(error => dispatch(forgotPassowordFailure(error)));
};
@@ -125,15 +121,11 @@ export const invalidForm = error => ({type: actions.INVALID_FORM, error});
const checkLoginRequest = () => ({type: actions.CHECK_LOGIN_REQUEST});
const checkLoginSuccess = (user, isAdmin) => ({type: actions.CHECK_LOGIN_SUCCESS, user, isAdmin});
const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
-const checkCSRF = (_csrf) => ({type: actions.CHECK_CSRF_TOKEN, _csrf});
export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
coralApi('/auth')
.then((result) => {
- if (result.csrfToken !== null) {
- dispatch(checkCSRF(result.csrfToken));
- }
if (!result.user) {
throw new Error('Not logged in');
}
diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js
index 303800a8f..48418b2a2 100644
--- a/client/coral-framework/actions/items.js
+++ b/client/coral-framework/actions/items.js
@@ -190,11 +190,10 @@ export function getItemsArray (ids) {
*/
export function postItem (item, type, id) {
- return (dispatch, getState) => {
+ return (dispatch) => {
if (id) {
item.id = id;
}
- item._csrf = getState().auth.get('_csrf');
return coralApi(`/${type}`, {method: 'POST', body: item})
.then((json) => {
dispatch(addItem({...item, id:json.id}, type));
@@ -221,8 +220,7 @@ export function postItem (item, type, id) {
*/
export function postAction (item_id, item_type, action) {
- return (dispatch, getState) => {
- action._csrf = getState().auth.get('_csrf');
+ return (dispatch) => {
return coralApi(`/${item_type}/${item_id}/actions`, {method: 'POST', body: action})
.then((json) => {
dispatch(updateItem(action.item_id, action.action_type, action.id, item_type));
diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js
index 2206d306b..aeb8d0989 100644
--- a/client/coral-framework/actions/user.js
+++ b/client/coral-framework/actions/user.js
@@ -14,10 +14,10 @@ const saveBioFailure = error => ({type: actions.SAVE_BIO_FAILURE, error});
export const saveBio = (user_id, formData) => dispatch => {
dispatch(saveBioRequest());
- coralApi(`/users/${user_id}/bio`, {method: 'PUT', body: formData})
- .then(({settings}) => {
+ coralApi('/account/settings', {method: 'PUT', body: formData})
+ .then(() => {
dispatch(addNotification('success', lang.t('successBioUpdate')));
- dispatch(saveBioSuccess(settings));
+ dispatch(saveBioSuccess(formData));
})
.catch(error => dispatch(saveBioFailure(error)));
};
diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js
index 6e09726d3..9c6f508fe 100644
--- a/client/coral-framework/constants/user.js
+++ b/client/coral-framework/constants/user.js
@@ -4,3 +4,4 @@ export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE';
export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST';
export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS';
export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE';
+export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
diff --git a/client/coral-framework/helpers/response.js b/client/coral-framework/helpers/response.js
index d8bfd28c4..d612aefb9 100644
--- a/client/coral-framework/helpers/response.js
+++ b/client/coral-framework/helpers/response.js
@@ -2,19 +2,28 @@ export const base = '/api/v1';
const buildOptions = (inputOptions = {}) => {
+ const csurfDOM = document.head.querySelector('[property=csrf]');
+
const defaultOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
- credentials: 'same-origin'
+ credentials: 'same-origin',
+ _csrf: csurfDOM ? csurfDOM.content : false
};
+
const options = Object.assign({}, defaultOptions, inputOptions);
- // Add CSRF field to each POST.
- if (options.method.toLowerCase() === 'post' && options._csrf) {
- options.body._csrf = options._csrf;
+ if (options._csrf) {
+ switch (options.method.toLowerCase()) {
+ case 'post':
+ case 'put':
+ case 'delete':
+ options.headers['x-csrf-token'] = options._csrf;
+ break;
+ }
}
if (options.method.toLowerCase() !== 'get') {
diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js
index a003b20ed..36f8e0764 100644
--- a/client/coral-framework/reducers/auth.js
+++ b/client/coral-framework/reducers/auth.js
@@ -11,8 +11,7 @@ const initialState = Map({
error: '',
passwordRequestSuccess: null,
passwordRequestFailure: null,
- successSignUp: false,
- _csrf: ''
+ successSignUp: false
});
const purge = user => {
diff --git a/client/coral-framework/reducers/user.js b/client/coral-framework/reducers/user.js
index bd5f78e87..7e0604c4b 100644
--- a/client/coral-framework/reducers/user.js
+++ b/client/coral-framework/reducers/user.js
@@ -31,12 +31,13 @@ export default function user (state = initialState, action) {
case authActions.FETCH_SIGNIN_FACEBOOK_FAILURE:
return initialState;
case actions.SAVE_BIO_SUCCESS:
- return state
- .set('settings', action.settings);
+ return state.set('settings', action.settings);
case actions.COMMENTS_BY_USER_SUCCESS:
return state.set('myComments', action.comments);
case assetActions.MULTIPLE_ASSETS_SUCCESS:
return state.set('myAssets', action.assets);
+ case actions.LOGOUT_SUCCESS:
+ return initialState;
default :
return state;
}
diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js
index 097ac9d5b..23943318d 100644
--- a/client/coral-plugin-flags/FlagButton.js
+++ b/client/coral-plugin-flags/FlagButton.js
@@ -102,7 +102,7 @@ class FlagButton extends Component {
const popupMenu = getPopupMenu[this.state.step](this.state.itemType);
return
-