@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
152 lines (150 loc) • 5.38 kB
JavaScript
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ofType } from 'redux-observable';
import {
login,
loginComplete,
loginFailed,
logout,
refreshAuthToken,
refreshAuthTokenComplete,
sharedWorkerError,
sharedWorkerTimeout,
sharedWorkerToken,
sharedWorkerUnauthenticated
} from '../actions/auth';
import { catchError, delay, ignoreElements, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import * as auth from '../../services/auth';
import { catchAjaxError } from '../../utils/ajax';
import { getRequestForgeryToken, getXSRFToken, setJwt, setRequestForgeryToken } from '../../utils/auth';
import { messageSharedWorker, openSiteSocket, showSystemNotification } from '../actions/system';
import { sessionTimeout } from '../actions/user';
const epics = [
// region login
(action$) =>
action$.pipe(
ofType(login.type),
switchMap((action) =>
auth.login(action.payload).pipe(
map(() => loginComplete()),
catchAjaxError(loginFailed)
)
)
),
// endregion
// region loginComplete
(action$, state$) =>
action$.pipe(
ofType(loginComplete.type),
withLatestFrom(state$),
switchMap(([, state]) => {
const hasActiveSite = Boolean(state.sites.active);
return [
messageSharedWorker(refreshAuthToken()),
// This action here assumes this epic is ran only when login from session timeout, not on initial login too
hasActiveSite && messageSharedWorker(openSiteSocket({ site: state.sites.active, xsrfToken: getXSRFToken() }))
].filter(Boolean);
})
),
// endregion
// region logout
(action$, state$) =>
action$.pipe(
ofType(logout.type),
withLatestFrom(state$),
// Spring requires regular post for logout....
// tap(([, state]) => (window.location.href = `${state.env.authoringBase}/logout`)),
tap(([, state]) => {
const tokenField = document.createElement('input');
tokenField.type = 'hidden';
tokenField.name = state.env.xsrfArgument;
tokenField.value = getRequestForgeryToken();
const form = document.createElement('form');
form.method = 'post';
form.action = state.env.logoutUrl;
form.appendChild(tokenField);
document.body.appendChild(form);
// The timeout purpose is to avoid immediate submission stopping
// the logout message from getting to the Service Worker
setTimeout(() => form.submit());
}),
map(() => messageSharedWorker(logout()))
),
// endregion
// region sessionTimeout
(action$) =>
action$.pipe(
ofType(sessionTimeout.type),
tap(() => setRequestForgeryToken()),
map(() => messageSharedWorker(sharedWorkerTimeout()))
),
// endregion
// region sharedWorkerToken
(action$) =>
action$.pipe(
ofType(sharedWorkerToken.type),
map(({ payload }) => refreshAuthTokenComplete(payload))
),
// endregion
// region sharedWorkerUnauthenticated
(action$) =>
action$.pipe(
ofType(sharedWorkerUnauthenticated.type),
delay(300),
// We need the new set of auth cookies to be set
// on this window so that if login attempted from the re-login dialog,
// it won't fail due to outdated XSRF/auth cookies.
switchMap(() => auth.fetchAuthenticationType().pipe(catchError(() => []))),
tap(() => setRequestForgeryToken()),
ignoreElements()
),
// endregion
// region sharedWorkerError
(action$) =>
action$.pipe(
ofType(sharedWorkerError.type),
map(({ payload }) =>
showSystemNotification({
message: `An error occurred communicating with the token refresh service. Error code ${payload.status}: ${payload.message}`
})
)
),
// endregion
// region refreshAuthTokenComplete
(action$) =>
action$.pipe(
ofType(refreshAuthTokenComplete.type),
tap(({ payload }) => setJwt(payload.token)),
ignoreElements()
)
// endregion
];
export default epics;