Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit e4b79d0

Browse files
ymc9Copilot
andauthored
feat: field-level access control (#557)
* WIP * WIP: implement read policies * fix tests * Update packages/plugins/policy/src/policy-handler.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix name mapper * fix tests * fix build * implement update field-level policies * update tests * update tests * add more tests * add more tests * simplify queries --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 20f0777 commit e4b79d0

File tree

12 files changed

+3237
-160
lines changed

12 files changed

+3237
-160
lines changed

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
- [x] JSDoc for CRUD methods
7878
- [x] Cache validation schemas
7979
- [x] Compound ID
80-
- [ ] Cross field comparison
8180
- [x] Many-to-many relation
8281
- [x] Self relation
8382
- [ ] Empty AND/OR/NOT behavior
@@ -101,6 +100,7 @@
101100
- [x] Validation
102101
- [ ] Access Policy
103102
- [ ] Short-circuit pre-create check for scalar-field only policies
103+
- [x] Field-level policies
104104
- [x] Inject "on conflict do update"
105105
- [x] `check` function
106106
- [ ] Custom functions

packages/language/src/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ export function isRelationshipField(field: DataField) {
149149
return isDataModel(field.type.reference?.ref);
150150
}
151151

152+
/**
153+
* Returns if the given field is a computed field.
154+
*/
155+
export function isComputedField(field: DataField) {
156+
return hasAttribute(field, '@computed');
157+
}
158+
152159
export function isDelegateModel(node: AstNode) {
153160
return isDataModel(node) && hasAttribute(node, '@@delegate');
154161
}

packages/language/src/validators/attribute-application-validator.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
isAuthOrAuthMemberAccess,
3434
isBeforeInvocation,
3535
isCollectionPredicate,
36+
isComputedField,
3637
isDataFieldReference,
3738
isDelegateModel,
3839
isRelationshipField,
@@ -261,23 +262,22 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
261262
});
262263
return;
263264
}
264-
const kindItems = this.validatePolicyKinds(kind, ['read', 'update', 'all'], attr, accept);
265+
this.validatePolicyKinds(kind, ['read', 'update', 'all'], attr, accept);
265266

266267
const expr = attr.args[1]?.value;
267268
if (expr && AstUtils.streamAst(expr).some((node) => isBeforeInvocation(node))) {
268-
accept('error', `"before()" is not allowed in field-level policy rules`, { node: expr });
269+
accept('error', `"before()" is not allowed in field-level policies`, { node: expr });
269270
}
270271

271-
// 'update' rules are not allowed for relation fields
272-
if (kindItems.includes('update') || kindItems.includes('all')) {
273-
const field = attr.$container as DataField;
274-
if (isRelationshipField(field)) {
275-
accept(
276-
'error',
277-
`Field-level policy rules with "update" or "all" kind are not allowed for relation fields. Put rules on foreign-key fields instead.`,
278-
{ node: attr },
279-
);
280-
}
272+
// relation fields are not allowed
273+
const field = attr.$container as DataField;
274+
275+
if (isRelationshipField(field)) {
276+
accept('error', `Field-level policies are not allowed for relation fields.`, { node: attr });
277+
}
278+
279+
if (isComputedField(field)) {
280+
accept('error', `Field-level policies are not allowed for computed fields.`, { node: attr });
281281
}
282282
}
283283

0 commit comments

Comments
 (0)