feat(useAcl): create checkUrl

This commit is contained in:
Alex Moreno 2024-09-20 11:43:15 +02:00
parent 1150739de7
commit bb85e8b3cb
5 changed files with 84 additions and 10 deletions

View File

@ -3,6 +3,8 @@ import qFormMixin from './qformMixin';
import mainShortcutMixin from './mainShortcutMixin'; import mainShortcutMixin from './mainShortcutMixin';
import keyShortcut from './keyShortcut'; import keyShortcut from './keyShortcut';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { AccessError } from 'src/utils/errors';
const { notify } = useNotify(); const { notify } = useNotify();
export default boot(({ app }) => { export default boot(({ app }) => {
@ -11,6 +13,12 @@ export default boot(({ app }) => {
app.directive('shortcut', keyShortcut); app.directive('shortcut', keyShortcut);
app.config.errorHandler = function (err) { app.config.errorHandler = function (err) {
console.error(err); console.error(err);
notify('globals.error', 'negative', 'error'); switch (err.constructor) {
case AccessError:
notify('errors.statusUnauthorized', 'negative', 'account_circle_off');
break;
default:
notify('globals.error', 'negative', 'error');
}
}; };
}); });

View File

@ -1,5 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { useState } from './useState'; import { useState } from './useState';
import { AccessError } from 'src/utils/errors';
export function useAcl() { export function useAcl() {
const state = useState(); const state = useState();
@ -8,31 +9,86 @@ export function useAcl() {
const { data } = await axios.get('VnUsers/acls'); const { data } = await axios.get('VnUsers/acls');
const acls = {}; const acls = {};
data.forEach((acl) => { data.forEach((acl) => {
acls[acl.model] = acls[acl.model] || {}; const model = acl.model.toLowerCase();
acls[acl.model][acl.property] = acls[acl.model][acl.property] || {}; const property = acl.property.toLowerCase();
acls[acl.model][acl.property][acl.accessType] = true; acls[model] = acls[model] || {};
acls[model][property] = acls[model][property] || {};
acls[model][property][acl.accessType] = true;
}); });
state.setAcls(acls); state.setAcls(acls);
} }
function hasAny(acls) { function hasAny(acls) {
let result = false;
for (const acl of acls) { for (const acl of acls) {
let { model, props, accessType } = acl; let { model, props, accessType } = acl;
const modelAcls = state.getAcls().value[model]; const modelAcls = state.getAcls().value[model.toLowerCase()];
Array.isArray(props) || (props = [props]); Array.isArray(props) || (props = [props]);
if (modelAcls) if (modelAcls) {
return ['*', ...props].some((key) => { result = ['*', ...props].some((key) => {
const acl = modelAcls[key]; const acl = modelAcls[key.toLowerCase()];
return acl && (acl['*'] || acl[accessType]); return acl && (acl['*'] || acl[accessType]);
}); });
}
if (result) return result;
} }
return false; return result;
}
function checkRead(model, urlSplit) {
const acls = [];
let props = urlSplit[1] ?? 'find';
if (typeof +props == 'number') {
props = urlSplit[2] ?? 'findById';
acls.push({ model, props: `__get__${props}`, accessType: 'READ' });
}
acls.push({ model, props, accessType: 'READ' });
console.log('acls: ', acls);
console.log('hasAny(acls): ', hasAny(acls));
return hasAny(acls);
}
function checkWrite(model, urlSplit, type) {
const acls = [];
let props = urlSplit[1] ?? (type != 'post' ? 'upsert' : 'create');
if (typeof +props == 'number') {
if (!urlSplit[2]) props = 'updateAttributes';
else if (['post', 'delete'].includes(type)) {
let prefix = 'create';
if (type == 'delete') prefix = type;
acls.push({ model, props: `__${prefix}__${props}`, accessType: 'WRITE' });
}
}
acls.push({ model, props, accessType: 'WRITE' });
console.log('acls: ', acls);
console.log('hasAny(acls): ', hasAny(acls));
return hasAny(acls);
}
function checkUrl(url, type = 'get', throwError, model) {
if (!url) return true;
const urlSplit = url.split('/');
model ??= urlSplit[0]?.slice(1, -1);
type = type.toLowerCase();
let hasPermission;
if (type == 'get') hasPermission = checkRead(model, urlSplit);
else hasPermission = checkWrite(model, urlSplit, type);
if (throwError && !hasPermission) throw new AccessError(url);
return hasPermission;
} }
return { return {
fetch, fetch,
hasAny, hasAny,
state, state,
checkRead,
checkWrite,
checkUrl,
}; };
} }

View File

@ -54,7 +54,7 @@ export function useSession() {
headers: { Authorization: storage.getItem(key) }, headers: { Authorization: storage.getItem(key) },
}); });
} catch (error) { } catch (error) {
notify('errors.statusUnauthorized', 'negative'); notify('errors.statusUnauthorized', 'negative', 'account_circle_off');
} finally { } finally {
storage.removeItem(key); storage.removeItem(key);
} }

View File

@ -17,6 +17,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
searchUrl: 'params', searchUrl: 'params',
navigate: null, navigate: null,
page: 1, page: 1,
checkAcl: true,
}; };
function get(key) { function get(key) {

9
src/utils/errors.js Normal file
View File

@ -0,0 +1,9 @@
class AccessError extends Error {
constructor(details, message) {
super(message ?? 'Access denied');
this.name = AccessError.name;
this.details = details;
}
}
export { AccessError };