View of data specific to the Authenticated User

Earlier, I was using FQL through JS Client, was short of time and implemented user specific data in the following way in my JS component:

  1. Add a “userid” field to the object Schema of collection
  2. On login, the component obtains and keeps the userid (auth.instance.id)
  3. When writing objects from the component, always adds this userid property (not visible to the actual App)
  4. While making index, uses this userid in the Term expression so that only user’s objects are obtained from that index

I had to go this way as I was short of time. But I don’t like the client component being aware of the userid internal and making use of it. It’s a security issue too. The ideal solution will be if the server can automatically deliver objects only belonging to the authenticated user by some trick which can be a combination of UDF, index, etc. The client side App should not even be aware of this processing.

I want to do this in GraphQL. Is that kind of entirely server based solution possible that gives the client a view of authenticated user’s data only? You already have a nice user authentication which is totally server based and configurable as far as collection name,encrypted field names are concerned. I wonder if there is a feature of user specific view too.

Thanks.

Hi @skanade,

If I understand correctly what you are trying to achieve, you can use ABAC and create a Role that allows users to read just their own data from collections.

Since you add the userID (hopefully it is the user Ref()), you can match it with Identity(), and only documents that match will be returned.

You can take a look here to see how ABAC/Roles work.

Luigi Servini

I understand roles and how they can work for security. But I want the userid to be added to the collection and filtered entirely on the server side based on the authenticated user. Client should not be required to send userid or do anything with the userid.

For example, when I add an object from client, the userid property should be added to it on the server end by some trick. Then when I get the list of objects, again server logic should filter objects based on authenticated userid. Is there some example where I can see how it is done?

Thanks.

When you create a new document using FQL you can create this way:

{
.
.
userid: Identity(),
.
.
}

The function is resolved server-side and the client just pass the function.
Might work that way?

Luigi

Unfortunately, I must use GraphQL. So it must be defined in the Schema which is not possible as identity. I will need to stuff it in the client as a string. For the list, I can define a parameterized index in the schema to get only the records with this userid. But this makes client use the userid which I wanted to avoid. I will wait for more ideas to see if it can be handled at the server entirely transparent to the client.

Thanks.

@skanade, I think you are missing something fundamental about Fauna, and also how the GraphQL server wraps around it.

You specify the types in relationships in your GraphQL schema, then Fauna generates mutations and queries that work with an actual Ref stored on the document. The ID values are not saved in the documents.

You will enter the id in graphql mutations and queries, but that get’s directly translated into a Ref, e.g. Ref(Collection('User'), '1234'), which is what you will see in the documents if you insprect them after creating something with GraphQL relation mutations.

If you want to update or create a document with FQL, and store a reference to the current user, then use Identity() inside of the query. If you need this functionality in GraphQL, use a UDF, and @resolver in your GraphQL schema. This should be possible because the javascript driver should be using a client that is using the user token from calling Login.

This is a part of ABAC/Roles that @Luigi_Servini was pointing you to. If you go through Fauna docs on ABAC and roles, you should understand what the Identity function does, where it is used, and why. Or at least be closer to it. So, please, do not dismiss that advise that was given.

example schema

Maybe you will find this helpful.

You can use a schema like the following to utilize UDFs in GraphQL that will assign the current user as owner of a document.

Note that User type is specified for Todo's owner field, not an ID.

type User {
  email: String! @unique
  todos: [Todo] @relation
}

type Todo {
  title: String!
  completed: Boolean!
  owner: User
}

input UserTodoInput {
  title: String!
  completed: Boolean!
}

input LoginInput {
  email: String!
  password: String!
}

type Query {
  me: User! @resolver(name: "me")
}

type Mutation {
  login(data: LoginInput): String! @resolver(name: "login")
  logout: Boolean! @resolver(name: "logout") @resolver(name: "logout")
  userCreateTodo(data: UserTodoInput): Todo! @resolver(name: "user_create_todo")
}

query to update the UDF after schema is uploaded. Note the use of Identity

q.Update(q.Function('user_create_todo'), {
  body: q.Query(
    q.Lambda(
      ['data'],
      q.Create(q.Collection('Todo'), {
        data: q.Merge(
          { completed: false, owner: q.Identity() },
          q.Var('data')
        )
      })
    )
  )
})

Code was taken from here:

Thanks. That’s really helpful. I already made a solution similar to this with a UDF. However, I chose to put only the id string in the field by Select(“id”, Identity()). Also I used Update and learned the Merge option from your solution.

However, what I’m not able to figure out a UDF for the query to get a list of objects where again the Identity() is used inside the UDF itself instead of supplying UserId as a parameter. I’m still trying with some pagination function samples.

Thanks.

Ok. Be aware that with GraphQL, the relationships will only be automatically expanded if the document contains the entire Ref.

querying user owned data directly

