diff --git a/.eslintignore b/.eslintignore index fc0d50a25..96e49da53 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,5 +3,10 @@ client/lib **/*.html plugins/* !plugins/coral-plugin-facebook-auth +!plugins/coral-plugin-auth !plugins/coral-plugin-respect +!plugins/coral-plugin-offtopic +!plugins/coral-plugin-like +!plugins/coral-plugin-mod +!plugins/coral-plugin-love node_modules diff --git a/app.js b/app.js index 16840f965..d0f0f316b 100644 --- a/app.js +++ b/app.js @@ -11,6 +11,8 @@ const errors = require('./errors'); const {createGraphOptions} = require('./graph'); const apollo = require('graphql-server-express'); const accepts = require('accepts'); +const compression = require('compression'); +const cookieParser = require('cookie-parser'); const app = express(); @@ -31,6 +33,8 @@ app.set('trust proxy', 1); app.use(helmet({ frameguard: false })); +app.use(compression()); +app.use(cookieParser()); app.use(bodyParser.json()); //============================================================================== diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index f5c2f848a..8a18a8e3f 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -8,7 +8,7 @@ import { UPDATE_ASSETS } from '../constants/assets'; -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; /** * Action disptacher related to assets diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index d5a1eeea1..ca48764f1 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -1,5 +1,6 @@ +import bowser from 'bowser'; import * as actions from '../constants/auth'; -import coralApi from 'coral-framework/helpers/response'; +import coralApi from 'coral-framework/helpers/request'; import * as Storage from 'coral-framework/helpers/storage'; import {handleAuthToken} from 'coral-framework/actions/auth'; @@ -9,16 +10,31 @@ import {handleAuthToken} from 'coral-framework/actions/auth'; export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => { dispatch({type: actions.LOGIN_REQUEST}); - const params = {method: 'POST', body: {email, password}}; + + const params = { + method: 'POST', + body: { + email, + password + } + }; + if (recaptchaResponse) { - params.headers = {'X-Recaptcha-Response': recaptchaResponse}; + params.headers = { + 'X-Recaptcha-Response': recaptchaResponse + }; } + return coralApi('/auth/local', params) .then(({user, token}) => { + if (!user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } return dispatch(checkLoginFailure('not logged in')); } + dispatch(handleAuthToken(token)); dispatch(checkLoginSuccess(user)); }) @@ -83,7 +99,9 @@ export const checkLogin = () => (dispatch) => { return coralApi('/auth') .then(({user}) => { if (!user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } return dispatch(checkLoginFailure('not logged in')); } diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 9a8743762..36b9ffb0a 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -14,7 +14,7 @@ import { HIDE_SUSPENDUSER_DIALOG } from '../constants/community'; -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; export const fetchAccounts = (query = {}) => (dispatch) => { diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index 21ff58de1..15413e220 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -1,4 +1,4 @@ -import coralApi from 'coral-framework/helpers/response'; +import coralApi from 'coral-framework/helpers/request'; import * as actions from '../constants/install'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index b0530e77e..65c200420 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -1,4 +1,4 @@ -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; export const SETTINGS_LOADING = 'SETTINGS_LOADING'; export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED'; diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js index 2642db90d..e888d5b74 100644 --- a/client/coral-admin/src/actions/users.js +++ b/client/coral-admin/src/actions/users.js @@ -1,4 +1,4 @@ -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; import * as userTypes from '../constants/users'; /** diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 0306079f3..66ed9ad56 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -8,16 +8,14 @@ import './graphql'; import {addExternalConfig} from 'coral-embed-stream/src/actions/config'; import reducers from './reducers'; -import localStore, {injectReducers} from 'coral-framework/services/store'; +import store, {injectReducers} from 'coral-framework/services/store'; import AppRouter from './AppRouter'; import {pym} from 'coral-framework'; injectReducers(reducers); -const store = (window.opener && window.opener.coralStore) ? window.opener.coralStore : localStore; - // Don't run this in the popup. -if (store === localStore) { +if (!window.opener) { store.dispatch(checkLogin()); pym.sendMessage('getConfig'); diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js index 129cd1c3c..9c0081470 100644 --- a/client/coral-framework/actions/asset.js +++ b/client/coral-framework/actions/asset.js @@ -1,5 +1,5 @@ import * as actions from '../constants/asset'; -import coralApi from '../helpers/response'; +import coralApi from '../helpers/request'; import {addNotification} from '../actions/notification'; import I18n from 'coral-framework/modules/i18n/i18n'; diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 6b71fa9dd..f714f1fac 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -1,14 +1,14 @@ -import {pym} from 'coral-framework'; -import * as Storage from '../helpers/storage'; -import * as actions from '../constants/auth'; -import coralApi, {base} from '../helpers/response'; import jwtDecode from 'jwt-decode'; +import {pym} from 'coral-framework'; +import bowser from 'bowser'; +import * as actions from '../constants/auth'; +import * as Storage from '../helpers/storage'; +import coralApi, {base} from '../helpers/request'; const lang = new I18n(translations); import translations from './../translations'; import I18n from '../../coral-framework/modules/i18n/i18n'; -// Dialog Actions export const showSignInDialog = () => (dispatch) => { const signInPopUp = window.open( '/embed/stream/login', @@ -121,26 +121,31 @@ export const handleAuthToken = (token) => (dispatch) => { // SIGN IN //============================================================================== -export const fetchSignIn = (formData) => (dispatch) => { - dispatch(signInRequest()); - return coralApi('/auth/local', {method: 'POST', body: formData}) - .then(({token}) => { - dispatch(handleAuthToken(token)); - dispatch(hideSignInDialog()); - }) - .catch((error) => { - if (error.metadata) { +export const fetchSignIn = (formData) => { + return (dispatch) => { + dispatch(signInRequest()); - // the user might not have a valid email. prompt the user user re-request the confirmation email - dispatch( - signInFailure(lang.t('error.emailNotVerified', error.metadata)) - ); - } else { + return coralApi('/auth/local', {method: 'POST', body: formData}) + .then(({token}) => { + if (!bowser.safari && !bowser.ios) { + dispatch(handleAuthToken(token)); + } + dispatch(hideSignInDialog()); + }) + .catch((error) => { + if (error.metadata) { - // invalid credentials - dispatch(signInFailure(lang.t('error.emailPasswordError'))); - } - }); + // the user might not have a valid email. prompt the user user re-request the confirmation email + dispatch( + signInFailure(lang.t('error.emailNotVerified', error.metadata)) + ); + } else { + + // invalid credentials + dispatch(signInFailure(lang.t('error.emailPasswordError'))); + } + }); + }; }; //============================================================================== @@ -187,7 +192,7 @@ export const fetchSignUpFacebook = () => (dispatch) => { ); }; -export const facebookCallback = (err, data) => (dispatch, getState) => { +export const facebookCallback = (err, data) => (dispatch) => { if (err) { dispatch(signInFacebookFailure(err)); return; @@ -196,10 +201,6 @@ export const facebookCallback = (err, data) => (dispatch, getState) => { dispatch(handleAuthToken(data.token)); dispatch(signInFacebookSuccess(data.user)); dispatch(hideSignInDialog()); - const {user: {canEditName, status}} = getState().auth.toJS(); - if (canEditName && status !== 'BANNED') { - dispatch(showCreateUsernameDialog()); - } } catch (err) { dispatch(signInFacebookFailure(err)); return; @@ -269,7 +270,9 @@ export const fetchForgotPassword = (email) => (dispatch) => { export const logout = () => (dispatch) => { return coralApi('/auth', {method: 'DELETE'}).then(() => { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } dispatch({type: actions.LOGOUT}); }); }; @@ -292,11 +295,18 @@ export const checkLogin = () => (dispatch) => { coralApi('/auth') .then((result) => { if (!result.user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } throw new Error('Not logged in'); } dispatch(checkLoginSuccess(result.user)); + + // Display create username dialog if necessary. + if (result.user.canEditName && result.user.status !== 'BANNED') { + dispatch(showCreateUsernameDialog()); + } }) .catch((error) => { console.error(error); diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index 910fe0865..95d4116a6 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -1,5 +1,5 @@ import {addNotification} from '../actions/notification'; -import coralApi from '../helpers/response'; +import coralApi from '../helpers/request'; import * as actions from '../constants/auth'; import I18n from 'coral-framework/modules/i18n/i18n'; diff --git a/client/coral-framework/helpers/response.js b/client/coral-framework/helpers/request.js similarity index 75% rename from client/coral-framework/helpers/response.js rename to client/coral-framework/helpers/request.js index 794a73e30..0e15003e1 100644 --- a/client/coral-framework/helpers/response.js +++ b/client/coral-framework/helpers/request.js @@ -1,22 +1,26 @@ +import bowser from 'bowser'; import * as Storage from './storage'; +import merge from 'lodash/merge'; const buildOptions = (inputOptions = {}) => { const defaultOptions = { method: 'GET', headers: { Accept: 'application/json', - Authorization: `Bearer ${Storage.getItem('token')}`, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, credentials: 'same-origin' }; - let options = Object.assign({}, defaultOptions, inputOptions); - options.headers = Object.assign( - {}, - defaultOptions.headers, - inputOptions.headers - ); + let options = merge({}, defaultOptions, inputOptions); + + if (!bowser.safari && !bowser.ios) { + let authorization = Storage.getItem('token'); + + if (authorization) { + options.headers.Authorization = `Bearer ${authorization}`; + } + } if (options.method.toLowerCase() !== 'get') { options.body = JSON.stringify(options.body); diff --git a/client/coral-framework/services/transport.js b/client/coral-framework/services/transport.js index e421ca3da..0c430a021 100644 --- a/client/coral-framework/services/transport.js +++ b/client/coral-framework/services/transport.js @@ -1,5 +1,6 @@ import {createNetworkInterface} from 'apollo-client'; import * as Storage from '../helpers/storage'; +import bowser from 'bowser'; //============================================================================== // NETWORK INTERFACE @@ -21,7 +22,11 @@ networkInterface.use([{ if (!req.options.headers) { req.options.headers = {}; // Create the header object if needed. } - req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`; + + if (!bowser.safari && !bowser.ios) { + req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`; + } + next(); } }]); diff --git a/package.json b/package.json index f40ea4e1e..152871fae 100644 --- a/package.json +++ b/package.json @@ -54,10 +54,13 @@ "app-module-path": "^2.2.0", "bcrypt": "^1.0.2", "body-parser": "^1.17.1", + "bowser": "^1.7.0", "cli-table": "^0.3.1", "colors": "^1.1.2", "commander": "^2.9.0", + "compression": "^1.6.2", "connect-redis": "^3.1.0", + "cookie-parser": "^1.4.3", "cross-spawn": "^5.1.0", "csurf": "^1.9.0", "dataloader": "^1.3.0", diff --git a/plugins/coral-plugin-auth/client/components/ChangeUsername.js b/plugins/coral-plugin-auth/client/components/ChangeUsername.js index 9b0cbb1a6..ee02b2e17 100644 --- a/plugins/coral-plugin-auth/client/components/ChangeUsername.js +++ b/plugins/coral-plugin-auth/client/components/ChangeUsername.js @@ -23,17 +23,27 @@ class ChangeUsernameContainer extends React.Component { this.state = { formData: { - username: props.user.username + username: (props.auth.user && props.auth.user.username) || '' }, errors: {}, showErrors: false }; } - handleChange = e => { + componentWillReceiveProps(next) { + if (!this.props.auth.showCreateUsernameDialog && next.auth.showCreateUsernameDialog) { + this.setState({ + formData: { + username: (this.props.auth.user && this.props.auth.user.username) || '', + }, + }); + } + } + + handleChange = (e) => { const {name, value} = e.target; this.setState( - state => ({ + (state) => ({ ...state, formData: { ...state.formData, @@ -47,7 +57,7 @@ class ChangeUsernameContainer extends React.Component { }; addError = (name, error) => { - return this.setState(state => ({ + return this.setState((state) => ({ errors: { ...state.errors, [name]: error @@ -65,26 +75,26 @@ class ChangeUsernameContainer extends React.Component { } else { const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line // Removes Error - this.setState(state => ({...state, errors})); + this.setState((state) => ({...state, errors})); } }; isCompleted = () => { const {formData} = this.state; - return !Object.keys(formData).filter(prop => !formData[prop].length).length; + return !Object.keys(formData).filter((prop) => !formData[prop].length).length; }; displayErrors = (show = true) => { this.setState({showErrors: show}); }; - handleSubmitUsername = e => { + handleSubmitUsername = (e) => { e.preventDefault(); const {errors} = this.state; const {validForm, invalidForm} = this.props; this.displayErrors(); if (this.isCompleted() && !Object.keys(errors).length) { - this.props.createUsername(this.props.user.id, this.state.formData); + this.props.createUsername(this.props.auth.user.id, this.state.formData); validForm(); } else { invalidForm(lang.t('createdisplay.checkTheForm')); @@ -100,7 +110,7 @@ class ChangeUsernameContainer extends React.Component { return (
({auth, user}); +const mapStateToProps = ({auth}) => ({ + auth: auth.toJS() +}); -const mapDispatchToProps = dispatch => +const mapDispatchToProps = (dispatch) => bindActionCreators( { createUsername, diff --git a/plugins/coral-plugin-auth/client/components/FakeComment.js b/plugins/coral-plugin-auth/client/components/FakeComment.js index 01cc6863e..349e7d0a4 100644 --- a/plugins/coral-plugin-auth/client/components/FakeComment.js +++ b/plugins/coral-plugin-auth/client/components/FakeComment.js @@ -69,4 +69,5 @@ export const FakeComment = ({username, created_at, body}) => (
-); \ No newline at end of file +); + diff --git a/plugins/coral-plugin-auth/client/components/ForgotContent.js b/plugins/coral-plugin-auth/client/components/ForgotContent.js index 2099330bc..8bd6b27a6 100644 --- a/plugins/coral-plugin-auth/client/components/ForgotContent.js +++ b/plugins/coral-plugin-auth/client/components/ForgotContent.js @@ -7,7 +7,7 @@ import I18n from 'coral-framework/modules/i18n/i18n'; const lang = new I18n(translations); class ForgotContent extends React.Component { - handleSubmit = e => { + handleSubmit = (e) => { e.preventDefault(); this.props.fetchForgotPassword(this.emailInput.value); }; @@ -25,7 +25,7 @@ class ForgotContent extends React.Component {
(this.emailInput = input)} + ref={(input) => (this.emailInput = input)} type="text" style={{fontSize: 16}} id="email" diff --git a/plugins/coral-plugin-auth/client/components/SignInButton.js b/plugins/coral-plugin-auth/client/components/SignInButton.js index b54cbfedd..881233c83 100644 --- a/plugins/coral-plugin-auth/client/components/SignInButton.js +++ b/plugins/coral-plugin-auth/client/components/SignInButton.js @@ -18,7 +18,7 @@ const mapStateToProps = ({auth}) => ({ loggedIn: auth.toJS().loggedIn }); -const mapDispatchToProps = dispatch => +const mapDispatchToProps = (dispatch) => bindActionCreators({showSignInDialog}, dispatch); export default connect(mapStateToProps, mapDispatchToProps)(SignInButton); diff --git a/plugins/coral-plugin-auth/client/components/SignInContainer.js b/plugins/coral-plugin-auth/client/components/SignInContainer.js index cbff000ee..523f343ed 100644 --- a/plugins/coral-plugin-auth/client/components/SignInContainer.js +++ b/plugins/coral-plugin-auth/client/components/SignInContainer.js @@ -61,7 +61,8 @@ class SignInContainer extends React.Component { window.removeEventListener('storage', this.handleAuth); } - handleAuth = e => { + handleAuth = (e) => { + // Listening to FB changes // FB localStorage key is 'auth' const authCallback = this.props.facebookCallback; @@ -72,10 +73,10 @@ class SignInContainer extends React.Component { } }; - handleChange = e => { + handleChange = (e) => { const {name, value} = e.target; this.setState( - state => ({ + (state) => ({ ...state, formData: { ...state.formData, @@ -88,12 +89,12 @@ class SignInContainer extends React.Component { ); }; - handleChangeEmail = e => { + handleChangeEmail = (e) => { const {value} = e.target; this.setState({emailToBeResent: value}); }; - handleResendVerification = e => { + handleResendVerification = (e) => { e.preventDefault(); this.props .requestConfirmEmail( @@ -102,6 +103,7 @@ class SignInContainer extends React.Component { ) .then(() => { setTimeout(() => { + // allow success UI to be shown for a second, and then close the modal this.props.hideSignInDialog(); }, 2500); @@ -109,7 +111,7 @@ class SignInContainer extends React.Component { }; addError = (name, error) => { - return this.setState(state => ({ + return this.setState((state) => ({ errors: { ...state.errors, [name]: error @@ -133,20 +135,20 @@ class SignInContainer extends React.Component { } else { const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line // Removes Error - this.setState(state => ({...state, errors})); + this.setState((state) => ({...state, errors})); } }; isCompleted = () => { const {formData} = this.state; - return !Object.keys(formData).filter(prop => !formData[prop].length).length; + return !Object.keys(formData).filter((prop) => !formData[prop].length).length; }; displayErrors = (show = true) => { this.setState({showErrors: show}); }; - handleSignUp = e => { + handleSignUp = (e) => { e.preventDefault(); const {errors} = this.state; const {fetchSignUp, validForm, invalidForm} = this.props; @@ -159,7 +161,7 @@ class SignInContainer extends React.Component { } }; - handleSignIn = e => { + handleSignIn = (e) => { e.preventDefault(); this.props.fetchSignIn(this.state.formData); }; @@ -183,17 +185,16 @@ class SignInContainer extends React.Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ auth: state.auth.toJS() }); -const mapDispatchToProps = dispatch => +const mapDispatchToProps = (dispatch) => bindActionCreators( { checkLogin, facebookCallback, fetchSignUp, - fetchSignUp, fetchSignIn, fetchSignInFacebook, fetchSignUpFacebook, diff --git a/plugins/coral-plugin-auth/client/components/SignUpContent.js b/plugins/coral-plugin-auth/client/components/SignUpContent.js index 5f2dd36ea..2c0cf1f31 100644 --- a/plugins/coral-plugin-auth/client/components/SignUpContent.js +++ b/plugins/coral-plugin-auth/client/components/SignUpContent.js @@ -1,5 +1,5 @@ import styles from './styles.css'; -import React, {PropTypes} from 'react'; +import React from 'react'; import translations from '../translations'; import I18n from 'coral-framework/modules/i18n/i18n'; import {Button, TextField, Spinner, Success, Alert} from 'coral-ui'; @@ -7,12 +7,17 @@ import {Button, TextField, Spinner, Success, Alert} from 'coral-ui'; const lang = new I18n(translations); class SignUpContent extends React.Component { - constructor() { - super(); - this.state = { - successfulSignup: false - }; + componentWillReceiveProps(next) { + if ( + !this.props.emailVerificationEnabled && + !this.props.auth.successSignUp && + next.auth.successSignUp + ) { + setTimeout(() => { + this.props.changeView('SIGNIN'); + }, 2000); + } } render() { @@ -28,19 +33,6 @@ class SignUpContent extends React.Component { fetchSignUpFacebook } = this.props; - const beforeSignup = !auth.isLoading && !auth.successSignUp; - const successfulSignup = !auth.isLoading && auth.successSignUp; - - // the first time we render a successfulSignup, trigger a timer - if (this.successfulSignup ^ successfulSignup && !emailVerificationEnabled) { - setTimeout(() => { - changeView('SIGNIN'); - }, 1000); - this.setState({ - successfulSignup: true - }); - } - return (
@@ -50,7 +42,7 @@ class SignUpContent extends React.Component {
{auth.error && {auth.error}} - {beforeSignup && + {!auth.successSignUp &&
} - {successfulSignup && + {auth.successSignUp &&
{emailVerificationEnabled && diff --git a/plugins/coral-plugin-auth/client/components/UserBox.js b/plugins/coral-plugin-auth/client/components/UserBox.js index a4a0e1c99..cfd0fc9d2 100644 --- a/plugins/coral-plugin-auth/client/components/UserBox.js +++ b/plugins/coral-plugin-auth/client/components/UserBox.js @@ -4,7 +4,7 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import translations from '../translations'; import I18n from 'coral-framework/modules/i18n/i18n'; -import {showSignInDialog, logout} from 'coral-framework/actions/auth'; +import {logout} from 'coral-framework/actions/auth'; const lang = new I18n(translations); const UserBox = ({loggedIn, user, logout, onShowProfile}) => ( @@ -23,12 +23,12 @@ const UserBox = ({loggedIn, user, logout, onShowProfile}) => (
); -const mapStateToProps = ({auth, user}) => ({ +const mapStateToProps = ({auth}) => ({ loggedIn: auth.toJS().loggedIn, - user: user.toJS() + user: auth.toJS().user }); -const mapDispatchToProps = dispatch => +const mapDispatchToProps = (dispatch) => bindActionCreators({logout}, dispatch); export default connect(mapStateToProps, mapDispatchToProps)(UserBox); diff --git a/plugins/coral-plugin-auth/client/index.js b/plugins/coral-plugin-auth/client/index.js index 41fa3adbe..c41c097bd 100644 --- a/plugins/coral-plugin-auth/client/index.js +++ b/plugins/coral-plugin-auth/client/index.js @@ -8,4 +8,5 @@ export default { stream: [UserBox, SignInButton, ChangeUserNameContainer], login: [SignInContainer] } -}; \ No newline at end of file +}; + diff --git a/plugins/coral-plugin-auth/index.js b/plugins/coral-plugin-auth/index.js index a09954537..85dfb349b 100644 --- a/plugins/coral-plugin-auth/index.js +++ b/plugins/coral-plugin-auth/index.js @@ -1 +1,2 @@ -module.exports = {}; \ No newline at end of file +module.exports = {}; + diff --git a/plugins/coral-plugin-like/client/components/LikeButton.js b/plugins/coral-plugin-like/client/components/LikeButton.js index b91ffd42a..0a88d35a4 100644 --- a/plugins/coral-plugin-like/client/components/LikeButton.js +++ b/plugins/coral-plugin-like/client/components/LikeButton.js @@ -1,19 +1,18 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; import styles from './style.css'; -import Icon from './Icon'; -import { I18n } from 'coral-framework'; +import {I18n} from 'coral-framework'; import cn from 'classnames'; import translations from '../translations.json'; -import { getMyActionSummary, getTotalActionCount } from 'coral-framework/utils'; +import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils'; const lang = new I18n(translations); const name = 'coral-plugin-like'; class LikeButton extends Component { handleClick = () => { - const { postLike, showSignInDialog, deleteAction } = this.props; - const { root: { me }, comment } = this.props; + const {postLike, showSignInDialog, deleteAction} = this.props; + const {root: {me}, comment} = this.props; const myLikeActionSummary = getMyActionSummary( 'LikeActionSummary', @@ -42,7 +41,7 @@ class LikeButton extends Component { }; render() { - const { comment } = this.props; + const {comment} = this.props; if (!comment) { return null; @@ -56,7 +55,7 @@ class LikeButton extends Component {
- ) + ); } } - const mapStateToProps = ({commentBox}) => ({commentBox}); -const mapDispatchToProps = dispatch => +const mapDispatchToProps = (dispatch) => bindActionCreators({addTag, removeTag}, dispatch); export default connect(mapStateToProps, mapDispatchToProps)(OffTopicCheckbox); diff --git a/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js b/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js index 73e2372ed..0bd0fb1c2 100644 --- a/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js +++ b/plugins/coral-plugin-offtopic/client/components/OffTopicTag.js @@ -2,8 +2,8 @@ import React from 'react'; import styles from './styles.css'; const isOffTopic = (tags) => { - return !!tags.filter(tag => tag.name === 'OFF_TOPIC').length -} + return !!tags.filter((tag) => tag.name === 'OFF_TOPIC').length; +}; export default (props) => ( diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 6f610e2b0..392b04e9b 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -22,9 +22,10 @@ router.get('/', (req, res, next) => { /** * This blacklists the token used to authenticate. */ + router.delete('/', HandleLogout); -//============================================================================== +// ============================================================================= // PASSPORT ROUTES //============================================================================== diff --git a/services/passport.js b/services/passport.js index c64bb2334..4f5a8fb8d 100644 --- a/services/passport.js +++ b/services/passport.js @@ -9,6 +9,7 @@ const errors = require('../errors'); const uuid = require('uuid'); const debug = require('debug')('talk:passport'); const {createClient} = require('./redis'); +const bowser = require('bowser'); // Create a redis client to use for authentication. const client = createClient(); @@ -32,6 +33,17 @@ const GenerateToken = (user) => JWT.sign({}, JWT_SECRET, { audience: JWT_AUDIENCE }); +// SetTokenForSafari sends the token in a cookie for Safari clients. +const SetTokenForSafari = (req, res, token) => { + const browser = bowser._detect(req.headers['user-agent']); + if (browser.ios || browser.safari) { + res.cookie('authorization', token, { + httpOnly: true, + expires: new Date(Date.now() + 900000) + }); + } +}; + // HandleGenerateCredentials validates that an authentication scheme did indeed // return a user, if it did, then sign and return the user and token to be used // by the frontend to display and update the UI. @@ -47,6 +59,8 @@ const HandleGenerateCredentials = (req, res, next) => (err, user) => { // Generate the token to re-issue to the frontend. const token = GenerateToken(user); + SetTokenForSafari(req, res, token); + // Send back the details! res.json({user, token}); }; @@ -66,6 +80,8 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => { // Generate the token to re-issue to the frontend. const token = GenerateToken(user); + SetTokenForSafari(req, res, token); + // We logged in the user! Let's send back the user data. res.render('auth-callback', {auth: JSON.stringify({err: null, data: {user, token}})}); }; @@ -134,6 +150,7 @@ const HandleLogout = (req, res, next) => { return next(err); } + res.clearCookie('authorization'); res.status(204).end(); }); }; @@ -158,11 +175,24 @@ const CheckBlacklisted = (jwt) => new Promise((resolve, reject) => { const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; +let cookieExtractor = function(req) { + let token = null; + + if (req && req.cookies) { + token = req.cookies['authorization']; + } + + return token; +}; + // Extract the JWT from the 'Authorization' header with the 'Bearer' scheme. passport.use(new JwtStrategy({ // Prepare the extractor from the header. - jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'), + jwtFromRequest: ExtractJwt.fromExtractors([ + cookieExtractor, + ExtractJwt.fromAuthHeaderWithScheme('Bearer') + ]), // Use the secret passed in which is loaded from the environment. This can be // a certificate (loaded) or a HMAC key. diff --git a/yarn.lock b/yarn.lock index 728687248..3d01f098e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1262,6 +1262,10 @@ boom@2.x.x: dependencies: hoek "2.x.x" +bowser@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.0.tgz#169de4018711f994242bff9a8009e77a1f35e003" + brace-expansion@^1.0.0: version "1.1.7" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" @@ -1395,6 +1399,10 @@ 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" +bytes@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.3.0.tgz#d5b680a165b6201739acb611542aabc2d8ceb070" + bytes@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" @@ -1815,6 +1823,12 @@ component-emitter@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" +compressible@~2.0.8: + version "2.0.10" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" + dependencies: + mime-db ">= 1.27.0 < 2" + compression-webpack-plugin@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-0.4.0.tgz#811de04215f811ea6a12d4d8aed8457d758f13ac" @@ -1824,6 +1838,17 @@ compression-webpack-plugin@^0.4.0: optionalDependencies: node-zopfli "^2.0.0" +compression@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.6.2.tgz#cceb121ecc9d09c52d7ad0c3350ea93ddd402bc3" + dependencies: + accepts "~1.3.3" + bytes "2.3.0" + compressible "~2.0.8" + debug "~2.2.0" + on-headers "~1.0.1" + vary "~1.1.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1925,6 +1950,13 @@ convert-source-map@^1.1.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" +cookie-parser@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5" + dependencies: + cookie "0.3.1" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -5143,14 +5175,14 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" +"mime-db@>= 1.27.0 < 2", mime-db@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" + mime-db@~1.12.0: version "1.12.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.12.0.tgz#3d0c63180f458eb10d325aaa37d7c58ae312e9d7" -mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" - mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: version "2.1.15" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"