Git Product home page Git Product logo

graphql-authz's Introduction

GraphQL authorization layer, flexible, (not only) directive-based, compatible with all modern GraphQL architectures.

Overview

Flexible modern way of adding an authorization layer on top of your existing GraphQL microservices or monolith backend systems.

Full overview can be found in this blog post: https://the-guild.dev/blog/graphql-authz

Features

  • Attaching rules to Query/Mutation/Object/Interface/Field
  • Attaching rules using directives, extensions or authSchema
  • Pre and Post execution rules
  • Any sync/async JS code inside rules
  • Compatible with all modern GraphQL architectures

Examples

Check examples in examples folder:

Integration

To integrate graphql-authz into the project you can follow several steps depending on your architecture:

Read more

Installation

yarn add @graphql-authz/core

Creating Rules

Create rule and rules object

import { preExecRule } from "@graphql-authz/core";

const IsAuthenticated = preExecRule()(context => !!context.user);

const rules = {
  IsAuthenticated
} as const;

Alternatively you can create a rule as a class

import { PreExecutionRule } from "@graphql-authz/core";

class IsAuthenticated extends PreExecutionRule {
  public execute(context) {
    if (!context.user) {
      throw this.error;
    }
  }
}

const rules = {
  IsAuthenticated
} as const;

Configuring server

Configuring Apollo Server plugin

See Apollo Server plugin readme or check examples:

Apollo Server (schema-first, directives)

Apollo Server (code-first, extensions)


Configuring Envelop plugin

See Envelop plugin readme or check examples:

Envelop (schema-first, directives)


Configuring express-graphql

Ensure authenticator is configured to add user info to the request object

const authenticator = (req, res, next) => {
  const user = someHowAuthenticateUser(req.get("authorization")));
  req.user = user;
  next();
};

app.use(authenticator);

Provide customExecuteFn to graphqlHTTP

import { execute } from 'graphql';
import { graphqlHTTP } from 'express-graphql';
import { wrapExecuteFn } from '@graphql-authz/core';

graphqlHTTP({
  ...
  customExecuteFn: wrapExecuteFn(execute, { rules }),
  ...
})

For Directives usage

Apply directive transformer to schema

import { authZDirective } from '@graphql-authz/directive';

const { authZDirectiveTransformer } = authZDirective();

graphqlHTTP({
    ...
    schema: authZDirectiveTransformer(schema),
    customExecuteFn: wrapExecuteFn(execute, { rules }),
    ...
  })

For AuthSchema usage

Pass additional parameter authSchema to wrapExecuteFn

import { execute } from 'graphql';
import { wrapExecuteFn } from '@graphql-authz/core';

graphqlHTTP({
  ...
  customExecuteFn: wrapExecuteFn(execute, { rules: authZRules, authSchema }),
  ...
})

Check an example: express-graphql (schema-first, directives)


Configuring GraphQL Helix

Ensure context parser is configured to perform authentication and add user info to context

import { processRequest } from 'graphql-helix';

processRequest({
  ...
  contextFactory: () => ({
    user: someHowAuthenticateUser(req.get("authorization")))
  }),
  ...
})

Provide execute option to processRequest

import { execute } from 'graphql';
import { processRequest } from 'graphql-helix';
import { wrapExecuteFn } from '@graphql-authz/core';

processRequest({
  ...
  execute: wrapExecuteFn(execute, { rules })
  ...
})

For Directives usage

Apply directive transformer to schema

import { authZDirective } from '@graphql-authz/directive';

const { authZDirectiveTransformer } = authZDirective();

processRequest({
    ...
    schema: authZDirectiveTransformer(schema),
    execute: wrapExecuteFn(execute, { rules }),
    ...
  })

For AuthSchema usage

Pass additional parameter authSchema to wrapExecuteFn

import { execute } from 'graphql';
import { processRequest } from 'graphql-helix';
import { wrapExecuteFn } from '@graphql-authz/core';

processRequest({
  ...
  execute: wrapExecuteFn(execute, { rules, authSchema })
  ...
})

Check an example: GraphQL Helix (schema-first, authSchema)


Configuring schema for directive usage

Schema First

