mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Add permissions store (#393)
* Fetch role nested in user store * Use nested role in presets * Add permissions store * Fix tests
This commit is contained in:
@@ -7,6 +7,7 @@ import { useCollectionPresetsStore } from '@/stores/collection-presets/';
|
||||
import { useSettingsStore } from '@/stores/settings/';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
import { useLatencyStore } from '@/stores/latency';
|
||||
import { usePermissionsStore } from '@/stores/permissions';
|
||||
|
||||
type GenericStore = {
|
||||
id: string;
|
||||
@@ -26,6 +27,7 @@ export function useStores(
|
||||
useSettingsStore,
|
||||
useProjectsStore,
|
||||
useLatencyStore,
|
||||
usePermissionsStore,
|
||||
]
|
||||
) {
|
||||
return stores.map((useStore) => useStore()) as GenericStore[];
|
||||
@@ -41,6 +43,11 @@ export async function hydrate(stores = useStores()) {
|
||||
appStore.state.hydrating = true;
|
||||
|
||||
try {
|
||||
/**
|
||||
* @NOTE
|
||||
* This will fetch the store data sequential. While this does prevent rate limiteres from
|
||||
* kicking in, we could optimize it by running (some of) the requests in parallel
|
||||
*/
|
||||
for (const store of stores) {
|
||||
await store.hydrate?.();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('Compositions / Collection Presets', () => {
|
||||
);
|
||||
|
||||
const userStore = useUserStore(req);
|
||||
(userStore.state.currentUser as any) = { id: 15, role: 25 };
|
||||
(userStore.state.currentUser as any) = { id: 15, role: { id: 25 } };
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
|
||||
@@ -28,7 +28,7 @@ export const useCollectionPresetsStore = createStore({
|
||||
// All role saved bookmarks and presets
|
||||
api.get(`/${currentProjectKey}/collection_presets`, {
|
||||
params: {
|
||||
'filter[role][eq]': role,
|
||||
'filter[role][eq]': role.id,
|
||||
'filter[user][null]': 1,
|
||||
},
|
||||
}),
|
||||
@@ -99,7 +99,7 @@ export const useCollectionPresetsStore = createStore({
|
||||
|
||||
const availablePresets = this.state.collectionPresets.filter((preset) => {
|
||||
const userMatches = preset.user === userID || preset.user === null;
|
||||
const roleMatches = preset.role === userRole || preset.role === null;
|
||||
const roleMatches = preset.role === userRole.id || preset.role === null;
|
||||
const collectionMatches = preset.collection === collection;
|
||||
|
||||
// Filter out all bookmarks
|
||||
@@ -117,7 +117,7 @@ export const useCollectionPresetsStore = createStore({
|
||||
const userPreset = availablePresets.find((preset) => preset.user === userID);
|
||||
if (userPreset) return userPreset;
|
||||
|
||||
const rolePreset = availablePresets.find((preset) => preset.role === userRole);
|
||||
const rolePreset = availablePresets.find((preset) => preset.role === userRole.id);
|
||||
if (rolePreset) return rolePreset;
|
||||
|
||||
// If the other two already came up empty, we can assume there's only one preset. That
|
||||
|
||||
4
src/stores/permissions/index.ts
Normal file
4
src/stores/permissions/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { usePermissionsStore } from './permissions';
|
||||
|
||||
export { usePermissionsStore };
|
||||
export default usePermissionsStore;
|
||||
66
src/stores/permissions/permissions.ts
Normal file
66
src/stores/permissions/permissions.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { createStore } from 'pinia';
|
||||
import useUserStore from '@/stores/user';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import { Permission } from './types';
|
||||
|
||||
const defaultAdminPermission: Partial<Permission> = {
|
||||
role: 1,
|
||||
create: 'full',
|
||||
read: 'full',
|
||||
update: 'full',
|
||||
delete: 'full',
|
||||
comment: 'full',
|
||||
explain: 'none',
|
||||
read_field_blacklist: [],
|
||||
write_field_blacklist: [],
|
||||
status_blacklist: [],
|
||||
};
|
||||
|
||||
const defaultPermission: Partial<Permission> = {
|
||||
create: 'none',
|
||||
read: 'none',
|
||||
update: 'none',
|
||||
delete: 'none',
|
||||
comment: 'none',
|
||||
explain: 'none',
|
||||
read_field_blacklist: [],
|
||||
write_field_blacklist: [],
|
||||
status_blacklist: [],
|
||||
};
|
||||
|
||||
export const usePermissionsStore = createStore({
|
||||
id: 'permissionsStore',
|
||||
state: () => ({
|
||||
permissions: [] as Permission[],
|
||||
}),
|
||||
actions: {
|
||||
getPermissionsForCollection(collection: string) {
|
||||
const userStore = useUserStore();
|
||||
|
||||
if (userStore.isAdmin.value) return defaultAdminPermission;
|
||||
|
||||
const permissions = this.state.permissions.filter(
|
||||
(permission) => permission.collection === collection
|
||||
);
|
||||
|
||||
return permissions ? permissions : defaultPermission;
|
||||
},
|
||||
async hydrate() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
if (userStore.isAdmin.value) return;
|
||||
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const permissionsResponse = await api.get(`/${currentProjectKey}/permissions`);
|
||||
|
||||
this.state.permissions = permissionsResponse.data.data;
|
||||
},
|
||||
async dehydrate() {
|
||||
this.reset();
|
||||
},
|
||||
},
|
||||
});
|
||||
15
src/stores/permissions/types.ts
Normal file
15
src/stores/permissions/types.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export type Permission = {
|
||||
id: number;
|
||||
collection: string;
|
||||
role: number;
|
||||
status: string | null;
|
||||
create: 'full' | 'none';
|
||||
read: 'none' | 'mine' | 'role' | 'full';
|
||||
update: 'none' | 'mine' | 'role' | 'full';
|
||||
delete: 'none' | 'mine' | 'role' | 'full';
|
||||
comment: 'none' | 'read' | 'create' | 'update' | 'full';
|
||||
explain: 'none' | 'create' | 'update' | 'always';
|
||||
read_field_blacklist: string[];
|
||||
write_field_blacklist: string[];
|
||||
status_blacklist: string[];
|
||||
};
|
||||
@@ -24,7 +24,16 @@ export type User = {
|
||||
external_id: string;
|
||||
'2fa_secret': string;
|
||||
theme: 'auto' | 'dark' | 'light';
|
||||
role: number;
|
||||
role: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
collection_listing: null;
|
||||
module_listing: null;
|
||||
enforce_2fa: null | boolean;
|
||||
external_id: null | string;
|
||||
ip_whitelist: string[];
|
||||
};
|
||||
password_reset_token: string | null;
|
||||
timezone: string;
|
||||
locale: string;
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('Stores / User', () => {
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith('/my-project/users/me', {
|
||||
params: {
|
||||
fields: '*,avatar.data',
|
||||
fields: '*,avatar.data,role.*',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,9 @@ export const useUserStore = createStore({
|
||||
if (state.currentUser === null) return null;
|
||||
return state.currentUser.first_name + ' ' + state.currentUser.last_name;
|
||||
},
|
||||
isAdmin(state) {
|
||||
return state.currentUser?.role.id === 1;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async hydrate() {
|
||||
@@ -28,7 +31,7 @@ export const useUserStore = createStore({
|
||||
try {
|
||||
const { data } = await api.get(`/${currentProjectKey}/users/me`, {
|
||||
params: {
|
||||
fields: '*,avatar.data',
|
||||
fields: '*,avatar.data,role.*',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user