Graphql 4.0 Create Authorization

I am attempting to create a simple authorization rule that says users may only create posts that they are the author of. However in practice this doesn't seem to work for me. Am I approaching this incorrectly? Is this possible to do with the 4.0 beta authorization and authentication configs?

I appreciate any help.

type User @authentication(operations: [UPDATE, DELETE]) {
  id: ID! @id
  username: String!
  password: String! @authentication
  posts: [Post!]! @relationship(type: "AUTHORED", direction: OUT)
}

type Post
  @authorization(
    validate: [{ where: { node: { author: { id: "$jwt.sub" } } } }]
    operations: [CREATE, UPDATE, DELETE]
  ) {
  id: ID! @id
  title: String!
  content: String!
  public: Boolean!
  author: User! @relationship(type: "AUTHORED", direction: IN)
}

edit: I'm sure I can use graphql-shield to achieve this but was hoping the built-in permissions layer is granular enough. Schema-first development was the goal for this project hence my question but if shield is the only way to achieve this I will proceed with that.

Hi,
I have the same issue, though I think I went a little farer than you. See my post here.

I managed to require that a post should only be created by its author, but I did not managed to prevent post from deletion.

About your code:

  • I'm surprised that it is working because operations is not allowed in @authorization
  • User should be protected by @authorization, not by @authentication. This is enough to let the system verify that only the author can create the post:
type User @authorization(validate: [{ when: [BEFORE], where: { node: { id: "$jwt.sub" } } }]){
...
  • I managed to add @authorization for Post but it has to include a when: [AFTER] section:
type Post
  @authorization(
     validate: [{
       when: [AFTER],
       where: { node: { author: { id: "$jwt.sub" } } } 
     }]
  ) {

Unfortunately, as previously written, no way here to prevent post from deletion.

Please keep me informed if you could do it.

(I'm also interested in shield, but I confess that I'm tired to get this low documented thing working, I'd rather downgrade to previous version).

sorry I should be more clear. the schema I have above is not supposed to work. I'm trying to demonstrate something I would like to do. You are correct that it does not work.

For the time being, I have opted to design my own authentication and authorization layer with shield.

here is a demo I have designed, after removing all authorization and authentication directives. the code is not great and there is definitely a better way to do the actual whitelisting and check:

// permission.ts
export const permissions = shield(
  {
    Query: {
      "*": allow,
    },
    Mutation: {
      signIn: allow,
      signUp: allow,
      // posts
      createPosts: and(rules.isAuthenticated, rules.createPostAsAuthor),
      ...
    },
  },
  {
    debug: true,
  }
);

// rules.ts

// whitelist keys for input
function allowedKeysCheck(input: any, allowedKeys: string[]) {
  const inputKeys = Object.keys(input);
  const isInputAllowed = inputKeys.every((key) => allowedKeys.includes(key));
  return isInputAllowed;
}

// author field MUST be of shape author: { connect: { where: { node: { id: "some-id" } } } }
function authorRestriction(object: any) {
  if (!allowedKeysCheck(object, ["author"])) return false;
  const author = object.author;
  if (!allowedKeysCheck(author, ["connect"])) return false;
  const connect = author.connect;
  if (!allowedKeysCheck(connect, ["where"])) return false;
  const where = connect.where;
  if (!allowedKeysCheck(where, ["node"])) return false;
  const node = where.node;
  if (!allowedKeysCheck(node, ["id"])) return false;
  return true;
}

export const createPostAsAuthor = rule("createPostAsAuthor", {
  cache: "no_cache",
})((_parent, { input }, ctx) => {
  // restrict input to only one post
  if (input.length !== 1) return new Error("Invalid input");

  // restrict input to only author, title, content, and public
  if (!allowedKeysCheck(input[0], ["author", "title", "content", "public"]))
    return new Error("Invalid input");

  // check author connection is valid
  if (!authorRestriction(input[0])) return new Error("Invalid input");

  const {
    author: {
      connect: {
        where: {
          node: { id },
        },
      },
    },
  } = input[0];

  // ensure that the author of the post is the same as the authenticated user
  if (id !== ctx.authContext.sub) return new Error("Not authorized");
  return true;
});

I would much prefer the schema first solution, but for me this will have to work for now.

Thanks for having kept me in touch!