Add rules and directive definition to the schema

  # this is custom list of your rules
  enum AuthZRules {
    IsAuthenticated
    IsAdmin
    CanReadPost
    CanPublishPost
  }

  # this is a common boilerplate
  input AuthZDirectiveCompositeRulesInput {
    and: [AuthZRules]
    or: [AuthZRules]
    not: AuthZRules
  }

  # this is a common boilerplate
  input AuthZDirectiveDeepCompositeRulesInput {
    id: AuthZRules
    and: [AuthZDirectiveDeepCompositeRulesInput]
    or: [AuthZDirectiveDeepCompositeRulesInput]
    not: AuthZDirectiveDeepCompositeRulesInput
  }

  # this is a common boilerplate
  directive @authz(
    rules: [AuthZRules]
    compositeRules: [AuthZDirectiveCompositeRulesInput]
    deepCompositeRules: [AuthZDirectiveDeepCompositeRulesInput]
  ) on FIELD_DEFINITION | OBJECT | INTERFACE

Alternatively generate rules and directive definition

import { directiveTypeDefs } from "@graphql-authz/core";
import { authZGraphQLDirective } from "@graphql-authz/directive";

const directive = authZGraphQLDirective(rules);
const authZDirectiveTypeDefs = directiveTypeDefs(directive);

const typeDefs = gql`
  ${authZDirectiveTypeDefs}

  ...

`;

Code First

Pass directive to schema options to generate rules and directive definition

import { authZGraphQLDirective } from "@graphql-authz/directive";

new GraphQLSchema({
  ...
  directives: [authZGraphQLDirective(rules)]
  ...
});

Attaching rules

Using Directives

Add authz directive to Query/Mutation/Object/Interface/Field you need to perform authorization on

Schema-First

interface TestInterface @authz(rules: [Rule01]) {
  testField1: String! @authz(rules: [Rule02])
}

type TestType implements TestInterface @authz(rules: [Rule01]) {
  testField1: String!
  testField2: Float! @authz(rules: [Rule02])
}

type Query {
  testQuery: TestType! @authz(rules: [Rule01, Rule02])
}

type Mutation {
  testMutation: TestType! @authz(rules: [Rule01, Rule02])
}

Code-First

Using TypeGraphQL or NestJS decorators

@InterfaceType()
@Directive(`@authz(rules: [Rule01]`)
class TestInterface {
  @Field()
  @Directive(`@authz(rules: [Rule02])`)
  public testField1!: string;
}

@ObjectType({ implements: TestInterface })
@Directive(`@authz(rules: [Rule01])`)
class TestType {
  @Field()
  public testField1!: string;

  @Field()
  @Directive(`@authz(rules: [Rule02])`)
  public testField2!: number;
}

@Resolver()
class ResolverClass {

  @Query(() => TestType)
  @Directive(`@authz(rules: [Rule01, Rule02])`)
  public testQuery() {
    ...
  }

  @Mutation(() => TestType)
  @Directive(`@authz(rules: [Rule01, Rule02])`)
  public testMutation() {
    ...
  }
}

Alternatively custom decorator-wrapper can be used

import { IAuthConfig } from "@graphql-authz/core";

function AuthZ(config: IAuthConfig<typeof rules>) {
  const args = Object.keys(config)
    .map(
      key => `${key}: ${JSON.stringify(config[key]).replace(/"/g, '')}`
    )
    .join(', ');
  const directiveArgs = `(${args})`;
  return Directive(`@authz${directiveArgs}`);
}

@InterfaceType()
@AuthZ({ rules: ['Rule01'] })
class TestInterface {
  @Field()
  @AuthZ({ rules: ['Rule02'] })
  public testField1!: string;
}

Using extensions (Code-First only)

Using TypeGraphQL or NestJS decorators

It looks pretty similar to the directive usage with decorators

import { IAuthConfig } from "@graphql-authz/core";

function AuthZ(args: IAuthConfig<typeof rules>) {
  return Extensions({
    authz: {
      directives: [
        {
          name: 'authz',
          arguments: args
        }
      ]
    }
  });
}

@InterfaceType()
@AuthZ({ rules: ['Rule01'] })
class TestInterface {
  @Field()
  @AuthZ({ rules: ['Rule02'] })
  public testField1!: string;
}

Using GraphQL.js constructors

import { IAuthConfig } from "@graphql-authz/core";

function createAuthZExtensions(args: IAuthConfig<typeof rules>) {
  return {
    authz: {
      directives: [
        {
          name: 'authz',
          arguments: args
        }
      ]
    }
  };
}

const Post = new GraphQLObjectType({
  name: 'Post',
  extensions: createAuthZExtensions({
    rules: ['CanReadPost']
  }),
  fields: () => ({
    ...
  })
});

