Multi user access control based on ownership of data AND admin role with neo4j-graphql-js

Hi, I'm fairly new to using Neo4j and the companion lib to build GQL APIs, I do love the concept and how easy it is to define complex queries right in the schema.

However I've been searching for a long time now, how to implement a fairly common pattern in web apps, where your users have their own data and shouldn't be able to access other users data, but you also have admin users who can access everything.

For example, let's say we have an e-commerce dashboard where people can manage their orders, products, etc... Each user has their own dashboard with only their own data. In addition to that, I want to have a global admin, that can for example give statistics about all data across users.

I found I can achieve the user owned data part with @additionalLabels, that works great, but the problem is now I can't give access to everything to an admin user. The only way I see is rewriting every query and mutation as a cypher query that ignores labels. That's not ideal.

I'm also not sure how to write @cypher queries that respect this labeling, for example

type Category @additionalLabels(labels: ["user_<%= $cypherParams.userId %>"]) {
        categoryID: ID! @id
        categoryName: String
        description: String
        grossProfit: Float! @cypher(statement: """
        MATCH (this)<-[:PART_OF]-(p:Product)<-[r:ORDERS]-(o:Order) 
        WHERE 'user_' + $cypherParams.userId IN labels(p) AND 'user_' + $cypherParams.userId IN labels(o) 
        RETURN SUM(r.quantity * toFloat(r.unitPrice))
        """)
    }

For some reason, this always returns 0, I am sure that $cypherParams.userId is set to 5 in my context and the label on my nodes is user_5, and the query works fine without the WHERE clause.

I'm also able to make this query work in neo4j browser if I replace $cypherParams.userId with 5, then it gives me the correct result.

EDIT: I found out that when you pass the number 5 as param it is converted to 5.0 as a string by default so the solution is to do toInteger($cypherParams.userId) instead.

So I'm at a point where my user's data is correctly segmented and accessible only to their owners but now I don't know how to bypass it for users that have admin roles.

Is there a way to achieve this kind of access control with neo4j-graphql-js ?
And if not, is there a way to create custom directives to achieve this ?

Welcome to the community!

In the GRANDstack docs, you'll see options for role based access control. It's baked into the authorization directives. https://grandstack.io/docs/neo4j-graphql-js-middleware-authorization#hasrole

Give that a shot.

Thanks, yes, as I said I've been searching for a long time, so I know there is role based and scope based directives. However, what I need is owner based, or property based auth and/or role based.

Meaning an admin can access everything regardless of the owner based authorization. While a user is restricted by the owner based authorization.

Basically what I need is resource.userId === currentUser.id || currentUser.role === 'admin'

I've been able to do the ownership part with @additionalLabels, but this doesn't allow me to say an admin can bypass that ownership restriction. Because if I add @hasRoles(roles: [admin]) then only an admin can access the resource and they still will only get data they own, which would be nothing since admins don't create resources, the users do.

Have you looked at the fine grained access control? https://neo4j.com/docs/operations-manual/current/authentication-authorization/access-control/