Access Control

Due to the permissionless nature of decentralized databases, it is essential to have control over who can interact with your database. WeaveDB has a powerful mechanism to precisely set up any advanced logic to your database instance by combining JsonLogic (opens in a new tab) and FPJSON (opens in a new tab)

Access control rules are as important as the schemas for decentralized database. WeaveDB rules are extremely powerful using JsonLogic (opens in a new tab) with an original add-on that enables JSON-based ramda (opens in a new tab) functional programming.

Add rules to a collection:

const rules = {
  let: {
    id: [
      "join",
      ":",
      [
        { var: "resource.newData.article_id" },
        { var: "resource.newData.user_address" },
      ],
    ],
  },
  "allow create": {
    and: [
      { "!=": [{ var: "request.auth.signer" }, null] },
      {
        "==": [{ var: "resource.id" }, { var: "id" }],
      },
      {
        "==": [
          { var: "request.auth.signer" },
          { var: "resource.newData.user_address" },
        ],
      },
      {
        "==": [
          { var: "request.block.timestamp" },
          { var: "resource.newData.date" },
        ],
      },
    ],
  },
  "allow delete": {
    "!=": [
      { var: "request.auth.signer" },
      { var: "resource.newData.user_address" },
    ],
  },
}
await db.setRules(rules, "bookmarks")
💡

In the following sample code, db represents the state variable of your database instance. For reference, see Initialize WeaveDB

Within the rules object, each top level key defines one rule. The keys should be a combination of (allow or deny) and (write or create or update or delete).

You can set up rules to either the entire write operation with write or specific operations such as create, update and delete.

allow write is equivalent to allow create,update,delete

So write = create + update + delete

Within the rules, you can access various information about the contract, block, transaction, and data to be uploaded.

{
  contract: { id, owners },
  request: {
    auth: { signer, relayer, jobID, extra },
    block: { height, timestamp },
    transaction: { id },
    resource: { data },
    id,
    path,
  },
  resource: { data, setter, newData, id, path },
}

Preset Variables

You can access various data within the validation blocks.

const data = {
  contract: { id, owners },
  request: {
    auth: { signer, relayer, jobID, extra },
    block: { height, timestamp },
    transaction: { id },
    resource: { data },
    id,
    path,
  },
  resource: { data, setter, newData, id, path },
}

And with JsonLogic, you can use var to access variables, such as {var: "resource.newData.user"} to access the user field of the newly updated data.

resource.setter is the data creator. The following ensures only the original data creators can update their own data:

To combine multiple operations, chain them with , like allow create,update

💡

signer will always be lowercase if it's an EVM wallet address. WeaveDB contract converts everything to lowercase to use them internally.

In regard to Arweave wallet addresses, the case will be retained, because it's RSA hashes and the case matters.

contract

  • id : contractTxId
  • owners : contract owners, equivalent to getOwner()

request

  • auth : signer of the query, and relayer info (relayer / jobID / extra)
  • block : block info
  • transaction : transaction info
  • resource : data in the query
  • id : doc id
  • path : collection / doc path

resource

  • data : data before this query
  • newData : data after this query
  • id : doc id
  • path : collection / doc path
  • setter : original creator of the doc

Modify Updated Data

You can amend the updated data before it's stored by modifying newData in access control rules.

For example always add signer address field as address field.

const rules = {
  let : { "resource.newData.address" : { var: "request.auth.signer" } },
  "allow create" : true
}
await db.setRules(rules, "people", { ar : arweave_wallet })

If you set { name : "Bob"} with wallet 0xABC, the stored data will be { name : "Bob", address : "0xABC" }.

await db.set({ name : "Bob" }, "people", "Bob")
expect(await db.get("people", "Bob")).to.eql({name : "Bob", address: "0xABC" }) // true

Get Other Data

ℹ️

Feature added in contract version 0.28.15

You can also execute get query in let variable assignments. Use the syntax ["get", [QUERY]].

The following will first get Bob from ppl collection and assign it to user, then set age field with the user's age.

const rules = {
  "let create" : { "user" : ["get", ["ppl", "Bob"]], "resource.newData.age": {var: "user.age"} },
  "allow create" : true
}

Conditional Statements

ℹ️

Feature added in contract version 0.28.16

if and ifelse can be used for conditional statements in access control rules.

// assigning age=20 if the name is Bob
const rules = {
  "let create" : { 
    "resource.newData.age" : ["if", ["equals", "Bob", {var: "resource.newData.name"}], 20] 
  },
  "allow create" : true
}
 
// assigning age=20 if the name is Bob, and age=30 otherwise
const rules = {
  "let create" : { 
    "resource.newData.age" : ["ifelse", ["equals", "Bob", {var: "resource.newData.name"}], 20, 30] 
  },
  "allow create" : true
}

Renaming Method

ℹ️

Feature added in contract version 0.28.16

You can rename request.method to something other than create / update / delete and assign rules to the renamed method for beter branching.

// rename method to create_bob if the name is Bob, and only allow create_bob
const rules = {
  "let create" : { 
    "request.method" : ["if", ["equals", "Bob", {var: "resource.newData.name"}], "create_bob"] 
  },
  "allow create_bob" : true
}

Add-on: JSON-based Functional Programming

Javascript functions cannot be passed and stored with Warp contracts. So WeaveDB invented a powerful & simple way to do functional programming using JSON objects. You can use most of the ramda (opens in a new tab) functions, which enables highly complex logics for access controls and data validations.

Within the rules object, you can define variables under let key and later use them within allow/deny validation blocks.

💡

By default, let will execute on create, update, and delete SDK operation. You can specify which operation/s you want let to be executed.

"let create": { } will execute only when creating a new document from the collection.

"let create,update": { } will execute only when creating and updating a document from the collection.

const rules = {
  let: {
    id: [
      "join",
      ":",
      [
        { var: "resource.newData.article_id" },
        { var: "resource.newData.user_address" },
      ],
    ],
  },
  ...
}

For example, above is equivalent to

const id = join(":", [ resource.newData.article_id, resource.newData.user_address ])

and later forcing doc ids to be article_id:user_address with JsonLogic.

{
  "==": [{ var: "resource.id" }, { var: "id" }],
}