const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    post: {
      type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Post))),
      extensions: createAuthZExtensions({
        rules: ['IsAuthenticated']
      }),
      ...
    }
  }
});

Using GiraphQL

import SchemaBuilder from '@giraphql/core';
import AuthzPlugin from '@giraphql/plugin-authz';

const builder = new SchemaBuilder<{
  AuthZRule: keyof typeof rules;
}>({
  plugins: [AuthzPlugin],
});

builder.queryType({
  fields: (t) => ({
    users: t.field({
      type: [User],
      authz: {
        rules: ['IsAuthenticated'],
      },
      resolve: () => users,
    }),
  }),
});

const Post = builder.objectRef<IPost>('Post');

Post.implement({
  authz: {
    rules: ['CanReadPost'],
  },
  fields: (t) => ({
    id: t.exposeID('id'),
  }),
});

GiraphQL provides a native GraphQL Authz plugin @giraphql/plugin-authz that helps to attach auth rules to Queries/Mutations/Objects/Interfaces/Fields in a type-safe way

Using AuthSchema

const authSchema = {
  Post: { __authz: { rules: ['CanReadPost'] } },
  User: {
    email: { __authz: { rules: ['IsAdmin'] } }
  },
  Mutation: {
    publishPost: { __authz: { rules: ['CanPublishPost'] } }
  },
  Query: {
    users: { __authz: { rules: ['IsAuthenticated'] } }
  }
};

Pre execution rules vs Post execution rules

Pre execution rules are executed before any resolvers are called and have context and fieldArgs as arguments.

context is GraphQL context

fieldArgs is an object that has all arguments passed to Query/Mutation/Field the rule is applied to. If the rule is applied to ObjectType or InterfaceType then fieldArgs is an empty object.

If no other data is needed to perform authorization (e.g. actual data from the database or actual response to the request) then the Pre execution rule should be used.

To create simple Pre execution rule preExecRule function should be used

import { preExecRule } from "@graphql-authz/core";

const IsAuthenticated = preExecRule()(context => !!context.user);

Alternatively new class extended from PreExecutionRule could be created

import { PreExecutionRule } from "@graphql-authz/core";

class IsAuthenticated extends PreExecutionRule {
  public execute(context) {
    if (!context.user) {
      throw this.error;
    }
  }
}

Post execution rules are executed after all resolvers logic is executed and the result response is ready. Because of that fact, Post execution rules have access to the actual result of query execution. This can resolve authorization cases when the decision depends on attributes of the requested entity, for example, if only the author of the post can see the post if it is in draft status.

In order to create Post execution rule postExecRule function should be used

const CanReadPost = postExecRule({
  selectionSet: '{ status author { id } }'
})(
  (
    context: IContext,
    fieldArgs: unknown,
    post: { status: string; author: { id: string } },
    parent: unknown
  ) =>
    post.status === 'public' || context.user?.id === post.author.id
);

Alternatively a new class extended from PostExecutionRule could be created

class CanReadPost extends PostExecutionRule {
  public execute(
    context: IContext,
    fieldArgs: any,
    post: Post,
    parent: unknown
  ) {
    if (post.status !== "public" && context.user?.id !== post.author.id) {
      throw this.error;
    }
  }

  public selectionSet = "{ status author { id } }";
}

By adding selectionSet we ensure that even if the client originally didn't request fields that are needed to perform authorization these fields are present in the result value that comes to a rule as an argument.

Please note that with post execution rules resolvers are executed even if the rule throws an unauthorized error, so such rules are not suitable for Mutations or Queries that update some counters (count of views for example)

Alternative by querying DB inside rules

All rules can be async so for cases where authorization depends on real data and should be performed strictly before resolvers execution (for Mutation or Queries that update some counters) you can query DB right inside pre execution rule

const CanPublishPost = preExecRule()(
  async (
    context: IContext,
    fieldArgs: { postId: string }
  ) => {
    const post = await db.posts.get(fieldArgs.postId);
    return post.authorId === context.user?.id;
  }
);

Alternative by querying schema itself inside rules

graphql-authz doesn't mutate executable schema so you can query schema itself right inside Pre execution rule

Note: you need to pass schema to context to access it inside rules

const CanPublishPost = preExecRule()(
  async (
    context: IContext,
    fieldArgs: { postId: string }
  ) => {
    // query executable schema from rules
    const graphQLResult = await graphql({
      schema: context.schema,
      source: `query post { post(id: ${fieldArgs.postId}) { author { id } } }`
    });

    const post = graphQLResult.data?.post;

    return post && post.author.id === context.user?.id;
  }
);

