Skip to content

Access rules

Every rule on the endpoint must allow. Rules run in orderIndex order. Evaluation stops at the first deny, and that rule’s reason is the 403 message. Later rules do not run. An allow does not skip later rules. Filters from rules that allow are merged: a later key overwrites an earlier key on the same field, and different fields combine as one Mongo object (implicit AND).

Bypass: platform JWTs, personal access tokens (kfx_…), and service tokens (kfxs_…) skip the rule engine. Except when the endpoint has requireServiceToken, which denies everything except a matching project service token. Project user JWTs do not bypass.

Empty rules: if the endpoint has no rules, the request is allowed. If STRICT_ACCESS_RULES is the string true and project auth is enabled, the request is denied with No access rules configured for this endpoint. Strict mode does not deny when auth is off. Hosted Tefily may enforce strict rules: always add rules on user-facing endpoints.

requireAuth only accepts a project-scoped JWT for that project. A platform token does not satisfy requireAuth; it bypasses the engine entirely instead.

Rejects unauthenticated project-user requests. Config is empty.

Deny messages include Authentication required, A project-scoped token is required, Token does not belong to this project, and Authentication is not enabled for this project.

Allows only a Tefily service token (kfxs_…) for this project. Denies unauthenticated callers, project JWTs, platform JWTs, and personal access tokens. Config is empty. Project auth does not need to be enabled.

Use this on endpoints that should only be called from your backend. See the server-only example below.

Compares a field on the _users document to a static value or a field on the record. The user document is _users looked up by claims.sub. A missing user is Authenticated user not found.

Owner rows (compare the logged-in user’s _id to authorId on the record):

{
"ruleType": "userFieldCompare",
"ruleConfig": {
"userField": "_id",
"operator": "eq",
"compareWith": "record",
"recordField": "authorId"
}
}

On list, this becomes a Mongo filter so users only receive their rows. The filter is AND-ed with the caller’s ?filter=. On single-document get, PUT, and DELETE, the stored row is checked. Bulk PATCH is different: the update rules run as that list filter, then again as an update against the patch body, with no stored document. A patch that omits authorId fails the second pass. See Endpoints and the posts example below.

Role gate (static value):

{
"ruleType": "userFieldCompare",
"ruleConfig": {
"userField": "role",
"operator": "eq",
"compareWith": "value",
"value": "admin"
}
}

compareWith: "value" compares the _users field to value. It does not add a Mongo filter.

Operators: eq, neq, gt, gte, lt, lte, in, contains. Send neq. compareWith is required: "record" needs recordField, "value" needs value.

On list, compareWith: "record" compares the user field to the record field. eq stores { "<recordField>": <user value> } and does not invert. gt means the user field is greater than the record field, so the list filter stores $lt on recordField. The same inversion applies to gte ($lte), lt ($gt), and lte ($gte). neq becomes $ne. in becomes $in when the user value is a list, and equality otherwise. contains matches an array element equal to the user value ($elemMatch); a null user value denies.

For create and update, the named body field must be present. Operator in also checks values. Any other operator only checks that the field is present. The rule allows immediately on list, get, and delete.

{
"ruleType": "fieldInBody",
"ruleConfig": {
"field": "status",
"operator": "in",
"values": ["draft", "published"]
}
}

Used on upload endpoints. On any other call, file metadata is missing and these rules deny. Put them on upload only. See Files.

Type Config
fileSizeLimit { "maxBytes": 10485760 }
fileMimeType { "allow": ["image/jpeg", "image/*"] }
fileExtensionAllow { "extensions": [".png", ".jpg"] }

Rules are per endpoint. Copy this pair onto list, get, update, and delete, with orderIndex 0 then 1. A list rule does not cover get.

{ "ruleType": "requireAuth", "ruleConfig": {} }
{
"ruleType": "userFieldCompare",
"ruleConfig": {
"userField": "_id",
"operator": "eq",
"compareWith": "record",
"recordField": "authorId"
}
}
  • list: the caller sends a project JWT. The engine adds { "authorId": "<user id>" } and combines it with ?filter= as { "$and": [ ruleFilter, userFilter ] }.
  • get / PUT / DELETE: the stored row’s authorId must equal the user’s _id, or the response is 403 User field "_id" does not match record field "authorId".
  • create: put the same pair on the create endpoint when new rows must belong to the caller. There is no stored row, so the body authorId must equal the user’s _id. The server does not fill authorId.
{ "title": "Hello", "authorId": "<same as user _id>" }
  • PATCH updateMany: the list pass adds the owner filter, then the update pass compares the patch. { "status": "archived" } returns 403 because authorId is absent on the body. The working patch includes authorId as well as the fields being changed:
{ "status": "archived", "authorId": "<user _id>" }
  • DELETE many: only the list-mode pass runs, so the owner filter scopes the delete. The SDKs still require a where.

On each method that only your backend should call, a single rule:

{ "ruleType": "requireServiceToken", "ruleConfig": {} }

Project auth does not need to be enabled. A missing token, a project JWT, a platform JWT, or a PAT is denied at the gate with A service token is required. A service token for another project is Service token is not allowed for this project.

A matching kfxs_… is allowed and then bypasses any further rules on that endpoint.

An empty rule list does not mean service-token-only. Add this rule. When auth is on, strict mode on an empty endpoint denies everyone, including service tokens.