Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement permission policies in the API #22384

Draft
wants to merge 180 commits into
base: auditus
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 113 commits
Commits
Show all changes
180 commits
Select commit Hold shift + click to select a range
54b67d4
WIP start on migrations
rijkvanzanten Mar 21, 2024
9faf285
Add migration
rijkvanzanten Mar 21, 2024
afde4d7
Don't insert if there's no rows
rijkvanzanten Mar 25, 2024
69479f4
Use service to read/write permissions
rijkvanzanten Mar 25, 2024
3bf7a80
Use payload service rather than itemsservice
rijkvanzanten Mar 26, 2024
054bb60
Start on downgrade command
rijkvanzanten Mar 26, 2024
0a226a3
Update system data structure
rijkvanzanten Mar 26, 2024
7ab5c01
Update migrations to keep structure flat
rijkvanzanten Mar 26, 2024
716cf58
Remove icon from policies
rijkvanzanten Mar 26, 2024
eb54dd9
Drop policies table on downgrade
rijkvanzanten Mar 26, 2024
458eb13
Rearchitect migrations to structure v3
rijkvanzanten Mar 28, 2024
a8a15ec
Add down migration
rijkvanzanten Mar 28, 2024
831913f
Update system fields
rijkvanzanten Mar 28, 2024
5cb5af9
Add policy to fields import
rijkvanzanten Mar 28, 2024
f1b4257
Merge branch 'auditus' into auditus-21760
rijkvanzanten Mar 29, 2024
376bd0a
Merge branch 'auditus' into auditus-21760
paescuj Apr 1, 2024
b322ae8
Merge branch 'auditus' into auditus-21760
rijkvanzanten Apr 9, 2024
4b152bf
Fix public role attachment
rijkvanzanten Apr 9, 2024
4c7d828
Update packages/system-data/src/fields/index.ts
rijkvanzanten Apr 10, 2024
93efe32
Update packages/system-data/src/fields/policies.yaml
rijkvanzanten Apr 10, 2024
9f20d13
Add nested roles
rijkvanzanten Apr 10, 2024
ab7b03c
Remove unused step
rijkvanzanten Apr 10, 2024
a3bdef0
Use two o2ms instead of m2a for attachments
rijkvanzanten Apr 10, 2024
655c0c4
Update system data
rijkvanzanten Apr 10, 2024
4a0946d
[WIP] Start reorging permissions handling
rijkvanzanten Apr 11, 2024
4badbff
Merge branch 'auditus' into auditus-21765
rijkvanzanten Apr 11, 2024
275fcf6
Setup field extraction
rijkvanzanten Apr 12, 2024
4534ffe
Remove watch from vitest
rijkvanzanten Apr 12, 2024
c2cd7a2
Finish fieldMap creation logic
rijkvanzanten Apr 12, 2024
f274910
Add tests for utils
rijkvanzanten Apr 12, 2024
4b48bdf
Improve tests
rijkvanzanten Apr 12, 2024
188ecb4
Improve coverage
rijkvanzanten Apr 12, 2024
df9c2d8
Split test and test:watch
rijkvanzanten Apr 12, 2024
137fcac
Continue on this fun
rijkvanzanten Apr 13, 2024
93528a2
[WIP] Setup processing
rijkvanzanten Apr 15, 2024
bf73d91
Sort roles
rijkvanzanten Apr 15, 2024
e41c8ea
Restructure to util files for org
rijkvanzanten Apr 15, 2024
f756e60
Add missing util tests
rijkvanzanten Apr 15, 2024
1057d2e
More tests
rijkvanzanten Apr 15, 2024
e7ee41f
Add cases/whencase to ast
rijkvanzanten Apr 16, 2024
b5989af
Start on injection logic
rijkvanzanten Apr 16, 2024
0755126
Add tests for inject cases
rijkvanzanten Apr 16, 2024
b0e36ef
Add tests for process
rijkvanzanten Apr 16, 2024
c5b5f16
Add todo
rijkvanzanten Apr 17, 2024
8bf1163
Merge branch 'auditus' into auditus-21765
rijkvanzanten Apr 17, 2024
638635b
Organize run-ast
rijkvanzanten Apr 18, 2024
61fb739
Merge branch 'auditus' into auditus-21765
rijkvanzanten Apr 22, 2024
9edb4b0
Add clear method to kv
rijkvanzanten Apr 23, 2024
73127b6
Remove reliance on acc.perm
rijkvanzanten Apr 23, 2024
71c3d52
Restructure permissions setup
rijkvanzanten Apr 23, 2024
db73e39
Drop perm from acc, add roles/policies
rijkvanzanten Apr 23, 2024
447948e
Remove get-permissions in middleware
rijkvanzanten Apr 23, 2024
9a52a5d
Remove/comment use of acc.perm
rijkvanzanten Apr 23, 2024
e57f617
Add default roles/permissions
rijkvanzanten Apr 23, 2024
8805955
Use knex
rijkvanzanten Apr 23, 2024
36f5b48
Use new fetching logic in get accountability
rijkvanzanten Apr 23, 2024
fd1c448
Add new fetch global access utils
rijkvanzanten Apr 23, 2024
c0b87a7
Gotta redo based on new setup
rijkvanzanten Apr 23, 2024
d6db532
Replaced with new util
rijkvanzanten Apr 23, 2024
930086a
Remove dropping of perm in acc
rijkvanzanten Apr 23, 2024
abb7f29
Temporarily comment out the enforce tfa check
rijkvanzanten Apr 23, 2024
552522f
Update usage of fetch tree to use knex
rijkvanzanten Apr 23, 2024
2a3d421
Don't store policies on accountability
rijkvanzanten Apr 23, 2024
b866c25
Feed in roles thru acc
rijkvanzanten Apr 23, 2024
08cd10a
Bit of whitespace
rijkvanzanten Apr 23, 2024
37529f6
Rename role->policy
rijkvanzanten Apr 23, 2024
57261a1
Wreck some more stuff
rijkvanzanten Apr 23, 2024
f5ac71d
Add ability to lookup all allowed fields in col+ac
rijkvanzanten Apr 23, 2024
9c81050
Add note so I don't forget stuff which i will
rijkvanzanten Apr 23, 2024
6536ab6
Handle null acc
rijkvanzanten Apr 23, 2024
13fcc61
Introduce parseAst to itemsservice
rijkvanzanten Apr 23, 2024
1c41a28
That cleans things up
rijkvanzanten Apr 23, 2024
8315ba4
Replace checkAccess with validateAccess
rijkvanzanten Apr 24, 2024
d900b0b
Remove checkaccess from service
rijkvanzanten Apr 24, 2024
5468408
cleanup imports
rijkvanzanten Apr 24, 2024
5477dd1
Merge branch 'auditus' into auditus-21765
rijkvanzanten Apr 25, 2024
6abde5a
Whoops one more
rijkvanzanten Apr 25, 2024
6aad409
Leave crumbs for next time
rijkvanzanten Apr 26, 2024
642180e
Implement most of the fn
rijkvanzanten Apr 26, 2024
037355b
Fix various tests
rijkvanzanten Apr 30, 2024
11d5401
Start on test for fetch roles tree
rijkvanzanten May 1, 2024
ade3a21
Merge branch 'auditus' into auditus-21765
rijkvanzanten May 1, 2024
c763e54
Add tests for fetch roles tree
rijkvanzanten May 1, 2024
089a17a
Fix process tests
rijkvanzanten May 1, 2024
e576576
All. of. the. tests.
rijkvanzanten May 2, 2024
88ee853
Update uses of validateAccess
rijkvanzanten May 2, 2024
34432ca
Fix name in runAst
rijkvanzanten May 2, 2024
9394ec5
Fix use of accountability in gql sub
rijkvanzanten May 2, 2024
9e63b89
Deprecate authorization service
rijkvanzanten May 2, 2024
814f78a
Remove getPermissions use
rijkvanzanten May 2, 2024
67df179
Drop old getpermissions
rijkvanzanten May 2, 2024
e7d9ec7
Pass services
rijkvanzanten May 2, 2024
76993d2
Replace admin/app uses with fetch global
rijkvanzanten May 2, 2024
17d3c92
Update fetch user count to pull from policies
rijkvanzanten May 3, 2024
6ecb0d3
Remove broken admin existence checks
rijkvanzanten May 3, 2024
be93506
Update min accountability
rijkvanzanten May 3, 2024
f24a808
Remove unused import
rijkvanzanten May 3, 2024
10c3d0d
Drop permissions override from controller
rijkvanzanten May 3, 2024
0cc3373
Refactor reliance on acc.perm
rijkvanzanten May 3, 2024
9ecfd58
Replace usage of permissions in fields
rijkvanzanten May 3, 2024
761a3dc
Replace usage of permissions in import/export
rijkvanzanten May 3, 2024
b835679
Drop permissions use from relations
rijkvanzanten May 3, 2024
71f75fa
Drop no longer used method
rijkvanzanten May 3, 2024
acb326c
Remove unused import
rijkvanzanten May 3, 2024
b61f2ca
fix type usage of pk in validate
rijkvanzanten May 3, 2024
64bb843
Fix default acc in user
rijkvanzanten May 3, 2024
fcf7737
Replace use of permissions in utils
rijkvanzanten May 3, 2024
550cd73
Update reduceSchema in specs/gql
rijkvanzanten May 3, 2024
313303e
Remove old share merging
rijkvanzanten May 3, 2024
6697ee0
Remove empty file
rijkvanzanten May 3, 2024
b8ced45
Remove outdated comment
rijkvanzanten May 3, 2024
ea4f20a
Merge branch 'auditus' into auditus-21765
rijkvanzanten May 6, 2024
ff8c1e2
Use ctx objects for large param fns
rijkvanzanten May 6, 2024
34624b2
Add with-cache memoize util
rijkvanzanten May 7, 2024
2bd23b8
Add cache to fetchpermissions
rijkvanzanten May 7, 2024
c6866d3
Update caching use in fetchRolesTree
rijkvanzanten May 7, 2024
529c952
Merge branch 'auditus' into auditus-21765
rijkvanzanten May 7, 2024
bb59943
Add caching to fetchAllowedFieldMap
rijkvanzanten May 7, 2024
fe3be0b
Add more cache
rijkvanzanten May 7, 2024
3f85165
Refactor call signatures
rijkvanzanten May 8, 2024
0e803ee
Move call signature updates
rijkvanzanten May 8, 2024
5e6bbe4
Handle presets
rijkvanzanten May 8, 2024
c48cd48
Update process call sig
rijkvanzanten May 8, 2024
84d0db3
Prevent infinite recursion in roles tree lookup
rijkvanzanten May 8, 2024
d6053ff
Use create util for acc
rijkvanzanten May 8, 2024
22fd3eb
Merge branch 'auditus' into auditus-21765
rijkvanzanten May 8, 2024
99c6bb7
Remove old checkIp
rijkvanzanten May 8, 2024
830b459
Fix where equality operator
rijkvanzanten May 8, 2024
e995181
Break EVERYTHING!
rijkvanzanten May 20, 2024
f4345cf
Fix build
rijkvanzanten May 21, 2024
66ca624
Add missing module tests
rijkvanzanten May 21, 2024
5cb095a
Don't crash on missing parent
rijkvanzanten May 21, 2024
3f203d1
Fix role lookup
rijkvanzanten May 21, 2024
07b3e3e
add missing type annotation
DanielBiegler May 22, 2024
0315d45
use logical-OR assignment and avoid a memory allocation
DanielBiegler May 22, 2024
2ac82c0
Attach admin policy in default admin creation
rijkvanzanten May 23, 2024
63059b7
Merge branch 'auditus' into auditus-21765
rijkvanzanten May 23, 2024
67b484d
Fix admin check
rijkvanzanten May 23, 2024
8f3b70a
Add todo for later
rijkvanzanten May 23, 2024
7a5846b
rm code duplication
DanielBiegler May 23, 2024
b134129
fix test
DanielBiegler May 23, 2024
acf3f46
add types and fix type error
DanielBiegler May 23, 2024
b482ac6
move spread order to avoid potential future mishaps
DanielBiegler May 23, 2024
b77cba7
reduce allocations, add escape hatch to loop and type db-row
DanielBiegler May 23, 2024
910a394
Implement case/when
rijkvanzanten May 23, 2024
2292385
Clean up comments
rijkvanzanten May 23, 2024
1cd4bd6
Optimize perm fetching in allowed f
rijkvanzanten May 24, 2024
b1b943e
Move apply case when to util fn
rijkvanzanten May 24, 2024
3da77a3
Optimize fetch-allowed-fields
rijkvanzanten May 24, 2024
e38f970
Add fetch inconsistent util
rijkvanzanten May 24, 2024
e2af80b
Allow nulls
rijkvanzanten May 24, 2024
8352ea7
Remove obsolete getCacheKey
hanneskuettner May 27, 2024
42f2a0c
Remove unused import
hanneskuettner May 28, 2024
4a03e9d
Update getAccountabilityForRole test
hanneskuettner May 28, 2024
6c3cbb2
Update fetchGlobalAccess test with one more test case + fix other tes…
hanneskuettner May 28, 2024
186f92b
Type cleanup
hanneskuettner May 28, 2024
d90d7b3
Fix "admin access means automatic app access" in fetchGlobalAccessFor…
hanneskuettner May 28, 2024
dff4037
Clean up and expand fetch-inconsistent-field-map.test.ts
hanneskuettner May 28, 2024
cd032c2
Test uncached functions
hanneskuettner May 28, 2024
74f4b27
Merge branch 'auditus' into auditus-21765
rijkvanzanten May 28, 2024
63ec3a1
Test uncached
rijkvanzanten May 28, 2024
49f38f1
Remove cases usage in parse-current-level
rijkvanzanten May 28, 2024
9cf065f
Only consider non-null rules in inject cases
hanneskuettner May 30, 2024
5abb031
Fix parseCurrentLevel call
hanneskuettner May 30, 2024
b19db14
Move service imports into functions to avoid circular imports
hanneskuettner May 30, 2024
392fde9
Ensure that we test that an error is thrown in processAst test
hanneskuettner May 30, 2024
d2b77d2
Add failing test case for flattenFilter
hanneskuettner May 30, 2024
4c636f3
Ensure uniqueness in extractPathsFromQuery
hanneskuettner May 30, 2024
395d963
Early exit in validatePath
hanneskuettner May 30, 2024
8e91e58
Add additional test case for process payload test
hanneskuettner May 30, 2024
83d0e8b
Update validateCollectionAccess test
hanneskuettner May 30, 2024
00d80de
Clean up validate-item-access.test.ts
hanneskuettner May 30, 2024
939850e
Remove redundant initializer
hanneskuettner May 30, 2024
a673bd7
Use createDefaultAccountability
hanneskuettner May 31, 2024
5264a7d
Fix fetch-user-count.test.ts
hanneskuettner May 31, 2024
41d2379
Cleanup unused default initializer
hanneskuettner May 31, 2024
335a934
Add empty cases to subfilter in _relationCount
hanneskuettner May 31, 2024
6ff4e43
Drop AccessService and PermissionsService usage from services
hanneskuettner May 31, 2024
f6bb0ad
Found some more PermissionsServices
hanneskuettner May 31, 2024
b9fe874
Fix a few more tests
hanneskuettner May 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"build": "tsc --project tsconfig.prod.json && copyfiles \"src/**/*.{yaml,liquid}\" -u 1 dist",
"cli": "NODE_ENV=development SERVE_APP=false tsx src/cli/run.ts",
"dev": "NODE_ENV=development SERVE_APP=true tsx watch --ignore extensions --clear-screen=false src/start.ts",
"test": "vitest --watch=false"
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@authenio/samlify-node-xmllint": "2.0.0",
Expand Down
3 changes: 0 additions & 3 deletions api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import { checkIP } from './middleware/check-ip.js';
import cors from './middleware/cors.js';
import errorHandler from './middleware/error-handler.js';
import extractToken from './middleware/extract-token.js';
import getPermissions from './middleware/get-permissions.js';
import rateLimiterGlobal from './middleware/rate-limiter-global.js';
import rateLimiter from './middleware/rate-limiter-ip.js';
import sanitizeQuery from './middleware/sanitize-query.js';
Expand Down Expand Up @@ -267,8 +266,6 @@ export default async function createApp(): Promise<express.Application> {

app.use(schema);

app.use(getPermissions);

await emitter.emitInit('middlewares.after', { app });

await emitter.emitInit('routes.before', { app });
Expand Down
4 changes: 4 additions & 0 deletions api/src/auth/drivers/ldap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,10 @@ export function createLDAPAuthRouter(provider: string): Router {
const accountability: Accountability = {
ip: getIPFromReq(req),
role: null,
user: null,
roles: [],
admin: false,
app: false,
};

const userAgent = req.get('user-agent')?.substring(0, 1024);
Expand Down
4 changes: 4 additions & 0 deletions api/src/auth/drivers/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export function createLocalAuthRouter(provider: string): Router {
const accountability: Accountability = {
ip: getIPFromReq(req),
role: null,
user: null,
roles: [],
admin: false,
app: false,
rijkvanzanten marked this conversation as resolved.
Show resolved Hide resolved
};

const userAgent = req.get('user-agent')?.substring(0, 1024);
Expand Down
4 changes: 4 additions & 0 deletions api/src/auth/drivers/oauth2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,10 @@ export function createOAuth2AuthRouter(providerName: string): Router {
const accountability: Accountability = {
ip: getIPFromReq(req),
role: null,
user: null,
roles: [],
admin: false,
app: false,
};

const userAgent = req.get('user-agent')?.substring(0, 1024);
Expand Down
4 changes: 4 additions & 0 deletions api/src/auth/drivers/openid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ export function createOpenIDAuthRouter(providerName: string): Router {
const accountability: Accountability = {
ip: getIPFromReq(req),
role: null,
roles: [],
user: null,
admin: false,
app: false,
};

const userAgent = req.get('user-agent')?.substring(0, 1024);
Expand Down
16 changes: 16 additions & 0 deletions api/src/controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ router.post(
const accountability: Accountability = {
ip: getIPFromReq(req),
role: null,
roles: [],
user: null,
admin: false,
app: false,
};

const userAgent = req.get('user-agent')?.substring(0, 1024);
Expand Down Expand Up @@ -159,6 +163,10 @@ router.post(
const accountability: Accountability = {
ip: getIPFromReq(req),
role: null,
roles: [],
user: null,
admin: false,
app: false,
};

const userAgent = req.get('user-agent')?.substring(0, 1024);
Expand Down Expand Up @@ -206,6 +214,10 @@ router.post(
const accountability: Accountability = {
ip: getIPFromReq(req),
role: null,
roles: [],
admin: false,
app: false,
user: null,
};

const userAgent = req.get('user-agent')?.substring(0, 1024);
Expand Down Expand Up @@ -245,6 +257,10 @@ router.post(
const accountability: Accountability = {
ip: getIPFromReq(req),
role: null,
user: null,
roles: [],
admin: false,
app: false,
};

const userAgent = req.get('user-agent')?.substring(0, 1024);
Expand Down
67 changes: 1 addition & 66 deletions api/src/controllers/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import {
InvalidPayloadError,
isDirectusError,
} from '@directus/errors';
import type { PrimaryKey, Role } from '@directus/types';
import type { PrimaryKey } from '@directus/types';
import express from 'express';
import Joi from 'joi';
import { respond } from '../middleware/respond.js';
import useCollection from '../middleware/use-collection.js';
import { validateBatch } from '../middleware/validate-batch.js';
import { AuthenticationService } from '../services/authentication.js';
import { MetaService } from '../services/meta.js';
import { RolesService } from '../services/roles.js';
import { TFAService } from '../services/tfa.js';
import { UsersService } from '../services/users.js';
import asyncHandler from '../utils/async-handler.js';
Expand Down Expand Up @@ -375,38 +374,6 @@ router.post(
throw new InvalidPayloadError({ reason: `"otp" is required` });
}

// Override permissions only when enforce TFA is enabled in role
if (req.accountability.role) {
const rolesService = new RolesService({
schema: req.schema,
});

const role = (await rolesService.readOne(req.accountability.role)) as Role;

if (role && role.enforce_tfa) {
const existingPermission = await req.accountability.permissions?.find(
(p) => p.collection === 'directus_users' && p.action === 'update',
);

if (existingPermission) {
existingPermission.fields = ['tfa_secret'];
existingPermission.permissions = { id: { _eq: req.accountability.user } };
existingPermission.presets = null;
existingPermission.validation = null;
} else {
(req.accountability.permissions || (req.accountability.permissions = [])).push({
action: 'update',
collection: 'directus_users',
fields: ['tfa_secret'],
permissions: { id: { _eq: req.accountability.user } },
presets: null,
role: req.accountability.role,
validation: null,
});
}
}
}

const service = new TFAService({
accountability: req.accountability,
schema: req.schema,
Expand All @@ -430,38 +397,6 @@ router.post(
throw new InvalidPayloadError({ reason: `"otp" is required` });
}

// Override permissions only when enforce TFA is enabled in role
if (req.accountability.role) {
const rolesService = new RolesService({
schema: req.schema,
});

const role = (await rolesService.readOne(req.accountability.role)) as Role;

if (role && role.enforce_tfa) {
const existingPermission = await req.accountability.permissions?.find(
(p) => p.collection === 'directus_users' && p.action === 'update',
);

if (existingPermission) {
existingPermission.fields = ['tfa_secret'];
existingPermission.permissions = { id: { _eq: req.accountability.user } };
existingPermission.presets = null;
existingPermission.validation = null;
} else {
(req.accountability.permissions || (req.accountability.permissions = [])).push({
action: 'update',
collection: 'directus_users',
fields: ['tfa_secret'],
permissions: { id: { _eq: req.accountability.user } },
presets: null,
role: req.accountability.role,
validation: null,
});
}
}
}

const service = new TFAService({
accountability: req.accountability,
schema: req.schema,
Expand Down
98 changes: 98 additions & 0 deletions api/src/database/get-ast-from-query/get-ast-from-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Generate an AST based on a given collection and query
*/

import type { Accountability, Query, SchemaOverview } from '@directus/types';
import { cloneDeep, uniq } from 'lodash-es';
import { AccessService } from '../../services/access.js';
import { PermissionsService } from '../../services/index.js';
import type { AST } from '../../types/index.js';
import { parseFields } from './lib/parse-fields.js';

export async function getAstFromQuery(
accessService: AccessService,
permissionsService: PermissionsService,
collection: string,
query: Query,
schema: SchemaOverview,
accountability: Accountability | null,
): Promise<AST> {
query = cloneDeep(query);

const ast: AST = {
type: 'root',
name: collection,
query: query,
children: [],
cases: [],
};

let fields = ['*'];

if (query.fields) {
fields = query.fields;
}

/**
* When using aggregate functions, you can't have any other regular fields
* selected. This makes sure you never end up in a non-aggregate fields selection error
*/
if (Object.keys(query.aggregate || {}).length > 0) {
fields = [];
}

/**
* Similarly, when grouping on a specific field, you can't have other non-aggregated fields.
* The group query will override the fields query
*/
if (query.group) {
fields = query.group;
}

fields = uniq(fields);

const deep = query.deep || {};

// Prevent fields/deep from showing up in the query object in further use
delete query.fields;
delete query.deep;

if (!query.sort) {
// We'll default to the primary key for the standard sort output
let sortField = schema.collections[collection]!.primary;

// If a custom manual sort field is configured, use that
if (schema.collections[collection]?.sortField) {
sortField = schema.collections[collection]!.sortField as string;
}

// When group by is used, default to the first column provided in the group by clause
if (query.group?.[0]) {
sortField = query.group[0];
}

query.sort = [sortField];
}

// When no group by is supplied, but an aggregate function is used, only a single row will be
// returned. In those cases, we'll ignore the sort field altogether
if (query.aggregate && Object.keys(query.aggregate).length && !query.group?.[0]) {
delete query.sort;
}

ast.children = await parseFields(
{
parentCollection: collection,
fields,
query,
deep,
},
{ schema, accountability },
{
accessService,
permissionsService,
},
);

return ast;
}