Wildcard rules

Attaching rules using wildcards is supported by authSchema. Wildcard can be used as a name of Object and as a name of field in different combinations. Here are some examples:

  // Reject rule is attached to every Object
  const authSchema = {
    '*': { __authz: { rules: ['Reject'] } }
  };
  // Reject rule is attached to every Field of every Object
  const authSchema = {
    '*': {
      '*': { __authz: { rules: ['Reject'] } }
    }
  };
  // Reject rule is attached to every Object AND every Field of every Object
  const authSchema = {
    '*': {
      { __authz: { rules: ['Reject'] } },
      '*': { __authz: { rules: ['Reject'] } }
    }
  };
  // Reject rule is attached to every Field of User Object
  const authSchema = {
    User: {
      '*': { __authz: { rules: ['Reject'] } }
    }
  };
  // Reject rule is attached to createdAt Field of every Object
  const authSchema = {
    '*': {
      createdAt: { __authz: { rules: ['Reject'] } }
    }
  };

Overwriting wildcard rules

Wildcard rules are attached to Object/Field only if no other rules are attached to it so to overwrite wildcard rule it's only necessary to attach any other rule to the Object/Field. Wildcard rules can be overwritten by rules attached using directive or extensions as well.

  // Reject rule is attached to all fields of the User object except of the id field. IsAuthenticated rule is attached to the id field.
  const authSchema = {
    User: {
      '*': { __authz: { rules: ['Reject'] } },
      id: { __authz: { rules: ['IsAuthenticated'] } }
    }
  };
  // Reject rule is attached to all Objects except of the User Object. IsAuthenticated rule is attached to the User Object.
  const authSchema = {
    '*': { __authz: { rules: ['Reject'] } },
    User: { __authz: { rules: ['IsAuthenticated'] } }
  };

Wildcard rules priority

Wildcard rules are not composing with each other. Only one wildcard rule is attached to the certain Object/Field. Wildcard rules are attached to a Field in following priority:

  1. Any Field in certain Object:
  const authSchema = {
    User: {
      '*': { __authz: { rules: ['Reject'] } }
    }
  };
  1. Certain field in any object:
  const authSchema = {
    '*': {
      createdAt: { __authz: { rules: ['Reject'] } }
    }
  };
  1. Any field in any object:
  const authSchema = {
    '*': {
      '*': { __authz: { rules: ['Reject'] } }
    }
  };

With following example:

  const authSchema = {
    '*': {
      '*': { __authz: { rules: ['Rule01'] } },
      id: { __authz: { rules: ['Rule02'] } }
    },
    User: {
      '*': { __authz: { rules: ['Rule03'] } }
    }
  };

only Rule03 is attached to the id field of the User object

Composing rules

Default composition

Rules that are passed to @authz directive as rules list are composing with AND logical operator. So @authz(rules: [Rule01, Rule02]) means that authorization will be passed only if Rule01 AND Rule02 passed.

Create composition rules

To create different composition rules and, or, not functions should be used

import { and, or, not } from "@graphql-authz/core";

const TestAndRule = and([Rule01, Rule02, Rule03]);

const TestOrRule = or([Rule01, Rule02, Rule03]);

const TestNotRule = not([Rule01, Rule02, Rule03]);

Alternatively new class extended from AndRule, OrRule or NotRule can be created

import {
  AndRule,
  OrRule,
  NotRule
} from "@graphql-authz/core";

class TestAndRule extends AndRule {
  public getRules() {
    return [Rule01, Rule02, Rule03];
  }
}

class TestOrRule extends OrRule {
  public getRules() {
    return [Rule01, Rule02, Rule03];
  }
}

class TestNotRule extends NotRule {
  public getRules() {
    return [Rule01, Rule02, Rule03];
  }
}

With such code

TestAndRule will pass only if all of Rule01, Rule02, Rule03 pass

TestOrRule will pass if any of Rule01, Rule02, Rule03 pass

TestNotRule will pass only if every of Rule01, Rule02, Rule03 fail

Composition rules can be used just like regular rules

@authz(rules: [TestAndRule])
@authz(rules: [TestOrRule])
@authz(rules: [TestNotRule])

Also, composite rules can be composed of other composite rules

class TestOrRule02 extends OrRule {
  public getRules() {
    return [TestAndRule, TestOrRule, TestNotRule];
  }
}

