Limiting privileges for user tokens

Hi,

I noticed a problem in his tests today.

I have a collection called User and I make users login through this collection. Logged in users have the User role that I created and authorized. After login, a secret is created and in this way the user can do the operations defined through the database. For example, he can edit his profile.

The problem is that if the user calls built-in functions with this secret, he can access all the documents in all authorized collections.

For example, I tested the following query on the dashboard and with the secret. All users’ information came up.

Map(Paginate(Documents(Collection(“User”))), Lambda(“x”, Get(Var(“x”))))

I think this is a very big security vulnerability. Fauna should not allow such a query via secret. It is probably possible to prevent this with predicate conditions. But I think Fauna should block it by default to prevent inexperienced users from creating such a security risk.

What do you think about this situation, am I missing something?

I would like to know how I can eliminate this risk.

Thank you

Hi @hasimyerlikaya. Tokens use Fauna’s Attribute-based Access Controls (ABAC) and start off with zero permissions until you explicitly provide them. If you can use the token to read the entire collection, then you need to review your Roles assigned to that token and update them to provide more limited privileges.

A couple of notes:

  • Tokens are granted Roles through the Role’s membership. It is possible that your token is assigned multiple Roles where one has higher privileges than you intend. You may need to update your Role’s membership predicate to make sure certain users do not have the higher privilege.
  • Privileges for a Collection apply to all documents in that collection, unless you provide a predicate that limits which documents the privilege applies to. For example, you could restrict a user from only reading and writing their own document, but you have to be explicit about that.
  • The v4 dashboard has a known issue where the “RUN QUERY AS” secret may not used when you execute your query using cmd+enter/ctrl+enter. Make sure you are running using your token by clicking the “RUN QUERY AS” button with your mouse.

Here is an example with some roles demonstrating the predicates for membership and privileges, using the v10 API’s Fauna Schema Language (FSL).

role LoggedIn {
  membership User

  privileges User {
    read {
      // can only read to your own document
      predicate ((doc) => doc == Query.identity())
    }
    write {
      // can only write to your own document
      predicate ((old, _new) => old == Query.identity())
    }
  }

  privileges Todo {
    read {
      // can only read to your owned documents
      predicate ((doc) => doc == Query.identity())
    }
    write {
      // can only write to your owned document, and cannot change the owner
      predicate ((old, new) => 
        old.owner == Query.identity() && old.owner == new.owner)
    }
  }
}
role Admin {
  membership User {
    // Only tokens for users with this field set to true will have this Role
    predicate ((user) => user.admin == true)
  }

  privileges User {
    read
    write
  }

  privileges Todo {
    read
    write
  }
}

Thanks for the detailed answer. The token belongs to a single role and I tested it by clicking the button.

It is precisely the second point you made that I also find wrong. We allow a role to read a collection. Because each user should be able to read their own files. But as you said, the token works in a way that it can access all documents. If we don’t apply any restrictions in the role settings, this is a huge security vulnerability.

Especially new Fauna users may not pay attention to this detail.

I would like the token to be authorized only for documents created by the user. If we wanted to access all documents in a collection, we could do a transaction for this or we could already do this with the admin key.

Now we need to add restrictions for each transaction for all indexes and collections that the role is authorized for. This is a huge waste of effort. I think that by default users should only access their own documents.

By adding a control like this, I can solve the problem. But I am worried that this will increase the number of queries and the cost will increase.

Will the control functions we add in this way increase the cost of our queries?

Fauna cannot know which documents are “owned” by any given token. You have to define that relationship yourself. This is intentional so that you have control over the permissions.

The account document you use to log in – that is, the one which you provide credentials for (see also v4 docs) – doesn’t say anything about what the resulting token should be able to read. Consider a common use case where the account document doesn’t contain any public information, not even public to the subject user. It’s common to put admin information in there, for example:

  • what plan they are subscribed to,
  • when they joined,
  • team role,
  • meta data from oauth signup
  • IP addresses

You would then have a separate collection with different permissions for public profile information. You could, of course, swap these permissions: provide read/write access to the account document and store admin information in a separate document that requires heightened privileges. It’s up to you and depends on what is best for your use case.

Cost

Any FQL you put in your role predicates is charged like any other FQL as part of your query. See our billing docs for additional information.

We can calculate the extra cost for your provided predicate, Equals(CurrentIdentity(), Var("ref")):

  • two FQL functions are called: 2/50ths of one TCO
  • no read operations are performed, so zero TROs are charged.
  • (equivalent v10 predicate is (doc) => doc == Query.identity() and costs the same.

You can perform read operations in role predicates, and those would be charged.

I gave the example of checking for admin membership: predicate ((user) => user.admin == true). This would require one (1) TRO in order to fetch the token’s identity and read the admin field.

In another example, you might need to look up if a user is part of an organization and has a particular role in a multi-tenant application. This is less common but sometimes needed.

role TeamMember {
  membership User

  privileges SalesOrder {
    create {
      predicate ((newSalesOrder) => {
        // requires an index read (~1 TRO)
        let teamMembership = Team.byUser(Query.identity()).first()!

        // requires a document read (~1 TRO)
        teamMembership.organizations[newSalesOrder.organization].roles.include("sales")
      })
    }
  }
}

This will only be charged if Fauna has to execute the predicate to resolve the required permissions. For example, if a query is not creating a SalesOrder, then the predicate would not be run.

1 Like

I do recommend migrating to v10 Indexes and roles in this case. Since Indexes are defined within a Collection in v10, there is no distinction between roles to read an Index vs reading a Collection (in v4 an Index could be defined with multiple source Collections). You can save some duplication effort in this way.

I might also recommend considering encapsulating work into UDFs. Those UDFs can have simpler permissions, but the token can be restricted from calling those UDFs with only their own document.

// FSL
@role("updateUser")
function readUser(id, params) {
  User.byId(id)!.update(params)
}

role updateUser {
  privileges User {
    write
  }
}

// other functions

role LoggedIn {
  membership User

  privileges updateUser {
    call {
      // No read ops necessary. This is only comparing references
      predicate ((id, params) => Query.identity() == User.byId(id))
    }
  }

  // call permissions for other UDFs
}


I love Fauna’s flexibility, that’s one of the reasons I prefer it. Of course, like every good thing, it comes at a price. You have to be very careful, in terms of security. For example, I shared the first version of the app a long time ago and I was not aware of such a risk of using tokens. Fortunately, I am revamping the whole app and I will eliminate this risk now.

Honestly, it scares me that all queries of fauna are charged. I have no idea what kind of bill I’m going to get, even dashboard queries are charged. Now I learned that authorization queries will also be charged. I wish these were free of charge, because they are related to settings and it is not possible to use Fauna without them.

It is not possible for me to switch to v10 right now. I am developing a Flutter application and Fauna has no official driver support. There is a package for the old version FQL and I use that.

I hope that Fauna will support Dart as soon as possible, I think it’s a big missing link. It’s a great database, it can replace Firebase. But since there is no Dart driver, unfortunately its usage is limited.
It will be to Fauna’s advantage that Flutter and Dart dominate the mobile space day by day. Maybe you can convey this to someone :slight_smile:

Thank you very much for your help. I will write authorization functions in the necessary places and solve this problem for now. Maybe I will have the opportunity to apply a more effective method in the transition to the new version later.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.