GithubHelp home page GithubHelp logo

Comments (23)

reergymerej avatar reergymerej commented on April 27, 2024 2

I’m a few years late, but this seems to be a general problem with functions doing more than one thing. Rules should just test and return a boolean. That’s a separate concern from deciding which error to throw.

from graphql-shield.

blazestudios23 avatar blazestudios23 commented on April 27, 2024 2

Is there no way to just map error messages to rules? That seems like the way to go.

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024 1

@kolybasov this is great. I have nothing to add to second question, let’s make a PR! πŸŽ‰

Right now, there are two ways to solving the first question. You could use allowExternalErrors which will allow all errors that happen during execution to pass through. The second one is by throwing CustomError in rule evaluation.

Hope this answers your question πŸ™‚

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024 1

@maticzav thanks for the fast response!

  1. Not sure if we understand each other correctly on this one. I will try to explain more clear.

Let's imagine I have 2 rules: isAuthenticated and isAdmin.

// isAuthenticated.js
import { shield, rule, and } from 'graphql-shield';

// User should be present in context
const isAuthenticated = rule()((parent, args, ctx) => Boolean(ctx.user));
// User role should be equal to 'admin'
const isAdmin = rule()((parent, args, ctx) => ctx.user.role === 'admin');

// I am combining this 2 rules together
const permissions = shield({
  Query: {
    getAllUsers: and(isAuthenticated, isAdmin)
  }
});

My goal to have this:

  1. If there is no user in the context(isAuthenticated is false) then throw 401 error.
  2. If the user is present but role is not an admin(isAdmin rule is false) then throw 403 error.

If I understood correctly now we always will have CustomError('Not Authorised!') if rules check was failed, right?

  1. Adding global customizable default error is fine, right?

Thanks for helping with this!

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024 1

@maticzav I believe they are just alternatives to each other and it is just matter of preference how you would like to specify custom error.

In my opinion returning an error instead of passing it as param gives more flexibility because then rule may have many custom errors specified in case you need them for some complex one. And it is hard to imagine implemented with passing an error as param.

rule()((parent, args, ctx, info) => {
  if (args.something) {
    return new Error('Something');
  } else if (args.somethingElse) {
    return new Error('Something else');
  } else if (args.evenMore) {
    return new Error('Even more!');
  }	
  return true;
});

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024 1

@maticzav I will join and ping you tomorrow then!

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024 1

@maticzav by some reason I can not receive invite to Prisma Slack. Any chance you are using Prisma Spectrum chat? https://spectrum.chat/prisma

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

Hey πŸ‘‹

Almost correct; logical operators, though, don't act like new rules (they should act like logical operators!). So, you can do something like this:

// isAuthenticated.js
import { shield, rule, and, CustomError } from "graphql-shield";

// User should be present in context
const isAuthenticated = rule()((parent, args, ctx) => {
  if (ctx.user) {
    return true;
  } else {
    throw new CustomError(401);
  }
});
// User role should be equal to 'admin'
const isAdmin = rule()((parent, args, ctx) => {
  if (ctx.user.role === 'admin') {
    return true;
  } else {
    throw new CustomError(403);
  }
});

// I am combining this 2 rules together
const permissions = shield({
  Query: {
    getAllUsers: and(isAuthenticated, isAdmin)
  }
});

As you can see, if one of the rules fails it will return desired error and vice versa.

And yes, big time! I would love to see a PR for customizable default error. I hope this helps you out. πŸ™‚

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024

And in case of or operator it will still throw an error, right?

import { shield, rule, or, CustomError } from "graphql-shield";

// User should be present in context
const isAuthenticated = rule()((parent, args, ctx) => {
  if (ctx.user) {
    return true;
  } else {
    throw new CustomError(401);
  }
});
// User role should be equal to 'admin'
const isAdmin = rule()((parent, args, ctx) => {
  if (ctx.user.role === 'admin') {
    return true;
  } else {
    throw new CustomError(403);
  }
});

// I am combining this 2 rules together
const permissions = shield({
  Query: {
    getAllUsers: or(isAuthenticated, isAdmin)
  }
});

So if I need only some of this rules then throwing custom error is not the case. Correct?

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

The first question, indeed, it will work with all logical operators includingor. I am not sure I understand the second one, though. Both of these rules work with or without being combined with or. Therefore, they will act the same way - considering errors - once they are combined or used alone.

Is this what you had in mind?

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024

Sorry for the confusion.

For me it looks like when I need to check against few rules combined with or and first rule will be false but second rule will result to true shield will throw an error.

My example don't plays really nice together(treat it as pseudocode) but if we will take both rules isAuthenticated and isAdmin and combine them with or(isAuthenticated, isAdmin) then rule never pass if isAuthenticated will throw an error. But it should pass if we will use booleans, right?

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