Inline composition rules

Rules can be composed in an inline way by using compositeRules and deepCompositeRules parameters. The difference between them is compositeRules supports only one level of inline composing. deepCompositeRules supports any levels of composing but it requires id key for the existing rule identificator.

Inline rules composition works with directives, extensions and authSchema

@authz(compositeRules: [{
  or: [Rule01, Rule03],
  not: [Rule02]
}])

@authz(deepCompositeRules: [{
  or: [
    {
      and: [{ id: Rule01 }, { id: Rule02 }]
    },
    {
      or: [{ id: Rule03 }, { id: Rule04 }]
    }
  ]
}])
const authSchema = {
  User: {
    __authz: {
      compositeRules: [{
        or: ['Rule01', 'Rule03'],
        not: ['Rule02']
      }]
    }
  },
  Post: {
    body: {
      __authz: {
        deepCompositeRules: [{
          or: [
            {
              and: [{ id: 'Rule01' }, { id: 'Rule02' }]
            },
            {
              or: [{ id: 'Rule03' }, { id: 'Rule04' }]
            }
          ]
        }]
      }
    }
  }
}

Pre and Post execution rules can be mixed in any way inside all types of composite rules.

Custom errors

To provide custom error for rule the error option should be provided

import { UnauthorizedError } from '@graphql-authz/core';

const SomeRule = postExecRule({
  error: 'User is not authenticated'
})(() => { /* rule body */ });

Using rule class

import { UnauthorizedError } from '@graphql-authz/core';

class SomeRule extends PreExecutionRule {
  public error = new UnauthorizedError("User is not authenticated");
  public execute() {
    throw this.error;
  }
}

It's important to throw an instance of UnauthorizedError imported from @graphql-authz/core to enable composite rules to correctly handle errors thrown from rules. If an instance of UnauthorizedError is thrown it's treated as a certain rule didn't pass. If the rule is wrapped with NotRule then execution should continue. Any other errors are treated as runtime errors and are thrown up, so if any other error is thrown from the rule that is wrapped with NotRule it will fail the entire request.

graphql-authz's People

Contributors

davidtkachenkoastrumu avatar defond0 avatar dependabot[bot] avatar dimatill avatar github-actions[bot] avatar hayes avatar kerimcharfi avatar vtereshyn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

graphql-authz's Issues

The apollo-server-plugin needs to be updated for Apollo Server v4

If using @apollo/server v4 the "context" value in rule execution is undefined.

