chore: Move frontend authorization to permission based system (#9709)

We previously relied on user roles to determine whether to render
specific routes in our frontend components. A permissions-based model is replacing this approach.


Follow up: #9695

Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
Sojan Jose
2024-07-03 15:13:16 -07:00
committed by GitHub
parent 5520bf68f3
commit cc4851b19d
37 changed files with 582 additions and 229 deletions

View File

@@ -20,7 +20,7 @@
:teams="teams"
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
:current-user="currentUser"
:is-on-chatwoot-cloud="isOnChatwootCloud"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
@@ -37,7 +37,8 @@ import alertMixin from 'shared/mixins/alertMixin';
import PrimarySidebar from './sidebarComponents/Primary.vue';
import SecondarySidebar from './sidebarComponents/Secondary.vue';
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
import router from '../../routes';
import router, { routesWithPermissions } from '../../routes';
import { hasPermissions } from '../../helper/permissionsHelper';
export default {
components: {
@@ -98,9 +99,13 @@ export default {
return getSidebarItems(this.accountId);
},
primaryMenuItems() {
const userPermissions = this.currentUser.permissions;
const menuItems = this.sideMenuConfig.primaryMenu;
return menuItems.filter(menuItem => {
const isAvailableForTheUser = menuItem.roles.includes(this.currentRole);
const isAvailableForTheUser = hasPermissions(
routesWithPermissions[menuItem.toStateName],
userPermissions
);
if (!isAvailableForTheUser) {
return false;

View File

@@ -9,7 +9,6 @@ const primaryMenuItems = accountId => [
featureFlag: FEATURE_FLAGS.INBOX_VIEW,
toState: frontendURL(`accounts/${accountId}/inbox-view`),
toStateName: 'inbox_view',
roles: ['administrator', 'agent'],
},
{
icon: 'chat',
@@ -17,7 +16,6 @@ const primaryMenuItems = accountId => [
label: 'CONVERSATIONS',
toState: frontendURL(`accounts/${accountId}/dashboard`),
toStateName: 'home',
roles: ['administrator', 'agent'],
},
{
icon: 'book-contacts',
@@ -26,7 +24,6 @@ const primaryMenuItems = accountId => [
featureFlag: FEATURE_FLAGS.CRM,
toState: frontendURL(`accounts/${accountId}/contacts`),
toStateName: 'contacts_dashboard',
roles: ['administrator', 'agent'],
},
{
icon: 'arrow-trending-lines',
@@ -34,8 +31,7 @@ const primaryMenuItems = accountId => [
label: 'REPORTS',
featureFlag: FEATURE_FLAGS.REPORTS,
toState: frontendURL(`accounts/${accountId}/reports`),
toStateName: 'settings_account_reports',
roles: ['administrator'],
toStateName: 'account_overview_reports',
},
{
icon: 'megaphone',
@@ -44,7 +40,6 @@ const primaryMenuItems = accountId => [
featureFlag: FEATURE_FLAGS.CAMPAIGNS,
toState: frontendURL(`accounts/${accountId}/campaigns`),
toStateName: 'ongoing_campaigns',
roles: ['administrator'],
},
{
icon: 'library',
@@ -54,7 +49,6 @@ const primaryMenuItems = accountId => [
alwaysVisibleOnChatwootInstances: true,
toState: frontendURL(`accounts/${accountId}/portals`),
toStateName: 'default_portal_articles',
roles: ['administrator'],
},
{
icon: 'settings',
@@ -62,7 +56,6 @@ const primaryMenuItems = accountId => [
label: 'SETTINGS',
toState: frontendURL(`accounts/${accountId}/settings`),
toStateName: 'settings_home',
roles: ['administrator', 'agent'],
},
];

View File

@@ -24,7 +24,6 @@ const settings = accountId => ({
'settings_inbox_list',
'settings_inbox_new',
'settings_inbox_show',
'settings_inbox',
'settings_inboxes_add_agents',
'settings_inboxes_page_channel',
'settings_integrations_dashboard_apps',
@@ -46,6 +45,9 @@ const settings = accountId => ({
icon: 'briefcase',
label: 'ACCOUNT_SETTINGS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/general`),
toStateName: 'general_settings_index',
},
@@ -53,6 +55,9 @@ const settings = accountId => ({
icon: 'people',
label: 'AGENTS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/agents/list`),
toStateName: 'agent_list',
featureFlag: FEATURE_FLAGS.AGENT_MANAGEMENT,
@@ -61,6 +66,9 @@ const settings = accountId => ({
icon: 'people-team',
label: 'TEAMS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/teams/list`),
toStateName: 'settings_teams_list',
featureFlag: FEATURE_FLAGS.TEAM_MANAGEMENT,
@@ -69,6 +77,9 @@ const settings = accountId => ({
icon: 'mail-inbox-all',
label: 'INBOXES',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`),
toStateName: 'settings_inbox_list',
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
@@ -77,6 +88,9 @@ const settings = accountId => ({
icon: 'tag',
label: 'LABELS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/labels/list`),
toStateName: 'labels_list',
featureFlag: FEATURE_FLAGS.LABELS,
@@ -85,6 +99,9 @@ const settings = accountId => ({
icon: 'code',
label: 'CUSTOM_ATTRIBUTES',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(
`accounts/${accountId}/settings/custom-attributes/list`
),
@@ -95,6 +112,9 @@ const settings = accountId => ({
icon: 'automation',
label: 'AUTOMATION',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
toStateName: 'automation_list',
featureFlag: FEATURE_FLAGS.AUTOMATIONS,
@@ -103,6 +123,9 @@ const settings = accountId => ({
icon: 'bot',
label: 'AGENT_BOTS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
globalConfigFlag: 'csmlEditorHost',
toState: frontendURL(`accounts/${accountId}/settings/agent-bots`),
toStateName: 'agent_bots',
@@ -112,6 +135,9 @@ const settings = accountId => ({
icon: 'flash-settings',
label: 'MACROS',
hasSubMenu: false,
meta: {
permissions: ['administrator', 'agent'],
},
toState: frontendURL(`accounts/${accountId}/settings/macros`),
toStateName: 'macros_wrapper',
featureFlag: FEATURE_FLAGS.MACROS,
@@ -120,6 +146,9 @@ const settings = accountId => ({
icon: 'chat-multiple',
label: 'CANNED_RESPONSES',
hasSubMenu: false,
meta: {
permissions: ['administrator', 'agent'],
},
toState: frontendURL(
`accounts/${accountId}/settings/canned-response/list`
),
@@ -130,6 +159,9 @@ const settings = accountId => ({
icon: 'flash-on',
label: 'INTEGRATIONS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
toStateName: 'settings_integrations',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
@@ -138,6 +170,9 @@ const settings = accountId => ({
icon: 'star-emphasis',
label: 'APPLICATIONS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/applications`),
toStateName: 'settings_applications',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
@@ -146,6 +181,9 @@ const settings = accountId => ({
icon: 'key',
label: 'AUDIT_LOGS',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/audit-log/list`),
toStateName: 'auditlogs_list',
isEnterpriseOnly: true,
@@ -156,6 +194,9 @@ const settings = accountId => ({
icon: 'document-list-clock',
label: 'SLA',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/sla/list`),
toStateName: 'sla_list',
isEnterpriseOnly: true,
@@ -166,6 +207,9 @@ const settings = accountId => ({
icon: 'credit-card-person',
label: 'BILLING',
hasSubMenu: false,
meta: {
permissions: ['administrator'],
},
toState: frontendURL(`accounts/${accountId}/settings/billing`),
toStateName: 'billing_settings_index',
showOnlyOnCloud: true,

View File

@@ -29,6 +29,8 @@ import SecondaryNavItem from './SecondaryNavItem.vue';
import AccountContext from './AccountContext.vue';
import { mapGetters } from 'vuex';
import { FEATURE_FLAGS } from '../../../featureFlags';
import { hasPermissions } from '../../../helper/permissionsHelper';
import { routesWithPermissions } from '../../../routes';
export default {
components: {
@@ -60,9 +62,9 @@ export default {
type: Object,
default: () => {},
},
currentRole: {
type: String,
default: '',
currentUser: {
type: Object,
default: () => {},
},
isOnChatwootCloud: {
type: Boolean,
@@ -80,16 +82,16 @@ export default {
return this.customViews.filter(view => view.filter_type === 'contact');
},
accessibleMenuItems() {
if (!this.currentRole) {
return [];
}
const menuItemsFilteredByRole = this.menuConfig.menuItems.filter(
menuItem =>
window.roleWiseRoutes[this.currentRole].indexOf(
menuItem.toStateName
) > -1
const menuItemsFilteredByPermissions = this.menuConfig.menuItems.filter(
menuItem => {
const { permissions: userPermissions = [] } = this.currentUser;
return hasPermissions(
routesWithPermissions[menuItem.toStateName],
userPermissions
);
}
);
return menuItemsFilteredByRole.filter(item => {
return menuItemsFilteredByPermissions.filter(item => {
if (item.showOnlyOnCloud) {
return this.isOnChatwootCloud;
}

View File

@@ -65,27 +65,29 @@
:show-child-count="showChildCount(child.count)"
:child-item-count="child.count"
/>
<router-link
v-if="showItem(menuItem)"
v-slot="{ href, navigate }"
:to="menuItem.toState"
custom
>
<li class="pl-1">
<a :href="href">
<woot-button
size="tiny"
variant="clear"
color-scheme="secondary"
icon="add"
:data-testid="menuItem.dataTestid"
@click="e => newLinkClick(e, navigate)"
>
{{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }}
</woot-button>
</a>
</li>
</router-link>
<Policy :permissions="['administrator']">
<router-link
v-if="menuItem.newLink"
v-slot="{ href, navigate }"
:to="menuItem.toState"
custom
>
<li class="pl-1">
<a :href="href">
<woot-button
size="tiny"
variant="clear"
color-scheme="secondary"
icon="add"
:data-testid="menuItem.dataTestid"
@click="e => newLinkClick(e, navigate)"
>
{{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }}
</woot-button>
</a>
</li>
</router-link>
</Policy>
</ul>
</li>
</template>
@@ -105,9 +107,10 @@ import {
isOnMentionsView,
isOnUnattendedView,
} from '../../../store/modules/conversations/helpers/actionHelpers';
import Policy from '../../policy.vue';
export default {
components: { SecondaryChildNavItem },
components: { SecondaryChildNavItem, Policy },
mixins: [adminMixin, configMixin],
props: {
menuItem: {

View File

@@ -0,0 +1,23 @@
<script setup>
import { useStoreGetters } from 'dashboard/composables/store';
import { computed } from 'vue';
import { hasPermissions } from '../helper/permissionsHelper';
const props = defineProps({
permissions: {
type: Array,
required: true,
},
});
const getters = useStoreGetters();
const user = getters.getCurrentUser.value;
const hasPermission = computed(() =>
hasPermissions(props.permissions, user.permissions)
);
</script>
<template>
<div v-if="hasPermission">
<slot />
</div>
</template>

View File

@@ -0,0 +1,34 @@
export const hasPermissions = (
requiredPermissions = [],
availablePermissions = []
) => {
return requiredPermissions.some(permission =>
availablePermissions.includes(permission)
);
};
const isPermissionsPresentInRoute = route =>
route.meta && route.meta.permissions;
export const buildPermissionsFromRouter = (routes = []) =>
routes.reduce((acc, route) => {
if (route.name) {
if (!isPermissionsPresentInRoute(route)) {
// eslint-disable-next-line
console.error(route);
throw new Error(
"The route doesn't have the required permissions defined"
);
}
acc[route.name] = route.meta.permissions;
}
if (route.children) {
acc = {
...acc,
...buildPermissionsFromRouter(route.children),
};
}
return acc;
}, {});

View File

@@ -1,19 +1,16 @@
import { hasPermissions } from './permissionsHelper';
// eslint-disable-next-line default-param-last
export const getCurrentAccount = ({ accounts } = {}, accountId) => {
return accounts.find(account => account.id === accountId);
};
// eslint-disable-next-line default-param-last
export const getUserRole = ({ accounts } = {}, accountId) => {
const currentAccount = getCurrentAccount({ accounts }, accountId) || {};
return currentAccount.role || null;
export const routeIsAccessibleFor = (route, userPermissions = []) => {
const { meta: { permissions: routePermissions = [] } = {} } = route;
return hasPermissions(routePermissions, userPermissions);
};
export const routeIsAccessibleFor = (route, role, roleWiseRoutes) => {
return roleWiseRoutes[role].includes(route);
};
const validateActiveAccountRoutes = (to, user, roleWiseRoutes) => {
const validateActiveAccountRoutes = (to, user) => {
// If the current account is active, then check for the route permissions
const accountDashboardURL = `accounts/${to.params.accountId}/dashboard`;
@@ -22,15 +19,13 @@ const validateActiveAccountRoutes = (to, user, roleWiseRoutes) => {
return accountDashboardURL;
}
const userRole = getUserRole(user, Number(to.params.accountId));
const isAccessible = routeIsAccessibleFor(to.name, userRole, roleWiseRoutes);
const isAccessible = routeIsAccessibleFor(to, user.permissions);
// If the route is not accessible for the user, return to dashboard screen
return isAccessible ? null : accountDashboardURL;
};
export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => {
export const validateLoggedInRoutes = (to, user) => {
const currentAccount = getCurrentAccount(user, Number(to.params.accountId));
// If current account is missing, either user does not have
// access to the account or the account is deleted, return to login screen
if (!currentAccount) {
@@ -40,7 +35,7 @@ export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => {
const isCurrentAccountActive = currentAccount.status === 'active';
if (isCurrentAccountActive) {
return validateActiveAccountRoutes(to, user, roleWiseRoutes);
return validateActiveAccountRoutes(to, user);
}
// If the current account is not active, then redirect the user to the suspended screen

View File

@@ -0,0 +1,84 @@
import {
buildPermissionsFromRouter,
hasPermissions,
} from '../permissionsHelper';
describe('hasPermissions', () => {
it('returns true if permission is present', () => {
expect(
hasPermissions(['contact_manage'], ['team_manage', 'contact_manage'])
).toBe(true);
});
it('returns true if permission is not present', () => {
expect(
hasPermissions(['contact_manage'], ['team_manage', 'user_manage'])
).toBe(false);
expect(hasPermissions()).toBe(false);
expect(hasPermissions([])).toBe(false);
});
});
describe('buildPermissionsFromRouter', () => {
it('returns a valid object when routes have permissions defined', () => {
expect(
buildPermissionsFromRouter([
{
path: 'agent',
name: 'agent_list',
meta: { permissions: ['agent_admin'] },
},
{
path: 'inbox',
children: [
{
path: '',
name: 'inbox_list',
meta: { permissions: ['inbox_admin'] },
},
],
},
{
path: 'conversations',
children: [
{
path: '',
children: [
{
path: 'attachments',
name: 'attachments_list',
meta: { permissions: ['conversation_admin'] },
},
],
},
],
},
])
).toEqual({
agent_list: ['agent_admin'],
inbox_list: ['inbox_admin'],
attachments_list: ['conversation_admin'],
});
});
it('throws an error if a named routed does not have permissions defined', () => {
expect(() => {
buildPermissionsFromRouter([
{
path: 'agent',
name: 'agent_list',
},
]);
}).toThrow("The route doesn't have the required permissions defined");
expect(() => {
buildPermissionsFromRouter([
{
path: 'agent',
name: 'agent_list',
meta: {},
},
]);
}).toThrow("The route doesn't have the required permissions defined");
});
});

View File

@@ -1,7 +1,6 @@
import {
getConversationDashboardRoute,
getCurrentAccount,
getUserRole,
isAConversationRoute,
routeIsAccessibleFor,
validateLoggedInRoutes,
@@ -15,24 +14,11 @@ describe('#getCurrentAccount', () => {
});
});
describe('#getUserRole', () => {
it('should return the current role', () => {
expect(
getUserRole({ accounts: [{ id: 1, role: 'administrator' }] }, 1)
).toEqual('administrator');
expect(getUserRole({ accounts: [] }, 1)).toEqual(null);
});
});
describe('#routeIsAccessibleFor', () => {
it('should return the correct access', () => {
const roleWiseRoutes = { agent: ['conversations'], admin: ['billing'] };
expect(routeIsAccessibleFor('billing', 'agent', roleWiseRoutes)).toEqual(
false
);
expect(routeIsAccessibleFor('billing', 'admin', roleWiseRoutes)).toEqual(
true
);
let route = { meta: { permissions: ['administrator'] } };
expect(routeIsAccessibleFor(route, ['agent'])).toEqual(false);
expect(routeIsAccessibleFor(route, ['administrator'])).toEqual(true);
});
});
@@ -40,11 +26,7 @@ describe('#validateLoggedInRoutes', () => {
describe('when account access is missing', () => {
it('should return the login route', () => {
expect(
validateLoggedInRoutes(
{ params: { accountId: 1 } },
{ accounts: [] },
{}
)
validateLoggedInRoutes({ params: { accountId: 1 } }, { accounts: [] })
).toEqual(`app/login`);
});
});
@@ -53,9 +35,12 @@ describe('#validateLoggedInRoutes', () => {
it('return suspended route', () => {
expect(
validateLoggedInRoutes(
{ name: 'conversations', params: { accountId: 1 } },
{ accounts: [{ id: 1, role: 'agent', status: 'suspended' }] },
{ agent: ['conversations'] }
{
name: 'conversations',
params: { accountId: 1 },
meta: { permissions: ['agent'] },
},
{ accounts: [{ id: 1, role: 'agent', status: 'suspended' }] }
)
).toEqual(`accounts/1/suspended`);
});
@@ -65,9 +50,22 @@ describe('#validateLoggedInRoutes', () => {
it('returns null (no action required)', () => {
expect(
validateLoggedInRoutes(
{ name: 'conversations', params: { accountId: 1 } },
{ accounts: [{ id: 1, role: 'agent', status: 'active' }] },
{ agent: ['conversations'] }
{
name: 'conversations',
params: { accountId: 1 },
meta: { permissions: ['agent'] },
},
{
permissions: ['agent'],
accounts: [
{
id: 1,
role: 'agent',
permissions: ['agent'],
status: 'active',
},
],
}
)
).toEqual(null);
});
@@ -76,9 +74,12 @@ describe('#validateLoggedInRoutes', () => {
it('returns dashboard url', () => {
expect(
validateLoggedInRoutes(
{ name: 'conversations', params: { accountId: 1 } },
{ accounts: [{ id: 1, role: 'agent', status: 'active' }] },
{ admin: ['conversations'], agent: [] }
{
name: 'billing',
params: { accountId: 1 },
meta: { permissions: ['administrator'] },
},
{ accounts: [{ id: 1, role: 'agent', status: 'active' }] }
)
).toEqual(`accounts/1/dashboard`);
});
@@ -88,8 +89,7 @@ describe('#validateLoggedInRoutes', () => {
expect(
validateLoggedInRoutes(
{ name: 'account_suspended', params: { accountId: 1 } },
{ accounts: [{ id: 1, role: 'agent', status: 'active' }] },
{ agent: ['account_suspended'] }
{ accounts: [{ id: 1, role: 'agent', status: 'active' }] }
)
).toEqual(`accounts/1/dashboard`);
});

View File

@@ -7,7 +7,9 @@ export const routes = [
{
path: frontendURL('accounts/:accountId/search'),
name: 'search',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: SearchView,
},
];

View File

@@ -7,13 +7,17 @@ export const routes = [
{
path: frontendURL('accounts/:accountId/contacts'),
name: 'contacts_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ContactsView,
},
{
path: frontendURL('accounts/:accountId/contacts/custom_view/:id'),
name: 'contacts_segments_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ContactsView,
props: route => {
return { segmentsId: route.params.id };
@@ -22,7 +26,9 @@ export const routes = [
{
path: frontendURL('accounts/:accountId/labels/:label/contacts'),
name: 'contacts_labels_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ContactsView,
props: route => {
return { label: route.params.label };
@@ -31,7 +37,9 @@ export const routes = [
{
path: frontendURL('accounts/:accountId/contacts/:contactId'),
name: 'contact_profile_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ContactManageView,
props: route => {
return { contactId: route.params.contactId };

View File

@@ -7,7 +7,9 @@ export default {
{
path: frontendURL('accounts/:accountId/dashboard'),
name: 'home',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: () => {
return { inboxId: 0 };
@@ -16,7 +18,9 @@ export default {
{
path: frontendURL('accounts/:accountId/conversations/:conversation_id'),
name: 'inbox_conversation',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => {
return { inboxId: 0, conversationId: route.params.conversation_id };
@@ -25,7 +29,9 @@ export default {
{
path: frontendURL('accounts/:accountId/inbox/:inbox_id'),
name: 'inbox_dashboard',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => {
return { inboxId: route.params.inbox_id };
@@ -36,7 +42,9 @@ export default {
'accounts/:accountId/inbox/:inbox_id/conversations/:conversation_id'
),
name: 'conversation_through_inbox',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => {
return {
@@ -48,7 +56,9 @@ export default {
{
path: frontendURL('accounts/:accountId/label/:label'),
name: 'label_conversations',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({ label: route.params.label }),
},
@@ -57,7 +67,9 @@ export default {
'accounts/:accountId/label/:label/conversations/:conversation_id'
),
name: 'conversations_through_label',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversation_id,
@@ -67,7 +79,9 @@ export default {
{
path: frontendURL('accounts/:accountId/team/:teamId'),
name: 'team_conversations',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({ teamId: route.params.teamId }),
},
@@ -76,7 +90,9 @@ export default {
'accounts/:accountId/team/:teamId/conversations/:conversationId'
),
name: 'conversations_through_team',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,
@@ -86,7 +102,9 @@ export default {
{
path: frontendURL('accounts/:accountId/custom_view/:id'),
name: 'folder_conversations',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({ foldersId: route.params.id }),
},
@@ -95,7 +113,9 @@ export default {
'accounts/:accountId/custom_view/:id/conversations/:conversation_id'
),
name: 'conversations_through_folders',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversation_id,
@@ -105,7 +125,9 @@ export default {
{
path: frontendURL('accounts/:accountId/mentions/conversations'),
name: 'conversation_mentions',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: () => ({ conversationType: 'mention' }),
},
@@ -114,7 +136,9 @@ export default {
'accounts/:accountId/mentions/conversations/:conversationId'
),
name: 'conversation_through_mentions',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,
@@ -124,7 +148,9 @@ export default {
{
path: frontendURL('accounts/:accountId/unattended/conversations'),
name: 'conversation_unattended',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: () => ({ conversationType: 'unattended' }),
},
@@ -133,7 +159,9 @@ export default {
'accounts/:accountId/unattended/conversations/:conversationId'
),
name: 'conversation_through_unattended',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,
@@ -143,7 +171,9 @@ export default {
{
path: frontendURL('accounts/:accountId/participating/conversations'),
name: 'conversation_participating',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: () => ({ conversationType: 'participating' }),
},
@@ -152,7 +182,9 @@ export default {
'accounts/:accountId/participating/conversations/:conversationId'
),
name: 'conversation_through_participating',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ConversationView,
props: route => ({
conversationId: route.params.conversationId,

View File

@@ -28,7 +28,9 @@ export default {
{
path: frontendURL('accounts/:accountId/suspended'),
name: 'account_suspended',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: Suspended,
},
],

View File

@@ -30,13 +30,17 @@ const portalRoutes = [
{
path: getPortalRoute(''),
name: 'default_portal_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator'],
},
component: DefaultPortalArticles,
},
{
path: getPortalRoute('all'),
name: 'list_all_portals',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllPortals,
},
{
@@ -47,55 +51,73 @@ const portalRoutes = [
path: '',
name: 'new_portal_information',
component: PortalDetails,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':portalSlug/customization',
name: 'portal_customization',
component: PortalCustomization,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':portalSlug/finish',
name: 'portal_finish',
component: PortalSettingsFinish,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},
{
path: getPortalRoute(':portalSlug'),
name: 'portalSlug',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ShowPortal,
},
{
path: getPortalRoute(':portalSlug/edit'),
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: EditPortal,
children: [
{
path: '',
name: 'edit_portal_information',
component: EditPortalBasic,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'customizations',
name: 'edit_portal_customization',
component: EditPortalCustomization,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'locales',
name: 'edit_portal_locales',
component: EditPortalLocales,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'categories',
name: 'list_all_locale_categories',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllCategories,
},
],
@@ -106,39 +128,51 @@ const articleRoutes = [
{
path: getPortalRoute(':portalSlug/:locale/articles'),
name: 'list_all_locale_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/new'),
name: 'new_article',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: NewArticle,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/mine'),
name: 'list_mine_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/archived'),
name: 'list_archived_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/draft'),
name: 'list_draft_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/articles/:articleSlug'),
name: 'edit_article',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: EditArticle,
},
];
@@ -147,19 +181,25 @@ const categoryRoutes = [
{
path: getPortalRoute(':portalSlug/:locale/categories'),
name: 'all_locale_categories',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllCategories,
},
{
path: getPortalRoute(':portalSlug/:locale/categories/new'),
name: 'new_category_in_locale',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: NewCategory,
},
{
path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'),
name: 'show_category',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListAllArticles,
},
{
@@ -167,13 +207,17 @@ const categoryRoutes = [
':portalSlug/:locale/categories/:categorySlug/articles'
),
name: 'show_category_articles',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: ListCategoryArticles,
},
{
path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'),
name: 'edit_category',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: EditCategory,
},
];

View File

@@ -12,13 +12,17 @@ export const routes = [
path: '',
name: 'inbox_view',
component: InboxEmptyStateView,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
{
path: ':notification_id',
name: 'inbox_view_conversation',
component: InboxDetailView,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
],
},

View File

@@ -18,7 +18,9 @@ export const routes = [
path: '',
name: 'notifications_index',
component: NotificationsView,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
],
},

View File

@@ -6,7 +6,9 @@ export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/general'),
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: SettingsContent,
props: {
headerTitle: 'GENERAL_SETTINGS.TITLE',
@@ -18,7 +20,9 @@ export default {
path: '',
name: 'general_settings_index',
component: Index,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},

View File

@@ -8,7 +8,9 @@ export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/agent-bots'),
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: SettingsContent,
props: {
headerTitle: 'AGENT_BOTS.HEADER',
@@ -20,19 +22,25 @@ export default {
path: '',
name: 'agent_bots',
component: Bot,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'csml/new',
name: 'agent_bots_csml_new',
component: CsmlNewBot,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'csml/:botId',
name: 'agent_bots_csml_edit',
component: CsmlEditBot,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},

View File

@@ -15,14 +15,15 @@ export default {
children: [
{
path: '',
name: 'agents_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'agent_list',
component: AgentHome,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},

View File

@@ -15,14 +15,15 @@ export default {
children: [
{
path: '',
name: 'attributes_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'attributes_list',
component: AttributesHome,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},

View File

@@ -16,13 +16,14 @@ export default {
children: [
{
path: '',
name: 'auditlogs_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'auditlogs_list',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: AuditLogsHome,
},
],

View File

@@ -15,14 +15,15 @@ export default {
children: [
{
path: '',
name: 'automation_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'automation_list',
component: Automation,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},

View File

@@ -6,7 +6,9 @@ export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/billing'),
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: SettingsContent,
props: {
headerTitle: 'BILLING_SETTINGS.TITLE',
@@ -18,7 +20,9 @@ export default {
path: '',
name: 'billing_settings_index',
component: Index,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},

View File

@@ -19,7 +19,9 @@ export default {
{
path: 'ongoing',
name: 'ongoing_campaigns',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],
@@ -35,7 +37,9 @@ export default {
{
path: 'one_off',
name: 'one_off',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],

View File

@@ -16,13 +16,14 @@ export default {
children: [
{
path: '',
name: 'canned_wrapper',
redirect: 'list',
},
{
path: 'list',
name: 'canned_list',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: CannedHome,
},
],

View File

@@ -28,14 +28,15 @@ export default {
children: [
{
path: '',
name: 'settings_inbox',
redirect: 'list',
},
{
path: 'list',
name: 'settings_inbox_list',
component: InboxHome,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'new',
@@ -45,19 +46,25 @@ export default {
path: '',
name: 'settings_inbox_new',
component: ChannelList,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':inbox_id/finish',
name: 'settings_inbox_finish',
component: FinishSetup,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':sub_page',
name: 'settings_inboxes_page_channel',
component: channelFactory.create(),
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
props: route => {
return { channel_name: route.params.sub_page };
},
@@ -65,7 +72,9 @@ export default {
{
path: ':inbox_id/agents',
name: 'settings_inboxes_add_agents',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: AddAgents,
},
],
@@ -74,7 +83,9 @@ export default {
path: ':inboxId',
name: 'settings_inbox_show',
component: Settings,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
],
},

View File

@@ -26,13 +26,17 @@ export default {
path: '',
name: 'settings_applications',
component: Index,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':integration_id',
name: 'settings_applications_integration',
component: IntegrationHooks,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
props: route => ({
integrationId: route.params.integration_id,
}),

View File

@@ -30,32 +30,42 @@ export default {
path: '',
name: 'settings_integrations',
component: Index,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'webhook',
component: Webhook,
name: 'settings_integrations_webhook',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'dashboard-apps',
component: DashboardApps,
name: 'settings_integrations_dashboard_apps',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'slack',
name: 'settings_integrations_slack',
component: Slack,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
props: route => ({ code: route.query.code }),
},
{
path: ':integration_id',
name: 'settings_integrations_integration',
component: ShowIntegration,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
props: route => {
return {
integrationId: route.params.integration_id,

View File

@@ -17,13 +17,17 @@ export default {
{
path: '',
name: 'labels_wrapper',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
redirect: 'list',
},
{
path: 'list',
name: 'labels_list',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],

View File

@@ -23,19 +23,25 @@ export default {
path: '',
name: 'macros_wrapper',
component: Macros,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
{
path: 'new',
name: 'macros_new',
component: MacroEditor,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
{
path: ':macroId/edit',
name: 'macros_edit',
component: MacroEditor,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
],
},

View File

@@ -8,14 +8,18 @@ export default {
{
path: frontendURL('accounts/:accountId/profile'),
name: 'profile_settings',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
component: SettingsContent,
children: [
{
path: 'settings',
name: 'profile_settings_index',
component: Index,
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
},
],
},

View File

@@ -29,7 +29,9 @@ export default {
{
path: 'overview',
name: 'account_overview_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: LiveReports,
},
],
@@ -46,7 +48,9 @@ export default {
{
path: 'conversation',
name: 'conversation_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],
@@ -63,7 +67,9 @@ export default {
{
path: 'csat',
name: 'csat_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: CsatResponses,
},
],
@@ -80,7 +86,9 @@ export default {
{
path: 'bot',
name: 'bot_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: BotReports,
},
],
@@ -97,7 +105,9 @@ export default {
{
path: 'agent',
name: 'agent_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: AgentReports,
},
],
@@ -114,7 +124,9 @@ export default {
{
path: 'label',
name: 'label_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: LabelReports,
},
],
@@ -131,7 +143,9 @@ export default {
{
path: 'inboxes',
name: 'inbox_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: InboxReports,
},
],
@@ -147,7 +161,9 @@ export default {
{
path: 'teams',
name: 'team_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: TeamReports,
},
],
@@ -164,7 +180,9 @@ export default {
{
path: 'sla',
name: 'sla_reports',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: SLAReports,
},
],

View File

@@ -24,7 +24,9 @@ export default {
{
path: frontendURL('accounts/:accountId/settings'),
name: 'settings_home',
roles: ['administrator', 'agent'],
meta: {
permissions: ['administrator', 'agent'],
},
redirect: () => {
if (store.getters.getCurrentRole === 'administrator') {
return frontendURL('accounts/:accountId/settings/general');

View File

@@ -13,13 +13,17 @@ export default {
{
path: '',
name: 'sla_wrapper',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
redirect: 'list',
},
{
path: 'list',
name: 'sla_list',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: Index,
},
],

View File

@@ -29,14 +29,15 @@ export default {
children: [
{
path: '',
name: 'settings_teams',
redirect: 'list',
},
{
path: 'list',
name: 'settings_teams_list',
component: TeamsHome,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'new',
@@ -46,18 +47,24 @@ export default {
path: '',
name: 'settings_teams_new',
component: CreateTeam,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':teamId/finish',
name: 'settings_teams_finish',
component: FinishSetup,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: ':teamId/agents',
name: 'settings_teams_add_agents',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: AddAgents,
},
],
@@ -70,18 +77,24 @@ export default {
path: '',
name: 'settings_teams_edit',
component: EditTeam,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'agents',
name: 'settings_teams_edit_members',
component: EditAgents,
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
},
{
path: 'finish',
name: 'settings_teams_edit_finish',
roles: ['administrator'],
meta: {
permissions: ['administrator'],
},
component: FinishSetup,
},
],

View File

@@ -5,33 +5,12 @@ import dashboard from './dashboard/dashboard.routes';
import store from '../store';
import { validateLoggedInRoutes } from '../helper/routeHelpers';
import AnalyticsHelper from '../helper/AnalyticsHelper';
import { buildPermissionsFromRouter } from '../helper/permissionsHelper';
const routes = [...dashboard.routes];
window.roleWiseRoutes = {
agent: [],
administrator: [],
};
// generateRoleWiseRoute - updates window object with agent/admin route
const generateRoleWiseRoute = route => {
route.forEach(element => {
if (element.children) {
generateRoleWiseRoute(element.children);
}
if (element.roles) {
element.roles.forEach(roleEl => {
window.roleWiseRoutes[roleEl].push(element.name);
});
}
});
};
// Create a object of routes
// accessible by each role.
// returns an object with roles as keys and routeArr as values
generateRoleWiseRoute(routes);
export const router = new VueRouter({ mode: 'history', routes });
export const routesWithPermissions = buildPermissionsFromRouter(routes);
export const validateAuthenticateRoutePermission = (to, next, { getters }) => {
const { isLoggedIn, getCurrentUser: user } = getters;
@@ -45,11 +24,7 @@ export const validateAuthenticateRoutePermission = (to, next, { getters }) => {
return next(frontendURL(`accounts/${user.account_id}/dashboard`));
}
const nextRoute = validateLoggedInRoutes(
to,
getters.getCurrentUser,
window.roleWiseRoutes
);
const nextRoute = validateLoggedInRoutes(to, getters.getCurrentUser);
return nextRoute ? next(frontendURL(nextRoute)) : next();
};