Comments (23)
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.
Is there no way to just map error messages to rules? That seems like the way to go.
from graphql-shield.
@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.
@maticzav thanks for the fast response!
- 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:
- If there is no user in the context(isAuthenticated is false) then throw 401 error.
- 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?
- Adding global customizable default error is fine, right?
Thanks for helping with this!
from graphql-shield.
@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.
@maticzav I will join and ping you tomorrow then!
from graphql-shield.
@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.
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.
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.
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.
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.
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
andnot
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.
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.
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.
@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 raiseNot Authenticated
error. - If
isAdmin
rule fails we raiseYou 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.
@maticzav so basically, graphql-shield rule can be in 4 states.
true
β all is finefalse
β rule is failed, use default/fallback errorReturnedError instanceof Error
β rule is failed, use returned errorUnhandled Exception
β rules is failed, use default/fallback error depends ondebug
andallowExternalErrors
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.
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.
@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.
I see... Should we make both cases possible? Can you imagine a scenario where the first idea would make more sense?
from graphql-shield.
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.
@maticzav sure! Just let me know what I can do and I will help!
from graphql-shield.
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.
Just released the new version, going to close the issue now.
from graphql-shield.
Related Issues (20)
- Execute root fields rules prior graphql execute phase
- Add the ability to use Fragments and post-execution rules with the single server/schema
- Add Typescript typings to rule args
- Add the ability to attach rules using GraphQL Directives
- CI: add codecov reports
- Provide a way to expose authorization metadata through the graphql schema
- GraphQL Shield Roadmap
- 7.6.4 ESM build broken HOT 5
- Documentation website down HOT 1
- How to use `if` condition in GraphQL Shield HOT 2
- Shield rules type generation based on schema HOT 1
- Feature request: wildcard functionality for field names
- Update [email protected] module to avoid @types/lodash and babel runtime in production deps
- fallbackError loses custom error types
- fallbackRule context information HOT 2
- Question: Is there a way to return objects on rules?
- ..
- Typo in the Docs
- Shield permissions only working properly with 'debug: true' HOT 1
- Performance -- every field wrapped is unnecessarily wrapped in a promise HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphql-shield.