This is because: (https://www.apollographql.com/docs/apollo-server/migration/#fields-on-graphqlrequestcontext)

The context field has been renamed contextValue for consistency with the graphql-js API and to help differentiate from the context option of integration functions (the function which returns a context value).

Adjusting the code for the plugin might not be too tricky, but new tests need to be written since @apollo/server v4 also changes mocks. (https://www.apollographql.com/docs/apollo-server/testing/mocking/)

๐Ÿ“ฃ New in Apollo Server 4: Apollo Server 4 removes both the mocks and mockEntireSchema constructor options

I am creating this issue as a placeholder, since my PR fails type-based tests. #92

Authorization checks not applied correctly for unions and interface

Was just skimming through some of the code when I fixed the __typename issue and it looks like authorization rules on objects are ignored when the query resolved them through a union or interface. This seems like a pretty important case to cover. This would make any nodes in a relay style graph accessible without auth checks through the node or nodes queries.

Queries with fields not in the schema cause an unhandled TypeError

When using the apollo-server plugin it looks like when an invalid query is passed to api, the plugin throws a type error.

TypeError: Cannot read properties of undefined (reading 'args')
    at getArgumentValues (/Users/jeffreydefond/projects/graphql-authz/node_modules/graphql/execution/values.js:183:28)
    at Object.Field (/Users/jeffreydefond/projects/graphql-authz/packages/core/src/rules-compiler.ts:290:27)
    at Object.enter (/Users/jeffreydefond/projects/graphql-authz/node_modules/graphql/utilities/TypeInfo.js:387:27)
    at visit (/Users/jeffreydefond/projects/graphql-authz/node_modules/graphql/language/visitor.js:200:21)
    at compileRules (/Users/jeffreydefond/projects/graphql-authz/packages/core/src/rules-compiler.ts:346:3)
    at Object.requestDidStart (/Users/jeffreydefond/projects/graphql-authz/packages/plugins/apollo-server/src/index.ts:26:29)
    at initializeRequestListenerDispatcher (/Users/jeffreydefond/projects/graphql-authz/node_modules/apollo-server-express/node_modules/apollo-server-core/src/requestPipeline.ts:598:39)
    at processGraphQLRequest (/Users/jeffreydefond/projects/graphql-authz/node_modules/apollo-server-express/node_modules/apollo-server-core/src/requestPipeline.ts:115:28)
    at ApolloServer.executeOperation (/Users/jeffreydefond/projects/graphql-authz/node_modules/apollo-server-express/node_modules/apollo-server-core/src/ApolloServer.ts:995:33)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

rules-compiler breaks esm build

After digging for quite a while I found the reason why the compiled version of my esm and esbuild based project contains the graphql dependency twice.
The culprit is line 14 of rules-compiler.ts in graphql-authz. graphql is marked as a peer dependency, but the deep import of getArgumentValues, still pulls in the complete graphql library a second time. graphql detects this and throws the "Ensure that there is only one instance of 'graphql' in node_modules" error, making 'graphql-authz' essentially incompatible with my setup.

The good news: there's no reason for the deep import, because getArgumentValues is also exported at the top level. If you could fix this, I would be very grateful.

Migrate to pnpm

Most new repositories for the organization and the community use pnpm. We need to look ahead and use a modern approach too

GiraphQL compatibility

Hey,

Just wrote a tiny plugin for GiraphQL that makes applying rules to fields/types slightly simpler when building schemas with giraphql. https://giraphql.com/plugins/authz

Obviously not necessary to make things work, but gets you something with better type-checking and avoids needing to define a complicated extensions object for each field/type.

@graphql-authz/envelop-plugin has conflicting peer dependencies

Getting errors about @graphql-authz/envelop-plugin has conflicting peer dependencies

xxx in ~/xxx > npm install @graphql-authz/envelop-plugin
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: @anvara-project/[email protected]
npm error Found: @envelop/[email protected]
npm error node_modules/@envelop/core
npm error   @envelop/core@"^5.0.1" from the root project
npm error
npm error Could not resolve dependency:
npm error peer @envelop/core@"^1.0.3" from @graphql-authz/[email protected]
npm error node_modules/@graphql-authz/envelop-plugin
npm error   @graphql-authz/envelop-plugin@"*" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error /xx/xx/.npm/_logs/2024-06-14T14_03_36_591Z-eresolve-report.txt
npm error A complete log of this run can be found in: /Users/xx/.npm/_logs/2024-06-14T14_03_36_591Z-debug-0.log

Am I doing something wrong?

I have a code first schema using apollo v4 server. I have sometning like this

currentMarketUser: {
extensions: createAuthZExtensions({
rules: ["IsMarketUserOnly"],
}),
type: internalServices.IAMService.api.graph.types
.CurrentMarketUserType,
resolve: (_, args, context) =>
internalServices.IAMService.api.graph.queries.currentMarketUser(
_,
args,
context
),
},

but it seems the rule never runs. I read the libary code and it seems

const compiledRules = (0, core_1.compileRules)({
document: filteredDocument,
schema: requestContext.schema,
rules,
variables,
directiveName,
authSchemaKey,
authSchema
});

compiledRules rules is empty and i think it because its checking only entities but not the top level query function. Is it supposed to? I feel like i copied the examples pretty closely

thanks for the help!

@graphql-authz/apollo-server-plugin should probably use @graphql-authz/core as peer dep

@graphql-authz/[email protected] declares @graphql-authz/[email protected] as dependency.

This can lead to a dangerous setup: this is what I had on my project:

@graphql-authz/[email protected]
@graphql-authz/[email protected]

Because my @graphql-authz/core was at version 1.2.1, my @graphql-authz/apollo-server-plugin was using an extra instance of @graphql-authz/core at version (1.3.0). This makes the two packages disconnected.
This setup result in all rules being silently ignored at all while running the graphql server.

A possible solution could be to declare @graphql-authz/[email protected] as peer dep, so yarn does not create an extra instance.
The runtime would crash, which would be desirable.

Check if rules used in authSchema even exists

export const authZRules = {
    IsAdmin
} as const;

const authSchema = {
  User: {
    email: { __authz: { rules: ['isAdmin'] } }
  }
};

Did you spot the typo? I think its necessary to check if a rule used in a schema even exists to prevent unindented data leakage.

I would make a simple checking inside this function:

export function completeConfig(

I can do it and make a PR if you want. Whats your opinion?

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.