Skip to content

Commit

Permalink
Merge branch 'main' into #246
Browse files Browse the repository at this point in the history
  • Loading branch information
ymaheshwari1 committed Mar 18, 2024
2 parents 6769c66 + 5ef2594 commit 883a772
Show file tree
Hide file tree
Showing 20 changed files with 1,630 additions and 1,466 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ VUE_APP_I18N_FALLBACK_LOCALE=en
VUE_APP_CACHE_MAX_AGE=3600
VUE_APP_VIEW_SIZE=10
VUE_APP_DATE_FORMAT=MM/dd/yyyy
VUE_APP_PERMISSION_ID=
VUE_APP_PERMISSION_ID="IMPORT_APP_VIEW"
VUE_APP_ALIAS={}
VUE_APP_MAPPING_TYPES={"PO": "PO_MAPPING_PREF","RSTINV": "INV_MAPPING_PREF"}
VUE_APP_MAPPING_PO={"orderId": { "label": "Order ID", "required": true }, "productSku": { "label": "Shopify product SKU", "required": true },"orderDate": { "label": "Arrival date", "required": true }, "quantity": { "label": "Ordered quantity", "required": true }, "facility": { "label": "Facility ID", "required": true }}
Expand Down
2,582 changes: 1,249 additions & 1,333 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
"dependencies": {
"@capacitor/android": "^2.4.7",
"@capacitor/core": "^2.4.7",
"@casl/ability": "^6.0.0",
"@hotwax/app-version-info": "^1.0.0",
"@hotwax/apps-theme": "^1.2.6",
"@hotwax/dxp-components": "^1.12.1",
"@hotwax/oms-api": "^1.10.0",
"@hotwax/oms-api": "^1.13.0",
"@ionic/core": "^7.6.0",
"@ionic/vue": "^7.6.0",
"@ionic/vue-router": "~7.6.0",
"boon-js": "^2.0.3",
"@types/file-saver": "^2.0.4",
"@types/papaparse": "^5.3.1",
"core-js": "^3.6.5",
Expand Down
2 changes: 2 additions & 0 deletions src/adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
api,
client,
getConfig,
hasError,
fetchProducts,
initialise,
logout,
Expand All @@ -16,6 +17,7 @@ export {
api,
client,
getConfig,
hasError,
fetchProducts,
initialise,
logout,
Expand Down
2 changes: 2 additions & 0 deletions src/authorization/Actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default {
}
4 changes: 4 additions & 0 deletions src/authorization/Rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
"APP_INVENTORY_VIEW": "MDM_IMP_INVENTORY_VIEW",
"IMPORT_APP_VIEW": "IMPORT_APP_VIEW"
} as any
124 changes: 124 additions & 0 deletions src/authorization/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { AbilityBuilder, PureAbility } from '@casl/ability';
import { getEvaluator, parse } from 'boon-js';
import { Tokens } from 'boon-js/lib/types'

// TODO Improve this
// We will move this code to an external plugin and use below Actions and Rules accordlingly
let Actions = {} as any;
let Rules = {} as any;

// We are using CASL library to define permissions.
// Instead of using Action-Subject based authorisation we are going with Claim based Authorization.
// We would be defining the permissions for each action and case, map with server permissiosn based upon certain rules.
// https://casl.js.org/v5/en/cookbook/claim-authorization
// Following the comment of Sergii Stotskyi, author of CASL
// https://github.com/stalniy/casl/issues/525
// We are defining a PureAbility and creating an instance with AbilityBuilder.
type ClaimBasedAbility = PureAbility<string>;
const { build } = new AbilityBuilder<ClaimBasedAbility>(PureAbility);
const ability = build();

/**
* The method returns list of permissions required for the rules. We are having set of rules,
* through which app permissions are defined based upon the server permissions.
* When getting server permissions, as all the permissions are not be required.
* Specific permissions used defining the rules are extracted and sent to server.
* @returns permissions
*/
const getServerPermissionsFromRules = () => {
// Iterate for each rule
const permissions = Object.keys(Rules).reduce((permissions: any, rule: any) => {
const permissionRule = Rules[rule];
// some rules may be empty, no permission is required from server
if (permissionRule) {
// Each rule may have multiple permissions along with operators
// Boon js parse rules into tokens, each token may be operator or server permission
// permissionId will have token name as identifier.
const permissionTokens = parse(permissionRule);
permissions = permissionTokens.reduce((permissions: any, permissionToken: any) => {
// Token object with name as identifier has permissionId
if (Tokens.IDENTIFIER === permissionToken.name) {
permissions.add(permissionToken.value);
}
return permissions;
}, permissions)
}
return permissions;
}, new Set())
return [...permissions];
}

/**
* The method is used to prepare app permissions from the server permissions.
* Rules could be defined such that each app permission could be defined based upon certain one or more server permissions.
* @param serverPermissions
* @returns appPermissions
*/
const prepareAppPermissions = (serverPermissions: any) => {
const serverPermissionsInput = serverPermissions.reduce((serverPermissionsInput: any, permission: any) => {
serverPermissionsInput[permission] = true;
return serverPermissionsInput;
}, {})
// Boonjs evaluator needs server permissions as object with permissionId and boolean value
// Each rule is passed to evaluator along with the server permissions
// if the server permissions and rule matches, app permission is added to list
const permissions = Object.keys(Rules).reduce((permissions: any, rule: any) => {
const permissionRule = Rules[rule];
// If for any app permission, we have empty rule we user is assigned the permission
// If rule is not defined, the app permisions is still evaluated or provided to all the users.
if (!permissionRule || (permissionRule && getEvaluator(permissionRule)(serverPermissionsInput))) {
permissions.push(rule);
}
return permissions;
}, [])
const { can, rules } = new AbilityBuilder<ClaimBasedAbility>(PureAbility);
permissions.map((permission: any) => {
can(permission);
})
return rules;
}

/**
*
* Sets the current app permissions. This should be used after perparing the app permissions from the server permissions
* @param permissions
* @returns
*/
const setPermissions = (permissions: any) => {
// If the user has passed undefined or null, it should not break the code
if (!permissions) permissions = [];
ability.update(permissions)
return true;
};

/**
* Resets the permissions list. Used for cases like logout
*/
const resetPermissions = () => setPermissions([]);

/**
*
* @param permission
* @returns
*/
const hasPermission = (permission: string) => ability.can(permission);

export { Actions, getServerPermissionsFromRules, hasPermission, prepareAppPermissions, resetPermissions, setPermissions};

// TODO Move this code to an external plugin, to be used across the apps
export default {
install(app: any, options: any) {

// Rules and Actions could be app and OMS package specific
Rules = options.rules;
Actions = options.actions;

// TODO Check why global properties is not working and apply across.
app.config.globalProperties.$permission = this;
},
getServerPermissionsFromRules,
hasPermission,
prepareAppPermissions,
resetPermissions,
setPermissions
}
7 changes: 4 additions & 3 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"App": "App",
"Apply": "Apply",
"Any edits made on this page will be lost.": "Any edits made on this page made will be lost.",
"Are you sure you want to change the time zone to?": "Are you sure you want to change the time zone to?",
"Are you sure you want to change the date time format?": "Are you sure you want to change the date time format?",
"Are you sure you want to delete this CSV mapping? This action cannot be undone.": "Are you sure you want to delete this CSV mapping? This action cannot be undone.",
"Are you sure you want to update this CSV mapping? This action cannot be undone.": "Are you sure you want to update this CSV mapping? This action cannot be undone.",
Expand Down Expand Up @@ -48,12 +47,14 @@
"Failed to save CSV mapping.": "Failed to save CSV mapping.",
"Failed to delete CSV mapping.": "Failed to delete CSV mapping.",
"Failed to update CSV mapping.": "Failed to update CSV mapping.",
"Fetching TimeZones": "Fetching TimeZones",
"Field mapping name": "Field mapping name",
"This CSV mapping has been saved.": "This CSV mapping has been saved.",
"This CSV mapping has been deleted.": "This CSV mapping has been deleted.",
"Changes to the CSV mapping has been saved.": "Changes to the CSV mapping has been saved.",
"File upload": "File upload",
"File uploaded successfully": "File uploaded successfully",
"Fetching time zones": "Fetching time zones",
"Go to Launchpad": "Go to Launchpad",
"Go to OMS": "Go to OMS",
"here": "here",
Expand Down Expand Up @@ -164,7 +165,6 @@
"UI Components": "UI Components",
"Update": "Update",
"Update mapping": "Update mapping",
"Update time zone": "Update time zone",
"Update date time format": "Update date time format",
"Upload": "Upload",
"Upload a file": "Upload a file",
Expand All @@ -173,5 +173,6 @@
"Version: ": "Version: {appVersion}",
"View all date time formats supported by the HotWax Import app.": "View all date time formats supported by the HotWax Import app.",
"View": "View",
"Would you like to update your time zone to . Your profile is currently set to . This setting can always be changed from the settings menu.": "Would you like to update your time zone to {localTimeZone}. Your profile is currently set to {profileTimeZone}. This setting can always be changed from the settings menu."
"Would you like to update your time zone to . Your profile is currently set to . This setting can always be changed from the settings menu.": "Would you like to update your time zone to {localTimeZone}. Your profile is currently set to {profileTimeZone}. This setting can always be changed from the settings menu.",
"You do not have permission to access this page": "You do not have permission to access this page"
}
9 changes: 7 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router';
import './registerServiceWorker'

import { IonicVue } from '@ionic/vue';

Expand All @@ -28,7 +27,9 @@ import '@hotwax/apps-theme';
import i18n from './i18n'
import store from './store'
import { DateTime } from 'luxon';

import permissionPlugin from '@/authorization';
import permissionRules from '@/authorization/Rules';
import permissionActions from '@/authorization/Actions';
import logger from './logger';
import { dxpComponents } from '@hotwax/dxp-components'
import { login, logout, loader } from './user-utils';
Expand All @@ -45,6 +46,10 @@ const app = createApp(App)
.use(router)
.use(i18n)
.use(store)
.use(permissionPlugin, {
rules: permissionRules,
actions: permissionActions
})
.use(dxpComponents, {
defaultImgUrl: require("@/assets/images/defaultImage.png"),
login,
Expand Down
31 changes: 0 additions & 31 deletions src/registerServiceWorker.ts

This file was deleted.

31 changes: 29 additions & 2 deletions src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,17 @@ import SavedMappings from '@/views/SavedMappings.vue'
import Settings from "@/views/Settings.vue"
import store from '@/store'
import MappingDetail from '@/views/MappingDetail.vue'
import { DxpLogin, useAuthStore } from '@hotwax/dxp-components';
import { DxpLogin, translate, useAuthStore } from '@hotwax/dxp-components';
import { loader } from '@/user-utils';
import { showToast } from '@/utils';
import { hasPermission } from '@/authorization';

// Defining types for the meta values
declare module 'vue-router' {
interface RouteMeta {
permissionId?: string;
}
}

const authGuard = async (to: any, from: any, next: any) => {
const authStore = useAuthStore()
Expand Down Expand Up @@ -52,7 +61,10 @@ const routes: Array<RouteRecordRaw> = [
path: '/inventory',
name: 'Inventory',
component: Inventory,
beforeEnter: authGuard
beforeEnter: authGuard,
meta: {
permissionId: "APP_INVENTORY_VIEW"
}
},
{
path: '/inventory-review',
Expand Down Expand Up @@ -91,4 +103,19 @@ const router = createRouter({
routes
})

router.beforeEach((to, from) => {
if (to.meta.permissionId && !hasPermission(to.meta.permissionId)) {
let redirectToPath = from.path;
// If the user has navigated from Login page or if it is page load, redirect user to settings page without showing any toast
if (redirectToPath == "/login" || redirectToPath == "/") redirectToPath = "/settings";
else {
showToast(translate('You do not have permission to access this page'));
}
return {
path: redirectToPath,
}
}
})


export default router
Loading

0 comments on commit 883a772

Please sign in to comment.