@nixxie-cms/core 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/CHANGES-1.1.md +134 -0
- package/context/dist/nixxie-cms-core-context.cjs.js +4 -3
- package/context/dist/nixxie-cms-core-context.esm.js +3 -2
- package/dist/declarations/src/access.d.ts +2 -2
- package/dist/declarations/src/access.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/components/Navigation.d.ts +2 -2
- package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/context.d.ts +6 -6
- package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/Fields.d.ts +3 -3
- package/dist/declarations/src/admin-ui/utils/Fields.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/filters.d.ts +5 -5
- package/dist/declarations/src/admin-ui/utils/filters.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts +3 -3
- package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/utils.d.ts +2 -2
- package/dist/declarations/src/admin-ui/utils/utils.d.ts.map +1 -1
- package/dist/declarations/src/context.d.ts +1 -1
- package/dist/declarations/src/context.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/bigInt/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/bigInt/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/bytes/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/bytes/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/calendarDay/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/calendarDay/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/checkbox/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/checkbox/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/decimal/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/decimal/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/file/index.d.ts +4 -4
- package/dist/declarations/src/fields/types/file/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/float/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/float/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/image/index.d.ts +4 -4
- package/dist/declarations/src/fields/types/image/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/integer/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/integer/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/json/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/json/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/multiselect/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/multiselect/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/password/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/password/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/index.d.ts +8 -8
- package/dist/declarations/src/fields/types/relationship/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts +3 -3
- package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts +3 -3
- package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/types.d.ts +3 -3
- package/dist/declarations/src/fields/types/relationship/views/types.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/select/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/select/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/text/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/text/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/timestamp/index.d.ts +3 -3
- package/dist/declarations/src/fields/types/timestamp/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/virtual/index.d.ts +7 -7
- package/dist/declarations/src/fields/types/virtual/index.d.ts.map +1 -1
- package/dist/declarations/src/helpers.d.ts +249 -13
- package/dist/declarations/src/helpers.d.ts.map +1 -1
- package/dist/declarations/src/index.d.ts +9 -4
- package/dist/declarations/src/index.d.ts.map +1 -1
- package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -1
- package/dist/declarations/src/lib/admin-meta.d.ts +11 -11
- package/dist/declarations/src/lib/admin-meta.d.ts.map +1 -1
- package/dist/declarations/src/lib/core/access-control.d.ts +18 -18
- package/dist/declarations/src/lib/core/access-control.d.ts.map +1 -1
- package/dist/declarations/src/lib/core/cascade.d.ts +47 -0
- package/dist/declarations/src/lib/core/cascade.d.ts.map +1 -0
- package/dist/declarations/src/lib/core/initialise-lists.d.ts +27 -24
- package/dist/declarations/src/lib/core/initialise-lists.d.ts.map +1 -1
- package/dist/declarations/src/lib/env.d.ts +9 -0
- package/dist/declarations/src/lib/env.d.ts.map +1 -0
- package/dist/declarations/src/lib/system.d.ts +1 -1
- package/dist/declarations/src/lib/system.d.ts.map +1 -1
- package/dist/declarations/src/list-features.d.ts +162 -0
- package/dist/declarations/src/list-features.d.ts.map +1 -0
- package/dist/declarations/src/schema.d.ts +24 -23
- package/dist/declarations/src/schema.d.ts.map +1 -1
- package/dist/declarations/src/session.d.ts +75 -0
- package/dist/declarations/src/session.d.ts.map +1 -1
- package/dist/declarations/src/types/admin-meta.d.ts +11 -11
- package/dist/declarations/src/types/admin-meta.d.ts.map +1 -1
- package/dist/declarations/src/types/config/access-control.d.ts +42 -42
- package/dist/declarations/src/types/config/access-control.d.ts.map +1 -1
- package/dist/declarations/src/types/config/fields.d.ts +19 -19
- package/dist/declarations/src/types/config/fields.d.ts.map +1 -1
- package/dist/declarations/src/types/config/hooks.d.ts +131 -131
- package/dist/declarations/src/types/config/hooks.d.ts.map +1 -1
- package/dist/declarations/src/types/config/index.d.ts +190 -8
- package/dist/declarations/src/types/config/index.d.ts.map +1 -1
- package/dist/declarations/src/types/config/lists.d.ts +146 -108
- package/dist/declarations/src/types/config/lists.d.ts.map +1 -1
- package/dist/declarations/src/types/context.d.ts +507 -47
- package/dist/declarations/src/types/context.d.ts.map +1 -1
- package/dist/declarations/src/types/next-fields.d.ts +28 -28
- package/dist/declarations/src/types/next-fields.d.ts.map +1 -1
- package/dist/declarations/src/types/type-info.d.ts +3 -3
- package/dist/declarations/src/types/type-info.d.ts.map +1 -1
- package/dist/{express-455ae20c.cjs.js → express-84d534c2.cjs.js} +6 -6
- package/dist/{express-7559ca2d.esm.js → express-d0a4ce99.esm.js} +6 -6
- package/dist/{index-15c8f81e.esm.js → index-5d8b0b4e.esm.js} +363 -183
- package/dist/index-6055753b.cjs.js +393 -0
- package/dist/{index-42045902.cjs.js → index-ac29f382.cjs.js} +363 -185
- package/dist/index-f1703b7b.esm.js +386 -0
- package/dist/nixxie-cms-core.cjs.js +1388 -30
- package/dist/nixxie-cms-core.esm.js +1362 -24
- package/dist/{non-null-graphql-add6bb3d.cjs.js → non-null-graphql-4a44c122.cjs.js} +1 -1
- package/dist/{non-null-graphql-a84ed64d.esm.js → non-null-graphql-8c5feaae.esm.js} +1 -1
- package/dist/{resolve-hooks-165a9ce2.cjs.js → resolve-hooks-10a5f84c.cjs.js} +240 -6
- package/dist/{resolve-hooks-6813a045.esm.js → resolve-hooks-9e676794.esm.js} +238 -7
- package/dist/{system-a321642d.cjs.js → system-6b37a5f8.cjs.js} +33 -7
- package/dist/{system-03e49e4f.esm.js → system-e591d821.esm.js} +33 -7
- package/fields/dist/nixxie-cms-core-fields.cjs.js +29 -576
- package/fields/dist/nixxie-cms-core-fields.esm.js +18 -565
- package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +4 -2
- package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +4 -2
- package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +1 -6
- package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +1 -6
- package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +4 -2
- package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +4 -2
- package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +4 -3
- package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +4 -3
- package/package.json +4 -4
- package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +4 -3
- package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +4 -3
- package/scripts/dist/nixxie-cms-core-scripts.cjs.js +4 -3
- package/scripts/dist/nixxie-cms-core-scripts.esm.js +4 -3
- package/session/dist/nixxie-cms-core-session.cjs.js +286 -0
- package/session/dist/nixxie-cms-core-session.esm.js +279 -1
- package/src/access.ts +25 -25
- package/src/admin-ui/admin-meta-graphql.ts +5 -5
- package/src/admin-ui/components/CreateButtonLink.tsx +46 -46
- package/src/admin-ui/components/Navigation.tsx +3 -3
- package/src/admin-ui/context.tsx +6 -6
- package/src/admin-ui/utils/Fields.tsx +241 -241
- package/src/admin-ui/utils/actionData.ts +36 -36
- package/src/admin-ui/utils/filters.ts +148 -148
- package/src/admin-ui/utils/useCreateItem.ts +171 -171
- package/src/admin-ui/utils/utils.tsx +127 -127
- package/src/context.ts +1 -1
- package/src/fields/non-null-graphql.ts +115 -115
- package/src/fields/types/bigInt/index.ts +6 -6
- package/src/fields/types/bytes/index.ts +6 -6
- package/src/fields/types/calendarDay/index.ts +18 -19
- package/src/fields/types/checkbox/index.ts +6 -6
- package/src/fields/types/decimal/index.ts +6 -6
- package/src/fields/types/file/index.ts +8 -8
- package/src/fields/types/float/index.ts +6 -6
- package/src/fields/types/image/index.ts +8 -8
- package/src/fields/types/integer/index.ts +6 -6
- package/src/fields/types/json/index.ts +5 -5
- package/src/fields/types/multiselect/index.ts +7 -7
- package/src/fields/types/multiselect/views/index.tsx +149 -151
- package/src/fields/types/password/index.ts +6 -6
- package/src/fields/types/relationship/index.ts +13 -13
- package/src/fields/types/relationship/views/ComboboxMany.tsx +110 -110
- package/src/fields/types/relationship/views/ComboboxSingle.tsx +115 -115
- package/src/fields/types/relationship/views/ContextualActions.tsx +139 -139
- package/src/fields/types/relationship/views/index.tsx +492 -492
- package/src/fields/types/relationship/views/types.ts +46 -46
- package/src/fields/types/relationship/views/useApolloQuery.ts +185 -185
- package/src/fields/types/relationship/views/useFilter.tsx +109 -109
- package/src/fields/types/select/index.ts +6 -6
- package/src/fields/types/text/index.ts +6 -6
- package/src/fields/types/timestamp/index.ts +23 -21
- package/src/fields/types/virtual/index.ts +11 -11
- package/src/helpers.ts +773 -42
- package/src/index.ts +66 -24
- package/src/internal-unstable/admin-ui/pages/ItemPage/common.tsx +4 -4
- package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +5 -5
- package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +8 -8
- package/src/lib/admin-meta.ts +369 -369
- package/src/lib/context/createContext.ts +6 -0
- package/src/lib/core/access-control.ts +434 -434
- package/src/lib/core/cascade.ts +236 -0
- package/src/lib/core/initialise-lists.ts +49 -33
- package/src/lib/core/mutations/index.ts +7 -0
- package/src/lib/core/mutations/nested-mutation-many-input-resolvers.ts +145 -145
- package/src/lib/core/mutations/nested-mutation-one-input-resolvers.ts +71 -71
- package/src/lib/core/queries/output-field.ts +178 -178
- package/src/lib/env.ts +50 -0
- package/src/lib/id-field.ts +2 -2
- package/src/lib/system.ts +221 -207
- package/src/lib/typescript-schema-printer.ts +227 -227
- package/src/list-features.ts +476 -0
- package/src/schema.ts +92 -22
- package/src/session.ts +225 -0
- package/src/types/admin-meta.ts +218 -218
- package/src/types/config/access-control.ts +186 -186
- package/src/types/config/fields.ts +96 -96
- package/src/types/config/hooks.ts +529 -529
- package/src/types/config/index.ts +206 -7
- package/src/types/config/lists.ts +606 -565
- package/src/types/context.ts +592 -55
- package/src/types/next-fields.ts +31 -31
- package/src/types/type-info.ts +38 -38
- package/src/types/type-tests.ts +21 -21
|
@@ -1,434 +1,434 @@
|
|
|
1
|
-
import { assertInputObjectType } from 'graphql'
|
|
2
|
-
|
|
3
|
-
import { allowAll } from '../../access'
|
|
4
|
-
import type {
|
|
5
|
-
ActionAccessControlFunction,
|
|
6
|
-
BaseItem,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
FieldAccessControl,
|
|
11
|
-
FieldAccessControlFunction,
|
|
12
|
-
FieldCreateItemAccessArgs,
|
|
13
|
-
FieldReadItemAccessArgs,
|
|
14
|
-
FieldUpdateItemAccessArgs,
|
|
15
|
-
NixxieContext,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from '../../types'
|
|
21
|
-
import { coerceAndValidateForGraphQLInput } from '../coerceAndValidateForGraphQLInput'
|
|
22
|
-
import { accessDeniedError, accessReturnError, extensionError, formatKeys } from './graphql-errors'
|
|
23
|
-
import type { InitialisedAction, InitialisedList } from './initialise-lists'
|
|
24
|
-
import { type InputFilter, type UniqueInputFilter, resolveUniqueWhereInput } from './where-inputs'
|
|
25
|
-
|
|
26
|
-
export function cannotForItem(operation: string, list: InitialisedList) {
|
|
27
|
-
if (operation === 'create')
|
|
28
|
-
return `You cannot ${operation} that ${list.graphql.names.outputTypeName}`
|
|
29
|
-
return `You cannot ${operation} that ${list.graphql.names.outputTypeName} - it may not exist`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function cannotActionForItem(action: InitialisedAction, list: InitialisedList) {
|
|
33
|
-
return `You cannot execute action "${action.actionKey}" for that ${list.graphql.names.outputTypeName}`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function cannotForItemFields(
|
|
37
|
-
operation: string,
|
|
38
|
-
list: InitialisedList,
|
|
39
|
-
fieldsDenied: string[]
|
|
40
|
-
) {
|
|
41
|
-
return `You cannot ${operation} that ${list.graphql.names.outputTypeName} - you cannot ${operation} the fields ${formatKeys(fieldsDenied)}`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function getOperationFieldAccess(
|
|
45
|
-
item: BaseItem,
|
|
46
|
-
list: InitialisedList,
|
|
47
|
-
fieldKey: string,
|
|
48
|
-
context: NixxieContext,
|
|
49
|
-
operation: 'read'
|
|
50
|
-
): Promise<boolean> {
|
|
51
|
-
if (context.__internal.sudo) return true
|
|
52
|
-
|
|
53
|
-
const { listKey } = list
|
|
54
|
-
let result
|
|
55
|
-
try {
|
|
56
|
-
result = await list.fields[fieldKey].access.read({
|
|
57
|
-
operation: 'read',
|
|
58
|
-
session: context.session,
|
|
59
|
-
listKey,
|
|
60
|
-
fieldKey,
|
|
61
|
-
context,
|
|
62
|
-
item,
|
|
63
|
-
})
|
|
64
|
-
} catch (error: any) {
|
|
65
|
-
throw extensionError('Access control', [
|
|
66
|
-
{ error, tag: `${list.listKey}.${fieldKey}.access.${operation}` },
|
|
67
|
-
])
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (typeof result !== 'boolean') {
|
|
71
|
-
throw accessReturnError([
|
|
72
|
-
{ tag: `${listKey}.access.operation.${operation}`, returned: typeof result },
|
|
73
|
-
])
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return result
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function getOperationAccess(
|
|
80
|
-
list: InitialisedList,
|
|
81
|
-
context: NixxieContext,
|
|
82
|
-
operation: 'query' | 'create' | 'update' | 'delete'
|
|
83
|
-
) {
|
|
84
|
-
if (context.__internal.sudo) return true
|
|
85
|
-
|
|
86
|
-
const { listKey } = list
|
|
87
|
-
let result
|
|
88
|
-
try {
|
|
89
|
-
if (operation === 'query') {
|
|
90
|
-
result = await list.access.operation.query({
|
|
91
|
-
operation,
|
|
92
|
-
session: context.session,
|
|
93
|
-
listKey,
|
|
94
|
-
context,
|
|
95
|
-
})
|
|
96
|
-
} else if (operation === 'create') {
|
|
97
|
-
result = await list.access.operation.create({
|
|
98
|
-
operation,
|
|
99
|
-
session: context.session,
|
|
100
|
-
listKey,
|
|
101
|
-
context,
|
|
102
|
-
})
|
|
103
|
-
} else if (operation === 'update') {
|
|
104
|
-
result = await list.access.operation.update({
|
|
105
|
-
operation,
|
|
106
|
-
session: context.session,
|
|
107
|
-
listKey,
|
|
108
|
-
context,
|
|
109
|
-
})
|
|
110
|
-
} else if (operation === 'delete') {
|
|
111
|
-
result = await list.access.operation.delete({
|
|
112
|
-
operation,
|
|
113
|
-
session: context.session,
|
|
114
|
-
listKey,
|
|
115
|
-
context,
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
} catch (error: any) {
|
|
119
|
-
throw extensionError('Access control', [
|
|
120
|
-
{ error, tag: `${listKey}.access.operation.${operation}` },
|
|
121
|
-
])
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (typeof result !== 'boolean') {
|
|
125
|
-
throw accessReturnError([
|
|
126
|
-
{ tag: `${listKey}.access.operation.${operation}`, returned: typeof result },
|
|
127
|
-
])
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return result
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export async function getAccessFilters(
|
|
134
|
-
list: InitialisedList,
|
|
135
|
-
context: NixxieContext,
|
|
136
|
-
operation: keyof typeof list.access.filter
|
|
137
|
-
): Promise<boolean | InputFilter> {
|
|
138
|
-
if (context.__internal.sudo) return true
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
let filters
|
|
142
|
-
if (operation === 'query') {
|
|
143
|
-
filters = await list.access.filter.query({
|
|
144
|
-
operation,
|
|
145
|
-
session: context.session,
|
|
146
|
-
listKey: list.listKey,
|
|
147
|
-
context,
|
|
148
|
-
})
|
|
149
|
-
} else if (operation === 'update') {
|
|
150
|
-
filters = await list.access.filter.update({
|
|
151
|
-
operation,
|
|
152
|
-
session: context.session,
|
|
153
|
-
listKey: list.listKey,
|
|
154
|
-
context,
|
|
155
|
-
})
|
|
156
|
-
} else if (operation === 'delete') {
|
|
157
|
-
filters = await list.access.filter.delete({
|
|
158
|
-
operation,
|
|
159
|
-
session: context.session,
|
|
160
|
-
listKey: list.listKey,
|
|
161
|
-
context,
|
|
162
|
-
})
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (typeof filters === 'boolean') return filters
|
|
166
|
-
if (!filters) return false // shouldn't happen, but, Typescript
|
|
167
|
-
|
|
168
|
-
const schema = context.sudo().graphql.schema
|
|
169
|
-
const whereInput = assertInputObjectType(schema.getType(list.graphql.names.whereInputName))
|
|
170
|
-
const result = coerceAndValidateForGraphQLInput(schema, whereInput, filters)
|
|
171
|
-
if (result.kind === 'valid') return result.value
|
|
172
|
-
throw result.error
|
|
173
|
-
} catch (error: any) {
|
|
174
|
-
throw extensionError('Access control', [
|
|
175
|
-
{ error, tag: `${list.listKey}.access.filter.${operation}` },
|
|
176
|
-
])
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export async function enforceListLevelAccessControl(
|
|
181
|
-
context: NixxieContext,
|
|
182
|
-
operation: 'create' | 'update' | 'delete',
|
|
183
|
-
list: InitialisedList,
|
|
184
|
-
inputData: Record<string, unknown>,
|
|
185
|
-
item: BaseItem | undefined
|
|
186
|
-
) {
|
|
187
|
-
if (context.__internal.sudo) return
|
|
188
|
-
|
|
189
|
-
let accepted: unknown // should be boolean, but dont trust, it might accidentally be a filter
|
|
190
|
-
try {
|
|
191
|
-
// apply access.item.* controls
|
|
192
|
-
if (operation === 'create') {
|
|
193
|
-
const itemAccessControl = list.access.item[operation]
|
|
194
|
-
accepted = await itemAccessControl({
|
|
195
|
-
operation,
|
|
196
|
-
session: context.session,
|
|
197
|
-
listKey: list.listKey,
|
|
198
|
-
context,
|
|
199
|
-
inputData,
|
|
200
|
-
})
|
|
201
|
-
} else if (operation === 'update' && item !== undefined) {
|
|
202
|
-
const itemAccessControl = list.access.item[operation]
|
|
203
|
-
accepted = await itemAccessControl({
|
|
204
|
-
operation,
|
|
205
|
-
session: context.session,
|
|
206
|
-
listKey: list.listKey,
|
|
207
|
-
context,
|
|
208
|
-
item,
|
|
209
|
-
inputData,
|
|
210
|
-
})
|
|
211
|
-
} else if (operation === 'delete' && item !== undefined) {
|
|
212
|
-
const itemAccessControl = list.access.item[operation]
|
|
213
|
-
accepted = await itemAccessControl({
|
|
214
|
-
operation,
|
|
215
|
-
session: context.session,
|
|
216
|
-
listKey: list.listKey,
|
|
217
|
-
context,
|
|
218
|
-
item,
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
} catch (error: any) {
|
|
222
|
-
throw extensionError('Access control', [
|
|
223
|
-
{ error, tag: `${list.listKey}.access.item.${operation}` },
|
|
224
|
-
])
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// short circuit the safe path
|
|
228
|
-
if (accepted === true) return
|
|
229
|
-
|
|
230
|
-
if (typeof accepted !== 'boolean') {
|
|
231
|
-
throw accessReturnError([
|
|
232
|
-
{
|
|
233
|
-
tag: `${list.listKey}.access.item.${operation}`,
|
|
234
|
-
returned: typeof accepted,
|
|
235
|
-
},
|
|
236
|
-
])
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
throw accessDeniedError(cannotForItem(operation, list))
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export async function enforceFieldLevelAccessControl(
|
|
243
|
-
context: NixxieContext,
|
|
244
|
-
operation: 'create' | 'update',
|
|
245
|
-
list: InitialisedList,
|
|
246
|
-
inputData: Record<string, unknown>,
|
|
247
|
-
item: BaseItem | undefined
|
|
248
|
-
) {
|
|
249
|
-
if (context.__internal.sudo) return
|
|
250
|
-
|
|
251
|
-
const nonBooleans: { tag: string; returned: string }[] = []
|
|
252
|
-
const fieldsDenied: string[] = []
|
|
253
|
-
const accessErrors: { error: Error; tag: string }[] = []
|
|
254
|
-
|
|
255
|
-
await Promise.allSettled(
|
|
256
|
-
Object.keys(inputData).map(async fieldKey => {
|
|
257
|
-
let accepted: unknown // should be boolean, but dont trust
|
|
258
|
-
try {
|
|
259
|
-
// apply fields.[fieldKey].access.* controls
|
|
260
|
-
if (operation === 'create') {
|
|
261
|
-
const fieldAccessControl = list.fields[fieldKey].access[operation]
|
|
262
|
-
accepted = await fieldAccessControl({
|
|
263
|
-
operation,
|
|
264
|
-
session: context.session,
|
|
265
|
-
listKey: list.listKey,
|
|
266
|
-
fieldKey,
|
|
267
|
-
context,
|
|
268
|
-
inputData: inputData as any, // FIXME
|
|
269
|
-
})
|
|
270
|
-
} else if (operation === 'update' && item !== undefined) {
|
|
271
|
-
const fieldAccessControl = list.fields[fieldKey].access[operation]
|
|
272
|
-
accepted = await fieldAccessControl({
|
|
273
|
-
operation,
|
|
274
|
-
session: context.session,
|
|
275
|
-
listKey: list.listKey,
|
|
276
|
-
fieldKey,
|
|
277
|
-
context,
|
|
278
|
-
item,
|
|
279
|
-
inputData,
|
|
280
|
-
})
|
|
281
|
-
}
|
|
282
|
-
} catch (error: any) {
|
|
283
|
-
accessErrors.push({ error, tag: `${list.listKey}.${fieldKey}.access.${operation}` })
|
|
284
|
-
return
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// short circuit the safe path
|
|
288
|
-
if (accepted === true) return
|
|
289
|
-
fieldsDenied.push(fieldKey)
|
|
290
|
-
|
|
291
|
-
// wrong type?
|
|
292
|
-
if (typeof accepted !== 'boolean') {
|
|
293
|
-
nonBooleans.push({
|
|
294
|
-
tag: `${list.listKey}.${fieldKey}.access.${operation}`,
|
|
295
|
-
returned: typeof accepted,
|
|
296
|
-
})
|
|
297
|
-
}
|
|
298
|
-
})
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
if (nonBooleans.length) {
|
|
302
|
-
throw accessReturnError(nonBooleans)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (accessErrors.length) {
|
|
306
|
-
throw extensionError('Access control', accessErrors)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (fieldsDenied.length) {
|
|
310
|
-
throw accessDeniedError(cannotForItemFields(operation, list, fieldsDenied))
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
export type ResolvedFieldAccessControl = {
|
|
315
|
-
read: FieldAccessControlFunction<FieldReadItemAccessArgs<
|
|
316
|
-
create: FieldAccessControlFunction<FieldCreateItemAccessArgs<
|
|
317
|
-
update: FieldAccessControlFunction<FieldUpdateItemAccessArgs<
|
|
318
|
-
// delete: not supported
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function parseFieldAccessControl(
|
|
322
|
-
access: FieldAccessControl<
|
|
323
|
-
): ResolvedFieldAccessControl {
|
|
324
|
-
if (typeof access === 'function') {
|
|
325
|
-
return {
|
|
326
|
-
read: access,
|
|
327
|
-
create: access,
|
|
328
|
-
update: access,
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return {
|
|
333
|
-
read: access?.read ?? allowAll,
|
|
334
|
-
create: access?.create ?? allowAll,
|
|
335
|
-
update: access?.update ?? allowAll,
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export type ResolvedActionAccessControl = ActionAccessControlFunction<
|
|
340
|
-
|
|
341
|
-
export type
|
|
342
|
-
operation: {
|
|
343
|
-
query:
|
|
344
|
-
create:
|
|
345
|
-
update:
|
|
346
|
-
delete:
|
|
347
|
-
}
|
|
348
|
-
filter: {
|
|
349
|
-
query:
|
|
350
|
-
// create: not supported
|
|
351
|
-
update:
|
|
352
|
-
delete:
|
|
353
|
-
}
|
|
354
|
-
item: {
|
|
355
|
-
// query: not supported
|
|
356
|
-
create:
|
|
357
|
-
update:
|
|
358
|
-
delete:
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export function parseListAccessControl(
|
|
363
|
-
access:
|
|
364
|
-
):
|
|
365
|
-
if (typeof access === 'function') {
|
|
366
|
-
return {
|
|
367
|
-
operation: {
|
|
368
|
-
query: access,
|
|
369
|
-
create: access,
|
|
370
|
-
update: access,
|
|
371
|
-
delete: access,
|
|
372
|
-
},
|
|
373
|
-
filter: {
|
|
374
|
-
query: allowAll,
|
|
375
|
-
update: allowAll,
|
|
376
|
-
delete: allowAll,
|
|
377
|
-
},
|
|
378
|
-
item: {
|
|
379
|
-
create: allowAll,
|
|
380
|
-
update: allowAll,
|
|
381
|
-
delete: allowAll,
|
|
382
|
-
},
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
let { operation, filter, item } = access
|
|
387
|
-
if (typeof operation === 'function') {
|
|
388
|
-
operation = {
|
|
389
|
-
query: operation,
|
|
390
|
-
create: operation,
|
|
391
|
-
update: operation,
|
|
392
|
-
delete: operation,
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return {
|
|
397
|
-
operation: {
|
|
398
|
-
query: operation.query,
|
|
399
|
-
create: operation.create,
|
|
400
|
-
update: operation.update,
|
|
401
|
-
delete: operation.delete,
|
|
402
|
-
},
|
|
403
|
-
filter: {
|
|
404
|
-
query: filter?.query ?? allowAll,
|
|
405
|
-
// create: not supported
|
|
406
|
-
update: filter?.update ?? allowAll,
|
|
407
|
-
delete: filter?.delete ?? allowAll,
|
|
408
|
-
},
|
|
409
|
-
item: {
|
|
410
|
-
// query: not supported
|
|
411
|
-
create: item?.create ?? allowAll,
|
|
412
|
-
update: item?.update ?? allowAll,
|
|
413
|
-
delete: item?.delete ?? allowAll,
|
|
414
|
-
},
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
export async function checkUniqueItemExists(
|
|
419
|
-
uniqueInput: UniqueInputFilter,
|
|
420
|
-
foreignList: InitialisedList,
|
|
421
|
-
context: NixxieContext,
|
|
422
|
-
operation: string
|
|
423
|
-
) {
|
|
424
|
-
// Validate and resolve the input filter
|
|
425
|
-
const uniqueWhere = await resolveUniqueWhereInput(uniqueInput, foreignList, context)
|
|
426
|
-
|
|
427
|
-
// Check whether the item exists (from this users POV).
|
|
428
|
-
try {
|
|
429
|
-
const item = await context.db[foreignList.listKey].findOne({ where: uniqueInput })
|
|
430
|
-
if (item !== null) return uniqueWhere
|
|
431
|
-
} catch (err) {}
|
|
432
|
-
|
|
433
|
-
throw accessDeniedError(cannotForItem(operation, foreignList))
|
|
434
|
-
}
|
|
1
|
+
import { assertInputObjectType } from 'graphql'
|
|
2
|
+
|
|
3
|
+
import { allowAll } from '../../access'
|
|
4
|
+
import type {
|
|
5
|
+
ActionAccessControlFunction,
|
|
6
|
+
BaseItem,
|
|
7
|
+
BaseCollectionTypeInfo,
|
|
8
|
+
CreateCollectionItemAccessControl,
|
|
9
|
+
DeleteCollectionItemAccessControl,
|
|
10
|
+
FieldAccessControl,
|
|
11
|
+
FieldAccessControlFunction,
|
|
12
|
+
FieldCreateItemAccessArgs,
|
|
13
|
+
FieldReadItemAccessArgs,
|
|
14
|
+
FieldUpdateItemAccessArgs,
|
|
15
|
+
NixxieContext,
|
|
16
|
+
CollectionAccessControl,
|
|
17
|
+
CollectionFilterAccessControl,
|
|
18
|
+
CollectionOperationAccessControl,
|
|
19
|
+
UpdateCollectionItemAccessControl,
|
|
20
|
+
} from '../../types'
|
|
21
|
+
import { coerceAndValidateForGraphQLInput } from '../coerceAndValidateForGraphQLInput'
|
|
22
|
+
import { accessDeniedError, accessReturnError, extensionError, formatKeys } from './graphql-errors'
|
|
23
|
+
import type { InitialisedAction, InitialisedList } from './initialise-lists'
|
|
24
|
+
import { type InputFilter, type UniqueInputFilter, resolveUniqueWhereInput } from './where-inputs'
|
|
25
|
+
|
|
26
|
+
export function cannotForItem(operation: string, list: InitialisedList) {
|
|
27
|
+
if (operation === 'create')
|
|
28
|
+
return `You cannot ${operation} that ${list.graphql.names.outputTypeName}`
|
|
29
|
+
return `You cannot ${operation} that ${list.graphql.names.outputTypeName} - it may not exist`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function cannotActionForItem(action: InitialisedAction, list: InitialisedList) {
|
|
33
|
+
return `You cannot execute action "${action.actionKey}" for that ${list.graphql.names.outputTypeName}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function cannotForItemFields(
|
|
37
|
+
operation: string,
|
|
38
|
+
list: InitialisedList,
|
|
39
|
+
fieldsDenied: string[]
|
|
40
|
+
) {
|
|
41
|
+
return `You cannot ${operation} that ${list.graphql.names.outputTypeName} - you cannot ${operation} the fields ${formatKeys(fieldsDenied)}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function getOperationFieldAccess(
|
|
45
|
+
item: BaseItem,
|
|
46
|
+
list: InitialisedList,
|
|
47
|
+
fieldKey: string,
|
|
48
|
+
context: NixxieContext,
|
|
49
|
+
operation: 'read'
|
|
50
|
+
): Promise<boolean> {
|
|
51
|
+
if (context.__internal.sudo) return true
|
|
52
|
+
|
|
53
|
+
const { listKey } = list
|
|
54
|
+
let result
|
|
55
|
+
try {
|
|
56
|
+
result = await list.fields[fieldKey].access.read({
|
|
57
|
+
operation: 'read',
|
|
58
|
+
session: context.session,
|
|
59
|
+
listKey,
|
|
60
|
+
fieldKey,
|
|
61
|
+
context,
|
|
62
|
+
item,
|
|
63
|
+
})
|
|
64
|
+
} catch (error: any) {
|
|
65
|
+
throw extensionError('Access control', [
|
|
66
|
+
{ error, tag: `${list.listKey}.${fieldKey}.access.${operation}` },
|
|
67
|
+
])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (typeof result !== 'boolean') {
|
|
71
|
+
throw accessReturnError([
|
|
72
|
+
{ tag: `${listKey}.access.operation.${operation}`, returned: typeof result },
|
|
73
|
+
])
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function getOperationAccess(
|
|
80
|
+
list: InitialisedList,
|
|
81
|
+
context: NixxieContext,
|
|
82
|
+
operation: 'query' | 'create' | 'update' | 'delete'
|
|
83
|
+
) {
|
|
84
|
+
if (context.__internal.sudo) return true
|
|
85
|
+
|
|
86
|
+
const { listKey } = list
|
|
87
|
+
let result
|
|
88
|
+
try {
|
|
89
|
+
if (operation === 'query') {
|
|
90
|
+
result = await list.access.operation.query({
|
|
91
|
+
operation,
|
|
92
|
+
session: context.session,
|
|
93
|
+
listKey,
|
|
94
|
+
context,
|
|
95
|
+
})
|
|
96
|
+
} else if (operation === 'create') {
|
|
97
|
+
result = await list.access.operation.create({
|
|
98
|
+
operation,
|
|
99
|
+
session: context.session,
|
|
100
|
+
listKey,
|
|
101
|
+
context,
|
|
102
|
+
})
|
|
103
|
+
} else if (operation === 'update') {
|
|
104
|
+
result = await list.access.operation.update({
|
|
105
|
+
operation,
|
|
106
|
+
session: context.session,
|
|
107
|
+
listKey,
|
|
108
|
+
context,
|
|
109
|
+
})
|
|
110
|
+
} else if (operation === 'delete') {
|
|
111
|
+
result = await list.access.operation.delete({
|
|
112
|
+
operation,
|
|
113
|
+
session: context.session,
|
|
114
|
+
listKey,
|
|
115
|
+
context,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
} catch (error: any) {
|
|
119
|
+
throw extensionError('Access control', [
|
|
120
|
+
{ error, tag: `${listKey}.access.operation.${operation}` },
|
|
121
|
+
])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (typeof result !== 'boolean') {
|
|
125
|
+
throw accessReturnError([
|
|
126
|
+
{ tag: `${listKey}.access.operation.${operation}`, returned: typeof result },
|
|
127
|
+
])
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function getAccessFilters(
|
|
134
|
+
list: InitialisedList,
|
|
135
|
+
context: NixxieContext,
|
|
136
|
+
operation: keyof typeof list.access.filter
|
|
137
|
+
): Promise<boolean | InputFilter> {
|
|
138
|
+
if (context.__internal.sudo) return true
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
let filters
|
|
142
|
+
if (operation === 'query') {
|
|
143
|
+
filters = await list.access.filter.query({
|
|
144
|
+
operation,
|
|
145
|
+
session: context.session,
|
|
146
|
+
listKey: list.listKey,
|
|
147
|
+
context,
|
|
148
|
+
})
|
|
149
|
+
} else if (operation === 'update') {
|
|
150
|
+
filters = await list.access.filter.update({
|
|
151
|
+
operation,
|
|
152
|
+
session: context.session,
|
|
153
|
+
listKey: list.listKey,
|
|
154
|
+
context,
|
|
155
|
+
})
|
|
156
|
+
} else if (operation === 'delete') {
|
|
157
|
+
filters = await list.access.filter.delete({
|
|
158
|
+
operation,
|
|
159
|
+
session: context.session,
|
|
160
|
+
listKey: list.listKey,
|
|
161
|
+
context,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (typeof filters === 'boolean') return filters
|
|
166
|
+
if (!filters) return false // shouldn't happen, but, Typescript
|
|
167
|
+
|
|
168
|
+
const schema = context.sudo().graphql.schema
|
|
169
|
+
const whereInput = assertInputObjectType(schema.getType(list.graphql.names.whereInputName))
|
|
170
|
+
const result = coerceAndValidateForGraphQLInput(schema, whereInput, filters)
|
|
171
|
+
if (result.kind === 'valid') return result.value
|
|
172
|
+
throw result.error
|
|
173
|
+
} catch (error: any) {
|
|
174
|
+
throw extensionError('Access control', [
|
|
175
|
+
{ error, tag: `${list.listKey}.access.filter.${operation}` },
|
|
176
|
+
])
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function enforceListLevelAccessControl(
|
|
181
|
+
context: NixxieContext,
|
|
182
|
+
operation: 'create' | 'update' | 'delete',
|
|
183
|
+
list: InitialisedList,
|
|
184
|
+
inputData: Record<string, unknown>,
|
|
185
|
+
item: BaseItem | undefined
|
|
186
|
+
) {
|
|
187
|
+
if (context.__internal.sudo) return
|
|
188
|
+
|
|
189
|
+
let accepted: unknown // should be boolean, but dont trust, it might accidentally be a filter
|
|
190
|
+
try {
|
|
191
|
+
// apply access.item.* controls
|
|
192
|
+
if (operation === 'create') {
|
|
193
|
+
const itemAccessControl = list.access.item[operation]
|
|
194
|
+
accepted = await itemAccessControl({
|
|
195
|
+
operation,
|
|
196
|
+
session: context.session,
|
|
197
|
+
listKey: list.listKey,
|
|
198
|
+
context,
|
|
199
|
+
inputData,
|
|
200
|
+
})
|
|
201
|
+
} else if (operation === 'update' && item !== undefined) {
|
|
202
|
+
const itemAccessControl = list.access.item[operation]
|
|
203
|
+
accepted = await itemAccessControl({
|
|
204
|
+
operation,
|
|
205
|
+
session: context.session,
|
|
206
|
+
listKey: list.listKey,
|
|
207
|
+
context,
|
|
208
|
+
item,
|
|
209
|
+
inputData,
|
|
210
|
+
})
|
|
211
|
+
} else if (operation === 'delete' && item !== undefined) {
|
|
212
|
+
const itemAccessControl = list.access.item[operation]
|
|
213
|
+
accepted = await itemAccessControl({
|
|
214
|
+
operation,
|
|
215
|
+
session: context.session,
|
|
216
|
+
listKey: list.listKey,
|
|
217
|
+
context,
|
|
218
|
+
item,
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
} catch (error: any) {
|
|
222
|
+
throw extensionError('Access control', [
|
|
223
|
+
{ error, tag: `${list.listKey}.access.item.${operation}` },
|
|
224
|
+
])
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// short circuit the safe path
|
|
228
|
+
if (accepted === true) return
|
|
229
|
+
|
|
230
|
+
if (typeof accepted !== 'boolean') {
|
|
231
|
+
throw accessReturnError([
|
|
232
|
+
{
|
|
233
|
+
tag: `${list.listKey}.access.item.${operation}`,
|
|
234
|
+
returned: typeof accepted,
|
|
235
|
+
},
|
|
236
|
+
])
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
throw accessDeniedError(cannotForItem(operation, list))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function enforceFieldLevelAccessControl(
|
|
243
|
+
context: NixxieContext,
|
|
244
|
+
operation: 'create' | 'update',
|
|
245
|
+
list: InitialisedList,
|
|
246
|
+
inputData: Record<string, unknown>,
|
|
247
|
+
item: BaseItem | undefined
|
|
248
|
+
) {
|
|
249
|
+
if (context.__internal.sudo) return
|
|
250
|
+
|
|
251
|
+
const nonBooleans: { tag: string; returned: string }[] = []
|
|
252
|
+
const fieldsDenied: string[] = []
|
|
253
|
+
const accessErrors: { error: Error; tag: string }[] = []
|
|
254
|
+
|
|
255
|
+
await Promise.allSettled(
|
|
256
|
+
Object.keys(inputData).map(async fieldKey => {
|
|
257
|
+
let accepted: unknown // should be boolean, but dont trust
|
|
258
|
+
try {
|
|
259
|
+
// apply fields.[fieldKey].access.* controls
|
|
260
|
+
if (operation === 'create') {
|
|
261
|
+
const fieldAccessControl = list.fields[fieldKey].access[operation]
|
|
262
|
+
accepted = await fieldAccessControl({
|
|
263
|
+
operation,
|
|
264
|
+
session: context.session,
|
|
265
|
+
listKey: list.listKey,
|
|
266
|
+
fieldKey,
|
|
267
|
+
context,
|
|
268
|
+
inputData: inputData as any, // FIXME
|
|
269
|
+
})
|
|
270
|
+
} else if (operation === 'update' && item !== undefined) {
|
|
271
|
+
const fieldAccessControl = list.fields[fieldKey].access[operation]
|
|
272
|
+
accepted = await fieldAccessControl({
|
|
273
|
+
operation,
|
|
274
|
+
session: context.session,
|
|
275
|
+
listKey: list.listKey,
|
|
276
|
+
fieldKey,
|
|
277
|
+
context,
|
|
278
|
+
item,
|
|
279
|
+
inputData,
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
} catch (error: any) {
|
|
283
|
+
accessErrors.push({ error, tag: `${list.listKey}.${fieldKey}.access.${operation}` })
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// short circuit the safe path
|
|
288
|
+
if (accepted === true) return
|
|
289
|
+
fieldsDenied.push(fieldKey)
|
|
290
|
+
|
|
291
|
+
// wrong type?
|
|
292
|
+
if (typeof accepted !== 'boolean') {
|
|
293
|
+
nonBooleans.push({
|
|
294
|
+
tag: `${list.listKey}.${fieldKey}.access.${operation}`,
|
|
295
|
+
returned: typeof accepted,
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if (nonBooleans.length) {
|
|
302
|
+
throw accessReturnError(nonBooleans)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (accessErrors.length) {
|
|
306
|
+
throw extensionError('Access control', accessErrors)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (fieldsDenied.length) {
|
|
310
|
+
throw accessDeniedError(cannotForItemFields(operation, list, fieldsDenied))
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export type ResolvedFieldAccessControl = {
|
|
315
|
+
read: FieldAccessControlFunction<FieldReadItemAccessArgs<BaseCollectionTypeInfo>>
|
|
316
|
+
create: FieldAccessControlFunction<FieldCreateItemAccessArgs<BaseCollectionTypeInfo>>
|
|
317
|
+
update: FieldAccessControlFunction<FieldUpdateItemAccessArgs<BaseCollectionTypeInfo>>
|
|
318
|
+
// delete: not supported
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function parseFieldAccessControl(
|
|
322
|
+
access: FieldAccessControl<BaseCollectionTypeInfo> | undefined
|
|
323
|
+
): ResolvedFieldAccessControl {
|
|
324
|
+
if (typeof access === 'function') {
|
|
325
|
+
return {
|
|
326
|
+
read: access,
|
|
327
|
+
create: access,
|
|
328
|
+
update: access,
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
read: access?.read ?? allowAll,
|
|
334
|
+
create: access?.create ?? allowAll,
|
|
335
|
+
update: access?.update ?? allowAll,
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export type ResolvedActionAccessControl = ActionAccessControlFunction<BaseCollectionTypeInfo>
|
|
340
|
+
|
|
341
|
+
export type ResolvedCollectionAccessControl = {
|
|
342
|
+
operation: {
|
|
343
|
+
query: CollectionOperationAccessControl<'query', BaseCollectionTypeInfo>
|
|
344
|
+
create: CollectionOperationAccessControl<'create', BaseCollectionTypeInfo>
|
|
345
|
+
update: CollectionOperationAccessControl<'update', BaseCollectionTypeInfo>
|
|
346
|
+
delete: CollectionOperationAccessControl<'delete', BaseCollectionTypeInfo>
|
|
347
|
+
}
|
|
348
|
+
filter: {
|
|
349
|
+
query: CollectionFilterAccessControl<'query', BaseCollectionTypeInfo>
|
|
350
|
+
// create: not supported
|
|
351
|
+
update: CollectionFilterAccessControl<'update', BaseCollectionTypeInfo>
|
|
352
|
+
delete: CollectionFilterAccessControl<'delete', BaseCollectionTypeInfo>
|
|
353
|
+
}
|
|
354
|
+
item: {
|
|
355
|
+
// query: not supported
|
|
356
|
+
create: CreateCollectionItemAccessControl<BaseCollectionTypeInfo>
|
|
357
|
+
update: UpdateCollectionItemAccessControl<BaseCollectionTypeInfo>
|
|
358
|
+
delete: DeleteCollectionItemAccessControl<BaseCollectionTypeInfo>
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function parseListAccessControl(
|
|
363
|
+
access: CollectionAccessControl<BaseCollectionTypeInfo>
|
|
364
|
+
): ResolvedCollectionAccessControl {
|
|
365
|
+
if (typeof access === 'function') {
|
|
366
|
+
return {
|
|
367
|
+
operation: {
|
|
368
|
+
query: access,
|
|
369
|
+
create: access,
|
|
370
|
+
update: access,
|
|
371
|
+
delete: access,
|
|
372
|
+
},
|
|
373
|
+
filter: {
|
|
374
|
+
query: allowAll,
|
|
375
|
+
update: allowAll,
|
|
376
|
+
delete: allowAll,
|
|
377
|
+
},
|
|
378
|
+
item: {
|
|
379
|
+
create: allowAll,
|
|
380
|
+
update: allowAll,
|
|
381
|
+
delete: allowAll,
|
|
382
|
+
},
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let { operation, filter, item } = access
|
|
387
|
+
if (typeof operation === 'function') {
|
|
388
|
+
operation = {
|
|
389
|
+
query: operation,
|
|
390
|
+
create: operation,
|
|
391
|
+
update: operation,
|
|
392
|
+
delete: operation,
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
operation: {
|
|
398
|
+
query: operation.query,
|
|
399
|
+
create: operation.create,
|
|
400
|
+
update: operation.update,
|
|
401
|
+
delete: operation.delete,
|
|
402
|
+
},
|
|
403
|
+
filter: {
|
|
404
|
+
query: filter?.query ?? allowAll,
|
|
405
|
+
// create: not supported
|
|
406
|
+
update: filter?.update ?? allowAll,
|
|
407
|
+
delete: filter?.delete ?? allowAll,
|
|
408
|
+
},
|
|
409
|
+
item: {
|
|
410
|
+
// query: not supported
|
|
411
|
+
create: item?.create ?? allowAll,
|
|
412
|
+
update: item?.update ?? allowAll,
|
|
413
|
+
delete: item?.delete ?? allowAll,
|
|
414
|
+
},
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export async function checkUniqueItemExists(
|
|
419
|
+
uniqueInput: UniqueInputFilter,
|
|
420
|
+
foreignList: InitialisedList,
|
|
421
|
+
context: NixxieContext,
|
|
422
|
+
operation: string
|
|
423
|
+
) {
|
|
424
|
+
// Validate and resolve the input filter
|
|
425
|
+
const uniqueWhere = await resolveUniqueWhereInput(uniqueInput, foreignList, context)
|
|
426
|
+
|
|
427
|
+
// Check whether the item exists (from this users POV).
|
|
428
|
+
try {
|
|
429
|
+
const item = await context.db[foreignList.listKey].findOne({ where: uniqueInput })
|
|
430
|
+
if (item !== null) return uniqueWhere
|
|
431
|
+
} catch (err) {}
|
|
432
|
+
|
|
433
|
+
throw accessDeniedError(cannotForItem(operation, foreignList))
|
|
434
|
+
}
|