OK, I can see it now. I think we should find a way to combine the CustomError somehow to make it work with or. I think this is more of a philosophical question in a way. Therefore, I would love to have a short discussion here before making changes.

My thinking process looks like this;

  • We want to reduce the number of unexpected errors as much as possible.
  • We also want to put privacy/security in front of functionality because we can fix functionality but can't fix security with no consequences.
  • I am quite certain that errors are not a good option in any case.
  • and is pretty clear since everything should work for it to pass.
  • or and not are a bit different in a way.

Maybe the best option would be to return an error instead of throwing it since this also better aligns with code writing philosophy in my opinion.

To conclude, I propose to change the functionality by adding customError or error function which is returned from the rule in case of "failure".

Nevertheless, if anything throws an error, the rule should fail. I believe this is crucial for reducing unexpected errors.

What do you think?

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024

Hey @maticzav! I am late a little bit :)

Totally agree with this:

Nevertheless, if anything throws an error, the rule should fail. I believe this is crucial for reducing unexpected errors.

Seems the best option in my opinion!

Maybe the best option would be to return an error instead of throwing it since this also better aligns with code writing philosophy in my opinion.

To conclude, I propose to change the functionality by adding customError or error function which is returned from the rule in case of "failure".

Any help required with this? I see you already started to implement some parts. Thanks!

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

Hey @kolybasov, I need some help. Could you give me your opinion on this; In a hypothetical situation, a user has defined a rule with a custom error. Unfortunately, they weren't precise and made a mistake in their resolver (not rule). Should the rule return customMessage or fallback by default?

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024

@maticzav I imagine it this way:

import { rule, and } from 'graphql-shield';

const isAuthenticated = rule()((parent, args, { user }) => {
  if (user) return true;
  return new Error('Not Authenticated');
});

const isAdmin = rule()((parent, args, { user }) => {
  if (user.admin) return true;
  return new Error('You have no permission to do this');
});

const canBanUser = and(isAuthenticated, isAdmin);
type Mutation {
  banUser(userId: ID!): Boolean
}
  • If isAuthenticated rule fails we raise Not Authenticated error.
  • If isAdmin rule fails we raise You have no permission to do this error.
  • If there is unhandled exception and allowExternalErrors: true we raise original error.
  • If there is unhandled exception and allowExternalErrors: false we raise fallback or default error.
  • If there is unhandled exception and debug: true original error should always be raised.

Hope it makes sense. Let me know if you have more questions :)

And thank you very much for handling this!

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024

@maticzav so basically, graphql-shield rule can be in 4 states.

  • true – all is fine
  • false – rule is failed, use default/fallback error
  • ReturnedError instanceof Error – rule is failed, use returned error
  • Unhandled Exception – rules is failed, use default/fallback error depends on debug and allowExternalErrors options.

Personally, I do not use graphql-shield ability to hide internal errors. I format them with graphql-apollo-errors https://github.com/GiladShoham/graphql-apollo-errors#usage

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

Ok, so my current implementation works like this; I used your first suggestion over mine because it seemed better in the meantime.

const isAdmin = rule({
  error: "You don't have permission to do this."
})(async (parent, args, ctx, info) => {
  if (ctx.user) {
    return true
  } else {
    return false
  }
})

// no user -> error "You don't have permission to do this."

const isOwner = rule({
  error: new ApolloError('Something')
})(async (parent, args, ctx, info) => {
  if (ctx.user) {
    return true
  } else {
    return false
  }
})

// no user -> ApolloError "Something."

Now that I think about it since having the ability to throw an error from inside the rule gives us the ability to access parent, args, ctx, info etc. This might be useful. Nevertheless, your approach from above seems clearer. At the same time, I believe throwing errors is not the best practice.

What do you think?

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024

@maticzav sorry for the initial confusion I was not sure about best solution also.
I am suggesting not to throw error but return it. So abstract rules check will looks like this:

const result = await rule.resolve(parent, args, ctx, info);

if (result instanceof Error) {
  throw result;
} else if (result === false) {
  throw new CustomError('Not Authorised!'); // Current default error, also make it replacebels via global config(IOptions interface)
} else {
  // all is ok
  return resolver(parent, args, ctx, info);
}

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

I see... Should we make both cases possible? Can you imagine a scenario where the first idea would make more sense?

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

I agree, the new code already reflects that. @kolybasov let me know if you have a bit of spare time, I could really use a hand to write docs and examples. You could also give feedback on the changes and introduce the new ones. Tell me if that's an option for you!

from graphql-shield.

m-basov avatar m-basov commented on April 27, 2024

@maticzav sure! Just let me know what I can do and I will help!

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

Perfect! Are you already in Prisma Slack? If not, could you join it here and find me at maticzav. I think we can better communicate there.

from graphql-shield.

maticzav avatar maticzav commented on April 27, 2024

Just released the new version, going to close the issue now.

from graphql-shield.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.