Auth refactor part 3

This commit is contained in:
Chi Vinh Le
2018-02-08 23:06:01 +01:00
parent d526a404ae
commit 37dd6ae044
26 changed files with 195 additions and 161 deletions
@@ -1,6 +1,6 @@
import jwtDecode from 'jwt-decode';
import bowser from 'bowser';
import * as actions from '../constants/auth';
import * as actions from '../constants/login';
import { notify } from 'coral-framework/actions/notification';
import t from 'coral-framework/services/i18n';
import get from 'lodash/get';
@@ -16,17 +16,10 @@ import cn from 'classnames';
export default class Embed extends React.Component {
changeTab = tab => {
// TODO: move data fetching to appropiate containers.
switch (tab) {
case 'profile':
this.props.data.refetch();
break;
}
this.props.setActiveTab(tab);
};
getTabs() {
const { user } = this.props.auth;
const tabs = [
<Tab
key="stream"
@@ -43,7 +36,7 @@ export default class Embed extends React.Component {
{t('framework.my_profile')}
</Tab>,
];
if (can(user, 'UPDATE_ASSET_CONFIG')) {
if (can(this.props.currentUser, 'UPDATE_ASSET_CONFIG')) {
tabs.push(
<Tab
key="config"
@@ -64,7 +57,8 @@ export default class Embed extends React.Component {
root,
root: { asset },
data,
auth: { showSignInDialog, signInDialogFocus },
showSignInDialog,
signInDialogFocus,
blurSignInDialog,
focusSignInDialog,
hideSignInDialog,
@@ -138,7 +132,9 @@ export default class Embed extends React.Component {
Embed.propTypes = {
setActiveTab: PropTypes.func,
auth: PropTypes.object,
currentUser: PropTypes.object,
showSignInDialog: PropTypes.bool,
signInDialogFocus: PropTypes.bool,
blurSignInDialog: PropTypes.func,
focusSignInDialog: PropTypes.func,
hideSignInDialog: PropTypes.func,
@@ -8,8 +8,15 @@ import branch from 'recompose/branch';
import renderComponent from 'recompose/renderComponent';
import { Spinner } from 'coral-ui';
import * as authActions from '../actions/auth';
import * as assetActions from '../actions/asset';
import {
logout,
checkLogin,
focusSignInDialog,
blurSignInDialog,
hideSignInDialog,
updateStatus,
} from '../actions/login';
import { fetchAssetSuccess } from '../actions/asset';
import {
getDefinitionName,
getSlotFragmentSpreads,
@@ -24,16 +31,6 @@ import t from 'coral-framework/services/i18n';
import PropTypes from 'prop-types';
import { setActiveTab } from '../actions/embed';
const {
logout,
checkLogin,
focusSignInDialog,
blurSignInDialog,
hideSignInDialog,
updateStatus,
} = authActions;
const { fetchAssetSuccess } = assetActions;
class EmbedContainer extends React.Component {
static contextTypes = {
pym: PropTypes.object,
@@ -42,7 +39,7 @@ class EmbedContainer extends React.Component {
subscriptions = [];
subscribeToUpdates(props = this.props) {
if (props.auth.loggedIn) {
if (props.currentUser) {
const newSubscriptions = [
{
document: USER_BANNED_SUBSCRIPTION,
@@ -80,7 +77,7 @@ class EmbedContainer extends React.Component {
props.data.subscribeToMore({
document: s.document,
variables: {
user_id: props.auth.user.id,
user_id: props.currentUser.id,
},
updateQuery: s.updateQuery,
})
@@ -107,7 +104,7 @@ class EmbedContainer extends React.Component {
}
componentWillReceiveProps(nextProps) {
if (this.props.auth.loggedIn !== nextProps.auth.loggedIn) {
if (this.props.currentUser !== nextProps.currentUser) {
// Refetch after login/logout.
this.props.data.refetch();
this.resubscribe(nextProps);
@@ -138,7 +135,22 @@ class EmbedContainer extends React.Component {
if (!this.props.root.asset) {
return <Spinner />;
}
return <Embed {...this.props} />;
return (
<Embed
setActiveTab={this.props.setActiveTab}
currentUser={this.props.currentUser}
blurSignInDialog={this.props.blurSignInDialog}
focusSignInDialog={this.props.focusSignInDialog}
hideSignInDialog={this.props.hideSignInDialog}
router={this.props.router}
commentId={this.props.commentId}
root={this.props.root}
activeTab={this.props.activeTab}
data={this.props.data}
showSignInDialog={this.props.showSignInDialog}
signInDialogFocus={this.props.signInDialogFocus}
/>
);
}
}
@@ -255,21 +267,45 @@ const EMBED_QUERY = gql`
`;
export const withEmbedQuery = withQuery(EMBED_QUERY, {
options: ({ auth, commentId, assetId, assetUrl, sortBy, sortOrder }) => ({
options: ({
currentUser,
commentId,
assetId,
assetUrl,
sortBy,
sortOrder,
}) => ({
variables: {
assetId,
assetUrl,
commentId,
hasComment: commentId !== '',
excludeIgnored: Boolean(auth && auth.user && auth.user.id),
excludeIgnored: Boolean(currentUser && currentUser.id),
sortBy,
sortOrder,
},
}),
});
EmbedContainer.propTypes = {
setActiveTab: PropTypes.func,
currentUser: PropTypes.object,
blurSignInDialog: PropTypes.func,
focusSignInDialog: PropTypes.func,
hideSignInDialog: PropTypes.func,
router: PropTypes.object,
commentId: PropTypes.string,
root: PropTypes.object,
activeTab: PropTypes.string,
data: PropTypes.object,
fetchAssetSuccess: PropTypes.func,
showSignInDialog: PropTypes.bool,
signInDialogFocus: PropTypes.bool,
};
const mapStateToProps = state => ({
auth: state.auth,
currentUser: state.auth.user,
checkedInitialLogin: state.auth.checkedInitialLogin,
commentId: state.stream.commentId,
assetId: state.stream.assetId,
assetUrl: state.stream.assetUrl,
@@ -277,6 +313,8 @@ const mapStateToProps = state => ({
config: state.config,
sortOrder: state.stream.sortOrder,
sortBy: state.stream.sortBy,
showSignInDialog: state.login.showSignInDialog,
signInDialogFocus: state.login.signInDialogFocus,
});
const mapDispatchToProps = dispatch =>
@@ -297,6 +335,6 @@ const mapDispatchToProps = dispatch =>
export default compose(
connect(mapStateToProps, mapDispatchToProps),
branch(props => !props.auth.checkedInitialLogin, renderComponent(Spinner)),
branch(props => !props.checkedInitialLogin, renderComponent(Spinner)),
withEmbedQuery
)(EmbedContainer);
-37
View File
@@ -1,55 +1,18 @@
import React from 'react';
import { render } from 'react-dom';
import {
checkLogin,
handleAuthToken,
logout,
} from 'coral-embed-stream/src/actions/auth';
import graphqlExtension from './graphql';
import { addExternalConfig } from 'coral-embed-stream/src/actions/config';
import { createContext } from 'coral-framework/services/bootstrap';
import AppRouter from './AppRouter';
import reducers from './reducers';
import TalkProvider from 'coral-framework/components/TalkProvider';
import pluginsConfig from 'pluginsConfig';
// TODO: move init code into `bootstrap` service after auth has been refactored.
function preInit({ store, pym, inIframe }) {
// TODO: This is popup specific code and needs to be refactored.
if (!inIframe) {
store.dispatch(addExternalConfig({}));
store.dispatch(checkLogin());
return;
}
pym.onMessage('login', token => {
if (token) {
store.dispatch(handleAuthToken(token));
}
store.dispatch(checkLogin());
});
pym.onMessage('logout', () => {
store.dispatch(logout());
});
return new Promise(resolve => {
pym.sendMessage('getConfig');
pym.onMessage('config', config => {
store.dispatch(addExternalConfig(JSON.parse(config)));
store.dispatch(checkLogin());
resolve();
});
});
}
async function main() {
const context = await createContext({
reducers,
graphqlExtension,
pluginsConfig,
preInit,
});
render(
<TalkProvider {...context}>
@@ -1,15 +1,13 @@
import auth from './auth';
import login from './login';
import asset from './asset';
import embed from './embed';
import config from './config';
import configure from './configure';
import stream from './stream';
export default {
auth,
login,
asset,
embed,
config,
configure,
stream,
};
@@ -1,4 +1,4 @@
import * as actions from '../constants/auth';
import * as actions from '../constants/login';
import pym from 'coral-framework/services/pym';
import merge from 'lodash/merge';
@@ -28,7 +28,7 @@ const purge = user => {
return userData;
};
export default function auth(state = initialState, action) {
export default function login(state = initialState, action) {
switch (action.type) {
case actions.FOCUS_SIGNIN_DIALOG:
return {
@@ -1,5 +1,5 @@
import * as actions from '../constants/stream';
import * as authActions from '../constants/auth';
import * as authActions from 'coral-framework/constants/auth';
function getQueryVariable(variable) {
let query = window.location.search.substring(1);
@@ -10,12 +10,7 @@ import NotLoggedIn from '../components/NotLoggedIn';
import { Spinner } from 'coral-ui';
import CommentHistory from '../components/CommentHistory';
// TODO: Auth logic needs refactoring.
import {
showSignInDialog,
checkLogin,
} from 'coral-embed-stream/src/actions/auth';
import { showSignInDialog } from 'coral-embed-stream/src/actions/login';
import { appendNewNodes } from 'plugin-api/beta/client/utils';
import update from 'immutability-helper';
import { getSlotFragmentSpreads } from 'coral-framework/utils';
@@ -24,7 +19,7 @@ import t from 'coral-framework/services/i18n';
class ProfileContainer extends Component {
componentWillReceiveProps(nextProps) {
if (!this.props.auth.loggedIn && nextProps.auth.loggedIn) {
if (!this.props.currentUser && nextProps.currentUser) {
// Refetch after login.
this.props.data.refetch();
}
@@ -55,13 +50,7 @@ class ProfileContainer extends Component {
};
render() {
const {
auth,
auth: { user: authUser },
showSignInDialog,
root,
data,
} = this.props;
const { currentUser, showSignInDialog, root, data } = this.props;
const { me } = this.props.root;
const loading = this.props.data.loading;
@@ -69,7 +58,7 @@ class ProfileContainer extends Component {
return <div>{this.props.data.error.message}</div>;
}
if (!auth.loggedIn) {
if (!currentUser) {
return <NotLoggedIn showSignInDialog={showSignInDialog} />;
}
@@ -77,7 +66,7 @@ class ProfileContainer extends Component {
return <Spinner />;
}
const localProfile = authUser.profiles.find(p => p.provider === 'local');
const localProfile = currentUser.profiles.find(p => p.provider === 'local');
const emailAddress = localProfile && localProfile.id;
return (
@@ -168,15 +157,20 @@ const withProfileQuery = withQuery(
${getSlotFragmentSpreads(slots, 'root')}
}
${CommentFragment}
`
`,
{
options: {
fetchPolicy: 'network-only',
},
}
);
const mapStateToProps = state => ({
auth: state.auth,
currentUser: state.auth.user,
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ showSignInDialog, checkLogin }, dispatch);
bindActionCreators({ showSignInDialog }, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
@@ -59,7 +59,7 @@ class Stream extends React.Component {
deleteAction,
showSignInDialog,
loadNewReplies,
auth: { user },
currentUser,
emit,
viewAllComments,
} = this.props;
@@ -111,7 +111,7 @@ class Stream extends React.Component {
disableReply={!open}
postComment={postComment}
asset={asset}
currentUser={user}
currentUser={currentUser}
highlighted={comment.id}
postFlag={postFlag}
postDontAgree={postDontAgree}
@@ -150,7 +150,7 @@ class Stream extends React.Component {
setActiveStreamTab,
loadNewReplies,
loadMoreComments,
auth: { user },
currentUser,
emit,
sortOrder,
sortBy,
@@ -200,7 +200,7 @@ class Stream extends React.Component {
notify={notify}
disableReply={asset.isClosed}
postComment={postComment}
currentUser={user}
currentUser={currentUser}
postFlag={postFlag}
postDontAgree={postDontAgree}
loadMore={loadMoreComments}
@@ -230,21 +230,23 @@ class Stream extends React.Component {
postComment,
notify,
updateItem,
auth: { loggedIn, user },
currentUser,
} = this.props;
const { keepCommentBox } = this.state;
const open = !asset.isClosed;
const banned = get(user, 'status.banned.status');
const suspensionUntil = get(user, 'status.suspension.until');
const rejectedUsername = get(user, 'status.username.status') === 'REJECTED';
const changedUsername = get(user, 'status.username.status') === 'CHANGED';
const banned = get(currentUser, 'status.banned.status');
const suspensionUntil = get(currentUser, 'status.suspension.until');
const rejectedUsername =
get(currentUser, 'status.username.status') === 'REJECTED';
const changedUsername =
get(currentUser, 'status.username.status') === 'CHANGED';
const temporarilySuspended =
user && suspensionUntil && new Date(suspensionUntil) > new Date();
currentUser && suspensionUntil && new Date(suspensionUntil) > new Date();
const showCommentBox =
loggedIn &&
currentUser &&
((!banned &&
!temporarilySuspended &&
!rejectedUsername &&
@@ -289,7 +291,8 @@ class Stream extends React.Component {
</RestrictedMessageBox>
)}
{changedUsername && <ChangedUsername />}
{!banned && rejectedUsername && <ChangeUsername user={user} />}
{!banned &&
rejectedUsername && <ChangeUsername user={currentUser} />}
{banned && <BannedAccount />}
{showCommentBox && (
<CommentBox
@@ -300,7 +303,7 @@ class Stream extends React.Component {
assetId={asset.id}
premod={asset.settings.moderation}
isReply={false}
currentUser={user}
currentUser={currentUser}
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
/>
@@ -312,10 +315,10 @@ class Stream extends React.Component {
<Slot fill="stream" queryData={slotQueryData} {...slotProps} />
{loggedIn && (
{currentUser && (
<ModerationLink
assetId={asset.id}
isAdmin={can(user, 'MODERATE_COMMENTS')}
isAdmin={can(currentUser, 'MODERATE_COMMENTS')}
/>
)}
@@ -342,7 +345,7 @@ Stream.propTypes = {
deleteAction: PropTypes.func,
showSignInDialog: PropTypes.func,
loadNewReplies: PropTypes.func,
auth: PropTypes.object,
currentUser: PropTypes.object,
emit: PropTypes.func,
sortOrder: PropTypes.string,
sortBy: PropTypes.string,
@@ -14,8 +14,8 @@ import {
withEditComment,
} from 'coral-framework/graphql/mutations';
import * as authActions from 'coral-embed-stream/src/actions/auth';
import * as notificationActions from 'coral-framework/actions/notification';
import { showSignInDialog, editName } from 'coral-embed-stream/src/actions/login';
import { notify } from 'coral-framework/actions/notification';
import {
setActiveReplyBox,
setActiveTab,
@@ -40,9 +40,6 @@ import {
} from '../../../graphql/utils';
import StreamError from '../components/StreamError';
const { showSignInDialog, editName } = authActions;
const { notify } = notificationActions;
class StreamContainer extends React.Component {
commentsAddedSubscription = null;
commentsEditedSubscription = null;
@@ -60,8 +57,8 @@ class StreamContainer extends React.Component {
// Ignore mutations from me.
// TODO: need way to detect mutations created by this client, and allow mutations from other clients.
if (
this.props.auth.user &&
commentEdited.user.id === this.props.auth.user.id
this.props.currentUser &&
commentEdited.user.id === this.props.currentUser.id
) {
return prev;
}
@@ -92,8 +89,8 @@ class StreamContainer extends React.Component {
// Ignore mutations from me.
// TODO: need way to detect mutations created by this client, and allow mutations from other clients.
if (
this.props.auth.user &&
commentAdded.user.id === this.props.auth.user.id
this.props.currentUser &&
commentAdded.user.id === this.props.currentUser.id
) {
return prev;
}
@@ -204,8 +201,8 @@ class StreamContainer extends React.Component {
}
}
userIsDegraged({ auth: { user } } = this.props) {
return !can(user, 'INTERACT_WITH_COMMUNITY');
userIsDegraged({ currentUser } = this.props) {
return !can(currentUser, 'INTERACT_WITH_COMMUNITY');
}
render() {
@@ -396,7 +393,7 @@ const fragments = {
};
const mapStateToProps = state => ({
auth: state.auth,
currentUser: state.auth.user,
activeReplyBox: state.stream.activeReplyBox,
commentId: state.stream.commentId,
assetId: state.stream.assetId,
+9 -6
View File
@@ -6,9 +6,6 @@ function cleanAuthData(localStorage) {
localStorage.removeItem('exp');
}
/**
* Check Login
*/
export const checkLogin = () => (
dispatch,
_,
@@ -54,9 +51,15 @@ const checkLoginSuccess = user => ({
user,
});
/**
* Login
*/
export const setAuthToken = token => (dispatch, _, { localStorage }) => {
if (localStorage) {
localStorage.setItem('exp', jwtDecode(token).exp);
localStorage.setItem('token', token);
}
dispatch(checkLogin());
};
export const handleSuccessfulLogin = (user, token) => (
dispatch,
_,
+6
View File
@@ -0,0 +1,6 @@
import { MERGE_CONFIG } from '../constants/config';
export const mergeConfig = config => ({
type: MERGE_CONFIG,
config,
});
-6
View File
@@ -1,6 +0,0 @@
import * as actions from '../constants/static';
export const setStaticConfiguration = config => ({
type: actions.SET_STATIC_CONFIGURATION,
config,
});
@@ -19,7 +19,7 @@ class Recaptcha extends React.Component {
// This should be fine because it's static and will never change.
// Prefer this to connect HOC because wie expose the instance method
// `reset`
return this.context.store.getState().static.TALK_RECAPTCHA_PUBLIC;
return this.context.store.getState().config.static.TALK_RECAPTCHA_PUBLIC;
}
render() {
@@ -0,0 +1,3 @@
const prefix = `TALK_FRAMEWORK`;
export const MERGE_CONFIG = `${prefix}_MERGE_CONFIG`;
@@ -1,3 +0,0 @@
const prefix = `TALK_FRAMEWORK`;
export const SET_STATIC_CONFIGURATION = `${prefix}_SET_STATIC_CONFIGURATION`;
+2 -2
View File
@@ -48,8 +48,8 @@ export default hoistStatics(WrappedComponent => {
}
const changeSet = { loading: false, error };
if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') {
changeSet.requireRecaptcha = !!this.context.store.getState().static
.TALK_RECAPTCHA_PUBLIC;
changeSet.requireRecaptcha = !!this.context.store.getState().config
.static.TALK_RECAPTCHA_PUBLIC;
}
this.setState(changeSet);
});
@@ -1,10 +1,10 @@
import * as actions from '../constants/static';
import { MERGE_CONFIG } from '../constants/config';
const initialState = {};
export default function auth(state = initialState, action) {
export default function config(state = initialState, action) {
switch (action.type) {
case actions.SET_STATIC_CONFIGURATION:
case MERGE_CONFIG:
return {
...state,
...action.config,
+3 -2
View File
@@ -1,7 +1,8 @@
import auth from './auth';
import staticConfiguration from './static';
import config from './config';
export default {
auth,
static: staticConfiguration,
login: auth,
config,
};
+49 -7
View File
@@ -23,8 +23,9 @@ import { createHistory } from 'coral-framework/services/history';
import { createIntrospection } from 'coral-framework/services/introspection';
import introspectionData from 'coral-framework/graphql/introspection.json';
import coreReducers from '../reducers';
import { checkLogin } from '../actions/auth';
import { setStaticConfiguration } from '../actions/static';
import { checkLogin as checkLoginAction } from '../actions/auth';
import { mergeConfig } from '../actions/config';
import { setAuthToken, logout } from '../actions/auth';
/**
* getAuthToken returns the active auth token or null
@@ -54,6 +55,19 @@ function areWeInIframe() {
}
}
function initExternalConfig({ store, pym, inIframe }) {
if (!inIframe) {
return;
}
return new Promise(resolve => {
pym.sendMessage('getConfig');
pym.onMessage('config', config => {
store.dispatch(mergeConfig(JSON.parse(config)));
resolve();
});
});
}
/**
* createContext setups and returns Talk dependencies that should be
* passed to `TalkProvider`.
@@ -73,6 +87,8 @@ export async function createContext({
notification,
preInit,
init = noop,
checkLogin = true,
addExternalConfig = true,
} = {}) {
const inIframe = areWeInIframe();
const eventEmitter = new EventEmitter({ wildcard: true });
@@ -166,7 +182,6 @@ export async function createContext({
// Create our redux store.
const finalReducers = {
...coreReducers,
authCore: coreReducers.auth,
...reducers,
...plugins.getReducers(),
};
@@ -186,12 +201,39 @@ export async function createContext({
[client.middleware(), apolloErrorReporter, createReduxEmitter(eventEmitter)]
);
store.dispatch(setStaticConfiguration(staticConfig));
store.dispatch(checkLogin());
if (inIframe) {
pym.onMessage('login', token => {
if (token) {
store.dispatch(setAuthToken(token));
}
});
pym.onMessage('logout', () => {
store.dispatch(logout());
});
}
const preInitList = [];
store.dispatch(
mergeConfig({
static: staticConfig,
})
);
if (preInit) {
preInitList.push(preInit(context));
}
if (addExternalConfig) {
preInitList.push(initExternalConfig(context));
}
// Run pre initialization.
if (preInit) {
await preInit(context);
await Promise.all(preInitList);
if (checkLogin) {
store.dispatch(checkLoginAction());
}
// Run initialization.
+1 -2
View File
@@ -15,8 +15,7 @@ import * as PropTypes from 'prop-types';
import { getDefinitionName } from '../utils';
import { t, can } from 'plugin-api/beta/client/services';
// TODO: Auth logic needs refactoring.
import { showSignInDialog } from 'coral-embed-stream/src/actions/auth';
import { showSignInDialog } from 'coral-embed-stream/src/actions/login';
/*
* Disable false-positive warning below, as it doesn't work well with how we currently
@@ -17,7 +17,7 @@ import {
invalidForm,
validForm,
updateUsername,
} from 'coral-embed-stream/src/actions/auth';
} from 'coral-embed-stream/src/actions/login';
class ChangeUsernameContainer extends React.Component {
constructor(props) {
@@ -2,12 +2,12 @@ import React from 'react';
import { Button } from 'plugin-api/beta/client/components/ui';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { showSignInDialog } from 'coral-embed-stream/src/actions/auth';
import { showSignInDialog } from 'coral-embed-stream/src/actions/login';
import t from 'coral-framework/services/i18n';
const SignInButton = ({ loggedIn, showSignInDialog }) => (
const SignInButton = ({ currentUser, showSignInDialog }) => (
<div className="talk-stream-auth-sign-in-button">
{!loggedIn ? (
{!currentUser ? (
<Button id="coralSignInButton" onClick={showSignInDialog} full>
{t('sign_in.sign_in_to_comment')}
</Button>
@@ -16,7 +16,7 @@ const SignInButton = ({ loggedIn, showSignInDialog }) => (
);
const mapStateToProps = ({ auth }) => ({
loggedIn: auth.loggedIn,
currentUser: auth.user,
});
const mapDispatchToProps = dispatch =>
@@ -19,7 +19,7 @@ import {
facebookCallback,
invalidForm,
validForm,
} from 'coral-embed-stream/src/actions/auth';
} from 'coral-embed-stream/src/actions/login';
class SignInContainer extends React.Component {
constructor(props) {
@@ -3,7 +3,7 @@ import styles from './styles.css';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import t from 'coral-framework/services/i18n';
import { logout } from 'coral-embed-stream/src/actions/auth';
import { logout } from 'coral-embed-stream/src/actions/login';
const UserBox = ({ loggedIn, user, logout, onShowProfile }) => (
<div>