objection-graphql icon indicating copy to clipboard operation
objection-graphql copied to clipboard

How to implement access control?

Open ionizer opened this issue 4 years ago • 2 comments

Hi there!

I am trying to implement a GraphQL server with Objection.js to handle database queries, and of course this library to build the GraphQL schema. And for security measures, some access control needs to be implemented, as it is undesirable to have let's say a user querying another user's data in Users table. Assuming there are common (normal user privilege) and admin user roles in the table, the access controls should be implemented is as follows:

  1. Unauthenticated users cannot query from the table
  2. Authenticated (common) users can only query their own info
  3. Authenticated (admin) users can query all user info

How can we achieve such access control with this library?

Thanks in advance!

ionizer avatar Nov 14 '19 12:11 ionizer

AHH! I'm terribly sorry, but I only just saw #56 and the access control example folder. But, I still don't understand what exactly needs to be done here to achieve such access control described above. But from what I understand, the objectives (according to #56) are:

  1. Pass context into graphqlHTTP's rootValue argument with onQuery function
  2. Create a custom model template class by extending Model and implement new abstract methods where it will be overridden by actual models (in this case, User model)
  3. Override the QueryBuilder of the custom model to use the new abstract methods which are used to control the query and result

Is this correct, or is there a better solution to access control?

ionizer avatar Nov 14 '19 12:11 ionizer

I finally managed to figure something out myself:

// app.js - Main file, graphql route
app.use('/graphql', isLoggedIn, (req, res, next) => graphql({
  schema,
  context: loggedUser,
  graphiql: true,
  rootValue: {
    onQuery: async (qb) => {
      await qb.mergeContext({ loggedUser, isGraphQLQuery: true });
    },
  },
})(req, res, next));

// user.js - Model file
const { Model } = require('objection');

class User extends Model {
  static get tableName() {
    return 'user';
  }

  static get jsonSchema() {
    return {
      type: 'object',
      required: ['name'],
      properties: {
        id: { type: 'integer' },
        name: { type: 'string' },
      },
    };
  }

  static query(...args) {
    const query = super.query(...args);

    return query.onBuild((qb) => {
      const ctx = qb.context();
      const { loggedUser: user, isGraphQLQuery } = ctx;

      if (user.id !== 1 && isGraphQLQuery) return qb.where('id', user.id);
      return qb;
    });
  }
}

module.exports = User;

What this basically does is if the authenticated user's id is not 1 (let's say this one is an admin), then the user cannot query for the other user info.

Though, I really feel this is a really hacky solution as this adds another AND user.id = ? to the WHERE clause resulting in something like this when querying {user(id: 2)} when logged in as user with id 3:

SELECT id, name FROM user WHERE id = 2 AND user.id = 3

So, would anybody share an improvement to this solution or even provide a better solution?

Thanks!

ionizer avatar Nov 27 '19 06:11 ionizer