Source: server/actions/logoutResponseActions.js

/**
 * All functions in this module are action creators and the return value should be passed to the
 * redux store dispatch() function.
 *
 * IMPORTANT: This file contains imports that should only be bundled when doing a 'node' build.
 * This action file should never be imported in the web bundle
 * @module
 */
import debugLib from 'debug';
import createAction from 'redux-actions/lib/createAction';
import getMasterKeyById from '../util/getMasterKeyById';
import unprotectAspNetData from '../util/unprotectAspNetData';
import RedirectError from '../../app/util/RedirectError';
import ContainerName from '../../app/data/enum/ContainerName';
import msUrlTokenDecode from '../util/msUrlTokenDecode';
import { cleanUserIdentity } from '../../app/actions/resources/accountActions';
import isLocalOrDevelopment from './../util/isLocalOrDevelopment';
import {
  ASPNET_IDENTITY_APP_COOKIE,
  ASPNET_MIGRATION_APP_COOKIE,
  IDSRV_SESSION_COOKIE,
  ASPNET_DOCS_COOKIE,
} from '../util/AuthenticationHelper/constants';

const debug = debugLib('SlimmingWorld:logoutResponseActions');

const SET_LOGOUT_RESPONSE = 'logoutResponseActions/SET_LOGOUT_RESPONSE';

/**
 * Sets the logout response as decoded from the query string
 * @function setLogoutResponse
 * @param query The parsed query string. This will be decoded into an object
 */
export const setLogoutResponse = createAction(SET_LOGOUT_RESPONSE);

/**
 * Validate if decoded contains Data.ClientId if it's missing it should
 * redirects to logout confirmation.
 * if Data.Client exist it should clean redux state identity, cookies from the browser
 * and redirect to postLogoutRedirectUri
 */
const redirectLogoutValidation = (decoded, res, environmentConfig) => dispatch => {
  // if Data.ClientId exist redirects to post logout redirect uri
  if (decoded.Data.ClientId) {
    // clean user identity in redux state
    dispatch(cleanUserIdentity());

    // clean the cookies in the browser
    res.clearCookie(ASPNET_IDENTITY_APP_COOKIE, {});
    res.clearCookie(ASPNET_MIGRATION_APP_COOKIE, {});
    res.clearCookie(IDSRV_SESSION_COOKIE, {});
    res.clearCookie(ASPNET_DOCS_COOKIE, {});

    // redirect to postLogoutRedirectUri;
    // if it's local should prepend account host otherwise just redirect to postLogoutRedirectUri
    throw new RedirectError(
      isLocalOrDevelopment()
        ? `${environmentConfig.web.account.host}${decoded.Data.PostLogoutRedirectUri}`
        : decoded.Data.PostLogoutRedirectUri,
    );
  }
};

/**
 * We need to clear the migration cookie nd redirect to the public site
 *
 * @param res
 * @returns {Function}
 */
export const redirectMigrationLogout = (res, environmentConfig) => dispatch => {
  // clean user identity in redux state
  dispatch(cleanUserIdentity());

  // clean the cookies in the browser
  res.clearCookie(ASPNET_MIGRATION_APP_COOKIE, {});
  // throw new RedirectError(`${config.web.public.host}`);
  res.redirect(environmentConfig.web.public.host);
};

/**
 * Parse the logout response on the query parameters, if it is present.
 * If the logout response is found, it will be decoded and validated using
 * the signature in the query string. If a valid response is present, it will
 * dispatch the setLogoutResponse action to pass the response to the redux
 * state. This will be read from routeRequirements on the logout callback route.
 * @param query The query object as parsed by react-router
 * @param res
 * @param azureBlobConnectionString
 */
export const parseLogoutResponseQuery = (
  query,
  res,
  { azureBlobConnectionString, azureBlobContainerPrefix, ...environmentConfig },
) => async dispatch => {
  if (query.logoutId) {
    const PURPOSES = [
      'SlimmingWorld.Platform.Account.Web',
      'IdentityServer4.Stores.ProtectedDataMessageStore',
    ];

    let data;
    try {
      data = msUrlTokenDecode(query.logoutId);
    } catch (e) {
      debug('Could not parse logoutId response query as base64. Ignoring query.');
      return;
    }

    let decrypted;
    try {
      decrypted = await unprotectAspNetData(
        data,
        PURPOSES,
        getMasterKeyById,
        azureBlobConnectionString,
        `${azureBlobContainerPrefix}${ContainerName.ACCOUNT}`,
      );
    } catch (e) {
      debug(`Error during unprotectAspNetData in logoutResponseActions:\n${e.message || e}`);
      return;
    }

    const decoded = JSON.parse(decrypted.toString());
    dispatch(setLogoutResponse(decoded));
    dispatch(redirectLogoutValidation(decoded, res, environmentConfig));
  }
};