Map(
   // Paginate(Match(Index('things_by_user'), Identity())),
   Paginate(Match(Index('things_by_userid'), Select('id', Identity()))),
   Lambda(['ref'], Get(Var('ref'))
)

For GraphQL

If relations are set up in GraphQL so that a user has many of a thing (in my examle, Todo's), then you can query the user, then expand the list of things. And if you have a UDF that returns the current User then you can use that. For example:

query {
  me: {
    _id
    name
    email
    todos {
      data {
        _id
        title
        completed
      }
    }
  }
}

This is a very canonical way of writing GraphQL queries so that the data can be cached appropriately.

For writing, I used a UDF similar to what is suggested in the answer marked as Solution above. The main point here is that the GraphQL input supplied to that function does not need to give a userid as the UDF stuffs it itself from the Identity()'s id. That means, the Client App does not change and doesn’t send any userid.

For reading the list, I made up my own solution which is basically a UDF that is quite similar to the one I gave in my question. The only difference is that it uses an Index with a term data.userid and supplies the userid by Identity()'s id to the Match function for the index. Moreover, since I did an update schema, using the same name for the new query Resolver as created earlier by default by FaunaDB for getting all objects, the client app worked without changing even one line of code. Everything happens at the Server as originally desired.

Thanks to ptpaterson for providing the ideas and the sample code.

@ptpaterson @skanade Thanks for this thread, I’ve found it useful. I do have some questions that I would love to get your thoughts on.

I’m wondering if we will run into performance and functionality issues by embedding todos under me. Since todos are a part of me, does that mean that all todos need to be fetched from the database when me is queried? The majority of the collections in my app belong to a user so I will have a lot of related collections inside of me. Do they all have to get loaded when me is queried?

Also, does this pose any issues with pagination? For example, if I have 5000 todos, is there a way to add pagination to the todos property? e.g

    {
      me {
         todos(_cursor: 2) {...}
      }
    }

Thank you for the help.

Fauna turns relations into paginated queries. You can specify size and cursor ID.

Check out the GraphQL playground and see that, for example with the schema I shared the todos field type is changed to TodoPage and the field has arguments for _size and _cursor.

At least in my design, the collection of objects is not under the User object. But rather the collection is segmented by “userid” (_id of User object). So they are separate collections and loading one would not load the other unless you load explicitly through a UDF.

In order to get the objects of this user only, your query will have an argument of userid that will be used as a term. So it’s effectively searching an index on userid.

Further when you define the queries that get the objects, either all or by some filter (term), by default, they are paginated. FaunaDB would send only 50 objects at a time. You can change the _size argument. You can also use the _cursor argument in the query to get subsequent pages or even step back when needed. If you search FaunaDB docs on _cursor, you will get some examples.

For one-to-many relations via GraphQL nothing is stored on the “one” side. Rather the “many” side collection stores a Ref for the relationship.

Like @skanade said, the “many” related docs are queries via an index. The index term can actually be a Ref, and not limited to the ID.

For good caching in a GraphQL client, having a common entry point for your user-related queries can be very helpful, and comes as advise straight from Github and Facebook devs.

Docs describe how the relationships are created and queries in the background.

If your only using FQL, then you have more options of course. Plenty of ways to skin the cat :slight_smile:

In my experience, it is better to store the Ref than just the ID, even outside of GraphQL. It’s of course required if the relationships are not all to the same Collection. More in general, it reduces the complexity of queries. That is, you can use the Ref directly rather than having to recreate it from an ID, and also lets you use the Refs directly client side.

@skanade @ptpaterson thanks for your responses.

@ptpaterson I have seen that pagination is done automatically when you define a relation in graphql. However, in the case of the me query, since it is being defined via UDF, I was under the impression that pagination of todos would not be built in. Does that make sense?

If you return a Page from a UDF, you should set pagination: true in @resolver.

If your UDF does not resolve into a Page, either because it’s a single object, or you Select('data',..., or otherwise return something not a Page, then the default is no pagination.

Be careful that your UDF specifies a large enough size so that it doesn’t drop any. default is 64. Max is 100000.

docs:

Some topics that might be helpful:

Thanks for the tip. How do you define an index on Ref when it comes to filter by User?

Same as you would any other index.

if you had a userRef field on a Collection, index that collection with userRef as the term.

@ptpaterson in this case the UDF for the me query would return a single object representing the current user but the user object will contain an array of todos. Since there is just one @resolver, I’m not sure where we would provide the todos _cursor for the next page.

e.g the ideal query would look like

{
    me {
       todos(_cursor: 2) {...}
    }
}

but since there is only one input argument that is passed to the UDF, I’m assuming the query will have to look something like:

{
    me(_todosCursor: 2) {
       todos {...}
    }
}

Is it possible to build the first schema?

Relationships are paged. If you query a user first, then expand the relationship, you can use paginated arguments.

If you have a UDF which returns a user (from Identity or otherwise) you can expand the user query like any other built in one that doesn’t use UDFs.