Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth directive on Union type ignores the applyPolicy function #98

Open
Eomm opened this issue Jan 21, 2023 · 4 comments
Open

Auth directive on Union type ignores the applyPolicy function #98

Eomm opened this issue Jan 21, 2023 · 4 comments
Labels
bug Something isn't working

Comments

@Eomm
Copy link
Contributor

Eomm commented Jan 21, 2023

Given this schema:

directive @auth(
  role: String
) on OBJECT

type Query {
  searchData: Grid
}

union Grid = AdminGrid | ModeratorGrid | UserGrid

type AdminGrid @auth(role: "admin") {
  totalRevenue: Float
}

type ModeratorGrid @auth(role: "moderator") {
  banHammer: Boolean
}

type UserGrid @auth(role: "user") {
  basicColumn: String
}

and this plugin setup:

  app.register(require('mercurius-auth'), {
    authContext (context) {
      // you can validate the headers here
      return {
        identity: context.reply.request.headers['x-user-type']
      }
    },
    async applyPolicy (policy, parent, args, context, info) {
      const role = policy.arguments[0].value.value
      app.log.info('Applying policy %s on user %s', role, context.auth.identity)

      // we compare the schema role directive with the user role
      return context.auth.identity === role
    },
    authDirective: 'auth'
  })

The applyPolicy function is never executed.

If I change the schema to:

type Query {
-  searchData: Grid
+  searchData: AdminGrid
}

The function is executed instead.

Here a complete code example + test (skipped) Eomm/blog-posts@7ec5f23

@jonnydgreen
Copy link
Collaborator

Good find, sounds like a bug in the way the type-level auth works - would you be up for contributing a PR?

@Eomm
Copy link
Contributor Author

Eomm commented Feb 2, 2023

No, sorry, I don't have enough time to work on it

@jonnydgreen
Copy link
Collaborator

jonnydgreen commented Feb 2, 2023

Completely understand, no problem at all!

@jonnydgreen jonnydgreen added the bug Something isn't working label Feb 2, 2023
@ninnjak
Copy link
Contributor

ninnjak commented Jun 4, 2023

Hi @Eomm. From your example I think the issue is your Grid resolveType function. You're checking for obj.adminColumn and obj.moderatorColumn both of which do not exist, unless i'm missing something. Here is a working version based on the example from apollographql

Grid: {
  resolveType(obj, context, info) {
    if (obj.totalRevenue) return "AdminGrid";
    if (obj.banHammer) return "ModeratorGrid";
    return "UserGrid";
  },
}

Another option is to use the user role from the context like so:

Grid: {
  resolveType(obj, context, info) {
    const role = context.auth.identity;
    if (role === "admin") return "AdminGrid";
    if (role === "moderator") return "ModeratorGrid";
    return "UserGrid";
  },
},

Here are some unit tests which i've successfully tested locally:

"use strict";

const { test } = require("tap");
const Fastify = require("fastify");
const mercurius = require("mercurius");
const mercuriusAuth = require("..");

const schema = `
  directive @auth(role: String) on OBJECT

  union Grid = AdminGrid | ModeratorGrid | UserGrid

  type AdminGrid @auth(role: "admin") {
    totalRevenue: Float
  }
  
  type ModeratorGrid @auth(role: "moderator") {
    banHammer: Boolean
  }
  
  type UserGrid @auth(role: "user") {
    basicColumn: String
  }

  type Query {
    searchData: Grid
  }
`;

const resolvers = {
  Query: {
    searchData: async function (root, args, context, info) {
      return {
        totalRevenue: 42,
        banHammer: true,
        basicColumn: "basic",
      };
    },
  },
  Grid: {
    resolveType(obj, contextValue, info) {
      const role = context.auth.identity;
      if (role === "admin") return "AdminGrid";
      if (role === "moderator") return "ModeratorGrid";
      return "UserGrid";
    },
  },
};

test("A user with the `admin` role should only be able to retrieve the `totalRevenue` field", (t) => {
  const app = Fastify();
  t.teardown(app.close.bind(app));

  app.register(mercurius, {
    schema,
    resolvers,
  });

  app.register(mercuriusAuth, {
    authContext(context) {
      return {
        identity: context.reply.request.headers["x-user"],
      };
    },
    async applyPolicy(policy, parent, args, context, info) {
      const role = policy.arguments[0].value.value;
      return context.auth.identity === role;
    },
    authDirective: "auth",
  });

  const request = (query) => {
    return app.inject({
      method: "POST",
      headers: { "content-type": "application/json", "X-User": "admin" },
      url: "/graphql",
      body: JSON.stringify({ query }),
    });
  };

  t.plan(3);

  t.test("should be able to retrieve the `totalRevenue` field", async (t) => {
    const query = `query {
      searchData {
        ... on AdminGrid {
          totalRevenue
        }
      }
    }`;

    const response = await request(query);

    t.same(JSON.parse(response.body), {
      data: {
        searchData: {
          totalRevenue: 42,
        },
      },
    });
  });

  t.test("should not be able to retrieve the `banHammer` field", async (t) => {
    const query = `query {
      searchData {
        ... on ModeratorGrid {
          banHammer
        }
      }
    }`;

    const response = await request(query);

    t.same(JSON.parse(response.body), {
      data: {
        searchData: {},
      },
    });
  });

  t.test(
    "should not be able to retrieve the `basicColumn` field",
    async (t) => {
      const query = `query {
      searchData {
        ... on UserGrid {
          basicColumn
        }
      }
    }`;

      const response = await request(query);

      t.same(JSON.parse(response.body), {
        data: {
          searchData: {},
        },
      });
    }
  );

  t.end();
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants