changes access permissions structure to be more typescript-friendly

This commit is contained in:
James
2020-11-24 12:53:09 -05:00
parent 777a2292dc
commit cfdacea210
21 changed files with 119 additions and 125 deletions

View File

@@ -121,7 +121,7 @@ const Routes = () => {
path={`${match.url}/collections/${collection.slug}`}
exact
render={(routeProps) => {
if (permissions?.[collection.slug]?.read?.permission) {
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<List
{...routeProps}
@@ -141,7 +141,7 @@ const Routes = () => {
path={`${match.url}/collections/${collection.slug}/create`}
exact
render={(routeProps) => {
if (permissions?.[collection.slug]?.create?.permission) {
if (permissions?.collections?.[collection.slug]?.create?.permission) {
return (
<Edit
{...routeProps}
@@ -161,7 +161,7 @@ const Routes = () => {
path={`${match.url}/collections/${collection.slug}/:id`}
exact
render={(routeProps) => {
if (permissions?.[collection.slug]?.read?.permission) {
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<Edit
isEditing
@@ -182,7 +182,7 @@ const Routes = () => {
path={`${match.url}/globals/${global.slug}`}
exact
render={(routeProps) => {
if (permissions?.[global.slug]?.read?.permission) {
if (permissions?.globals?.[global.slug]?.read?.permission) {
return (
<EditGlobal
{...routeProps}

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { NavLink, Link, useHistory } from 'react-router-dom';
import { useConfig } from '@payloadcms/config-provider';
import { useAuth } from '@payloadcms/config-provider';
import { useConfig, useAuth } from '@payloadcms/config-provider';
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
import Chevron from '../../icons/Chevron';
import LogOut from '../../icons/LogOut';
@@ -65,7 +65,7 @@ const DefaultNav = () => {
{collections && collections.map((collection, i) => {
const href = `${admin}/collections/${collection.slug}`;
if (permissions?.[collection.slug]?.read.permission) {
if (permissions?.collections?.[collection.slug]?.read.permission) {
return (
<NavLink
activeClassName="active"
@@ -88,7 +88,7 @@ const DefaultNav = () => {
{globals.map((global, i) => {
const href = `${admin}/globals/${global.slug}`;
if (permissions?.[global.slug].read.permission) {
if (permissions?.globals?.[global.slug].read.permission) {
return (
<NavLink
activeClassName="active"

View File

@@ -7,7 +7,7 @@ import Select from '../../../../../Select';
const createOptions = (collections, permissions) => collections.reduce((options, collection) => {
if (permissions[collection.slug]?.read?.permission && collection?.admin?.enableRichTextRelationship) {
if (permissions?.collections?.[collection.slug]?.read?.permission && collection?.admin?.enableRichTextRelationship) {
return [
...options,
{

View File

@@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useConfig } from '@payloadcms/config-provider';
import { useConfig, useAuth } from '@payloadcms/config-provider';
import { useStepNav } from '../../elements/StepNav';
import { useAuth } from '@payloadcms/config-provider';
import usePayloadAPI from '../../../hooks/usePayloadAPI';
import { useLocale } from '../../utilities/Locale';
import DefaultAccount from './Default';
@@ -31,7 +31,7 @@ const AccountView = () => {
const { fields } = collection;
const collectionPermissions = permissions?.[user?.collection];
const collectionPermissions = permissions?.collections?.[user?.collection];
const [{ data, isLoading }] = usePayloadAPI(
`${serverURL}${api}/${collection?.slug}/${user?.id}?depth=0`,

View File

@@ -33,7 +33,7 @@ const Dashboard = (props) => {
<h3 className={`${baseClass}__label`}>Collections</h3>
<ul className={`${baseClass}__card-list`}>
{collections.map((collection) => {
const hasCreatePermission = permissions?.[collection.slug]?.create?.permission;
const hasCreatePermission = permissions?.collections?.[collection.slug]?.create?.permission;
return (
<li key={collection.slug}>

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useConfig } from '@payloadcms/config-provider';
import { useAuth } from '@payloadcms/config-provider';
import { useConfig, useAuth } from '@payloadcms/config-provider';
import { useStepNav } from '../../elements/StepNav';
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
import DefaultDashboard from './Default';
@@ -22,7 +22,7 @@ const Dashboard = () => {
useEffect(() => {
setFilteredGlobals(
globals.filter((global) => permissions?.[global.slug]?.read?.permission),
globals.filter((global) => permissions?.globals?.[global.slug]?.read?.permission),
);
}, [permissions, globals]);
@@ -36,7 +36,7 @@ const Dashboard = () => {
CustomComponent={CustomDashboard}
componentProps={{
globals: filteredGlobals,
collections: collections.filter((collection) => permissions?.[collection.slug]?.read?.permission),
collections: collections.filter((collection) => permissions?.collections?.[collection.slug]?.read?.permission),
permissions,
}}
/>

View File

@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useHistory, useLocation } from 'react-router-dom';
import { useConfig } from '@payloadcms/config-provider';
import { useConfig, useAuth } from '@payloadcms/config-provider';
import { useStepNav } from '../../elements/StepNav';
import usePayloadAPI from '../../../hooks/usePayloadAPI';
import { useAuth } from '@payloadcms/config-provider';
import { useLocale } from '../../utilities/Locale';
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
@@ -77,7 +77,7 @@ const GlobalView = (props) => {
awaitInitialState();
}, [dataToRender, fields]);
const globalPermissions = permissions?.[slug];
const globalPermissions = permissions?.globals?.[slug];
return (
<NegativeFieldGutterProvider allow>

View File

@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Redirect, useRouteMatch, useHistory, useLocation } from 'react-router-dom';
import { useConfig } from '@payloadcms/config-provider';
import { useConfig, useAuth } from '@payloadcms/config-provider';
import { useStepNav } from '../../../elements/StepNav';
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import { useAuth } from '@payloadcms/config-provider';
import RenderCustomComponent from '../../../utilities/RenderCustomComponent';
import DefaultEdit from './Default';
@@ -91,7 +91,7 @@ const EditView = (props) => {
);
}
const collectionPermissions = permissions?.[slug];
const collectionPermissions = permissions?.collections?.[slug];
const apiURL = `${serverURL}${api}/${slug}/${id}`;
const action = `${serverURL}${api}/${slug}${isEditing ? `/${id}` : ''}?locale=${locale}&depth=0`;

View File

@@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import queryString from 'qs';
import { useLocation } from 'react-router-dom';
import { useConfig } from '@payloadcms/config-provider';
import { useAuth } from '@payloadcms/config-provider';
import { useConfig, useAuth } from '@payloadcms/config-provider';
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import DefaultList from './Default';
import RenderCustomComponent from '../../../utilities/RenderCustomComponent';
@@ -37,7 +37,7 @@ const ListView = (props) => {
const [columns, setColumns] = useState([]);
const [sort, setSort] = useState(null);
const collectionPermissions = permissions?.[slug];
const collectionPermissions = permissions?.collections?.[slug];
const hasCreatePermission = collectionPermissions?.create?.permission;
const { page } = queryString.parse(location.search, { ignoreQueryPrefix: true });

View File

@@ -1,3 +1,6 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - need to do this because this file doesn't actually exist
import config from 'payload/config';
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -6,7 +9,6 @@ import { WindowInfoProvider } from '@faceless-ui/window-info';
import { ModalProvider, ModalContainer } from '@faceless-ui/modal';
import { ToastContainer, Slide } from 'react-toastify';
import { ConfigProvider, AuthProvider } from '@payloadcms/config-provider';
import unsanitizedConfig from 'payload/unsanitizedConfig';
import { SearchParamsProvider } from './components/utilities/SearchParams';
import { LocaleProvider } from './components/utilities/Locale';
import Routes from './components/Routes';
@@ -16,7 +18,7 @@ import './scss/app.scss';
const Index = () => (
<React.Fragment>
<ConfigProvider config={unsanitizedConfig}>
<ConfigProvider config={config}>
<WindowInfoProvider breakpoints={{
xs: parseInt(getCSSVariable('breakpoint-xs-width').replace('px', ''), 10),
s: parseInt(getCSSVariable('breakpoint-s-width').replace('px', ''), 10),

View File

@@ -67,18 +67,20 @@ async function accessOperation(args) {
});
};
const executeEntityPolicies = (entity, operations) => {
results[entity.slug] = {
const executeEntityPolicies = (entity, operations, type) => {
if (!results[type]) results[type] = {};
results[type][entity.slug] = {
fields: {},
};
operations.forEach((operation) => {
executeFieldPolicies(results[entity.slug].fields, entity.fields, operation);
executeFieldPolicies(results[type][entity.slug].fields, entity.fields, operation);
if (typeof entity.access[operation] === 'function') {
promises.push(createAccessPromise(results[entity.slug], entity.access[operation], operation));
promises.push(createAccessPromise(results[type][entity.slug], entity.access[operation], operation));
} else {
results[entity.slug][operation] = {
results[type][entity.slug][operation] = {
permission: isLoggedIn,
};
}
@@ -87,17 +89,17 @@ async function accessOperation(args) {
if (userCollectionConfig) {
results.canAccessAdmin = userCollectionConfig.access.admin ? userCollectionConfig.access.admin(args) : isLoggedIn;
if (results.canAccessAdmin) results.license = config.license;
if (results.canAccessAdmin) results.license = this.license;
} else {
results.canAccessAdmin = false;
}
config.collections.forEach((collection) => {
executeEntityPolicies(collection, allOperations);
executeEntityPolicies(collection, allOperations, 'collection');
});
config.globals.forEach((global) => {
executeEntityPolicies(global, ['read', 'update']);
executeEntityPolicies(global, ['read', 'update'], 'global');
});
await Promise.all(promises);

View File

@@ -3,31 +3,51 @@ export type Permission = {
where?: Record<string, unknown>
}
export type Permissions = {
[key: string]: boolean | {
create: Permission
read: Permission
update: Permission
delete: Permission
fields: {
[field: string]: {
create: {
permission: boolean
}
read: {
permission: boolean
}
update: {
permission: boolean
}
delete: {
permission: boolean
}
export type CollectionPermission = {
create: Permission
read: Permission
update: Permission
delete: Permission
fields: {
[field: string]: {
create: {
permission: boolean
}
read: {
permission: boolean
}
update: {
permission: boolean
}
delete: {
permission: boolean
}
}
}
}
export type GlobalPermission = {
read: Permission
update: Permission
fields: {
[field: string]: {
read: {
permission: boolean
}
update: {
permission: boolean
}
}
}
}
export type Permissions = {
canAccessAdmin: boolean
license?: string
collections: CollectionPermission[]
globals?: GlobalPermission[]
}
export type User = {
id: string
email: string

View File

@@ -66,7 +66,6 @@ const sanitizeConfig = (config: Config): Config => {
sanitizedConfig.components = { ...(config.components || {}) };
sanitizedConfig.hooks = { ...(config.hooks || {}) };
sanitizedConfig.admin = { ...(config.admin || {}) };
return sanitizedConfig as DeepRequired<Config>;
};

View File

@@ -3,7 +3,8 @@ import { Response, NextFunction } from 'express';
import formatErrorResponse from '../responses/formatError';
import { PayloadRequest } from '../types/payloadRequest';
const errorHandler = (config, logger) => async (err, req: PayloadRequest, res: Response): Promise<void> => {
// NextFunction must be passed for Express to use this middleware as error handler
const errorHandler = (config, logger) => async (err, req: PayloadRequest, res: Response, next: NextFunction): Promise<void> => {
const data = formatErrorResponse(err);
let response;
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR;

View File

@@ -318,4 +318,7 @@ export class Payload {
}
}
module.exports = new Payload();
const payload = new Payload();
export default payload;
module.exports = payload;

View File

@@ -78,7 +78,7 @@ export default (config: Config): Configuration => {
},
modules: ['node_modules', path.resolve(__dirname, '../../node_modules')],
alias: {
'payload/unsanitizedConfig': config.paths.config,
'payload/config': config.paths.config,
'@payloadcms/payload$': mockModulePath,
},
extensions: ['.ts', '.tsx', '.js', '.json'],

View File

@@ -103,7 +103,7 @@ export default (config: Config): Configuration => {
},
modules: ['node_modules', path.resolve(__dirname, '../../node_modules')],
alias: {
'payload/unsanitizedConfig': config.paths.config,
'payload/config': config.paths.config,
'@payloadcms/payload$': mockModulePath,
},
},