如何在GraphQL中处理复杂的用户权限

我一直在GraphQL研讨会工作 ,这是一次很棒的学习经历。 我在GraphQL中必须处理的最棘手的事情之一就是处理复杂的用户权限。 直到一个朋友将我的注意力吸引到Hasura之前,这一直是一个麻烦。

Hasura是一个开源引擎,可连接到您的数据库和微服务,并立即为您提供可用于生产的GraphQL API。

最简单形式的权限

假设您具有以下模型……

并且要求您确保以下几点:

  1. 只有登录用户才能创建帖子
  2. 只有管​​理员用户可以删除帖子

这些都是可行的-您将需要确保GraphQL解析器能够处理逻辑。 可以从您的解析器调用的函数如下所示:

function createPost ( post )  {
  const isAuthenticated = await isAuthenticated()
  if (!isAuthenticated) {
    // return 401
  }
  return await Post.create(post)
}

为了确保只有管理员用户才能删除帖子,您必须检查用户的角色,授予或权限是否具有管理员值:

function deletePost ( postId, loggedInUserId )  {
  const isAuthenticated = await isAuthenticated()
  const isAdmin = await isAdmin(loggedInUserId)
  if (!isAuthenticated && !isAdmin) {
    // return 401
  }
  return await Post.delete(postId)
}

function isAdmin ( userId )  {
  const userRoles = await getUserRoles(userId);
  return userRoles.includes( 'admin' )
}

假设您将此示例投放到了生产环境中,并且您的论坛或博客甚至黑客新闻克隆都处于活动状态-祝贺您。 您明天回来,就会意识到用户正在更新其他用户的帖子-令人讨厌。

实现这一目标也不是那么困难。 您将需要检查登录用户的ID,并将其与所需帖子上的外键user_id进行比较。 如果它们匹配,则用户可以编辑,如果不匹配,则应返回401:

function updatePost ( post, loggedInUserId )  {
  if (post.user_id !== loggedInUserId) {
    // return 401
  }
  return Post.update(post)
}

我们刚刚看到了日常的CRUD许可策略-很难实现这种授权。 但是,当您开始引入独特的业务逻辑时,事情就会变得棘手。

当权限变得棘手时

几周后,您想鼓励您参与正在进行的任何社交/协作项目。 实现此目的的一种好方法是添加“赞”,“投票”或“拍手”功能。

添加投票功能不只是添加一张具有正确关系的表格。 您需要考虑以下标准的投票规则:

  • 只有登录的用户可以投票
  • 帖子创建者无法对他们创建的帖子进行投票
  • 选民只能在给定帖子中投票一次

这是这种逻辑的样子:

function vote ( post, loggedInUserId )  {
  // 1. Must be logged in
  const isAuthenticated = await isAuthenticated()
  if (!isAuthenticated) {
    // return 401
  }

  // 2. Disallow votes for post creators
  if (post.user_id === loggedInUserId) {
    // return 401
  }

  // 3. Disallow multiple votes per voter
  const votes = await Vote.all({
    where : {
      post_id : post.id, 
      user_id : loggedInUserId
    }
  })

  if (votes.length > 0 ) {
    // return 401
  }

  // Finally, vote
  return await Vote.create({
    post_id : post.id, 
    user_id : loggedInUserId
  })
}

看看随着我们的用户需求不断增加,这种情况变得越来越长。 错误有更多的机会潜入。使情况更糟的是,如果我们不断收到这样的要求,那么维护就变得很痛苦。

想象一下,我们决定执行Medium的功能并添加一个鼓掌功能。 请注意,拍手与投票不同,因为允许用户拍手不止一次,但不能永远拍手:

function clap ( post, loggedInUserId )  {
  // 1. Must be logged in

  // 2. Disallow claps for post creators

  // 3. Disallow more than 20 claps per clapper
  const claps = await Clap.all({
    where : {
      post_id : post.id, 
      user_id : loggedInUserId
    }
  })

  if (claps.length > 20 ) {
    // return 401
  }

  // Finally, clap (again)
  return await Clap.create({
    post_id : post.id, 
    user_id : loggedInUserId
  })
}

您可以吹嘘一个可以正常工作的应用程序,但是请告诉我,当越来越多的功能请求不断涌入时,您如何管理这几年的工作。

与Hasura一体

首先,首先-删除所有这些解析器。

我喜欢将Hasura称为“后端即服务”。 它处理数据管理,关系,模式,授权/权限,并允许您通过其可扩展性功能专注于自定义业务逻辑。

对于开发人员来说,这意味着您将花费更多的时间在网络上建立用户体验,而将更少的时间用于处理诸如CRUD,角色等重复性任务。

要查看运行中的Hasura并将其用于复杂的权限,您需要进行一些设置。 我不会在本文中详细介绍它们,因为我不会做出正式的文档正义:

用户→发布关系应如下所示:

使用Hasura获得许可

在我们看到Hasura中的权限是什么样之前,您需要记住一件事-HTTP是无状态的。 因此,对于发送到Hasura后端的每个查询或变异,都需要确保会话变量或JWT可用。

您可以设置身份验证服务器或使用Auth0之类的东西。 无论您选择哪种选择,都有一个不错的文档可以指导您。 我们不会仅仅设置身份验证,而是让我们可以使用会话变量或JWT。 我们可以模拟它们并将它们用于测试您设置的API。

很高兴知道Hasura权限允许您定义行和列的访问。 例如:

  1. 用户A只能编辑用户A的帖子(行规则)
  2. 用户A无法更改创建日期(列规则)

到目前为止,这是我们从本文开始就拥有的所有许可规则:

  1. 只有登录用户才能创建帖子
  2. 只有管​​理员用户可以删除帖子
  3. 只有登录的用户可以投票
  4. 帖子创建者无法对他们创建的帖子进行投票
  5. 选民只能在给定帖子中投票一次

1.只有登录用户才能创建帖子

这简直太简单了-我们需要告诉Hasura,在尝试编写/创建帖子之前,它应该检查一个具有用户ID的会话变量,以确保对用户进行身份验证。

如果您所有的数据都向公众开放,则身份验证是没有用的。 因此,在创建身份验证和规则之前,您应该做的第一件事就是将Hasura API设为私有

重新加载您的本地主机,应该要求您提供用于将Hasura私有化的秘密:

要创建权限,请在导航栏中单击“数据”菜单,然后从左侧选择要向其添加权限的表。 由于我们要确保只有登录的用户才能创建帖子,因此我们应该选择帖子表。

单击“权限”选项卡,然后按照下面的屏幕快照创建插入权限:

我们暂时不需要对用户进行自定义检查。 我们关心的只是在允许插入之前用户是否已登录。 我们不在乎它是哪个用户。

我们刚刚定义了行权限,但是我们还需要设置用户可以在其中插入值的列。我们不希望用户编辑id或外键user_id

保存权限并从GraphQL菜单创建用户。 我们需要该用户的ID来创建帖子。

x-hasura-role标头设置为user ,这是迄今为止除管理员以外唯一的权限。 请注意,一旦设置了该值,您可以运行的操作将仅限于insert_post突变:

2.只有管理员用户可以删除帖子

删除就像将x-hasura-role更新为admin一样简单:

3.只有登录的用户可以投票

许可就像我们创建帖子一样:

现在,只要将x-hasura-role设置为user就可以将插入投票作为一项操作:

4.帖子创建者无法对他们创建的帖子进行投票

此权限看起来会稍有不同。 我们必须更新在上一步中创建的用户角色,该角色允许任何用户投票。 我们需要对其进行更新以使用自定义检查,以确保发布表上的user_id列不等于X-Hasura-User-Id标头值。 X-Hasura-User-Id是已认证的user

为了使投票表属性在投票权限规则中可用,您需要确保该关系不仅止于添加外键 。 您还需要添加一个对象关系

保存权限并尝试以创建帖子的用户的身份进行投票:

mutation MyMutation {
  insert _vote( objects : { post_id : 1, user_id : 1}) {
    affected_rows
  }
}

您将获得权限错误,但是当您尝试以其他登录用户身份登录时,将可以投票:

(请记住添加一个用户ID值为2的用户)

5.选民只能在给定职位上投票一次

为确保我们不允许用户在特定帖子上投票超过一次,我们需要能够计算出到目前为止该用户对该帖子拥有多少票。 我们有两种选择:

  1. 使用事件触发器编写自定义验证逻辑。 使用此自定义逻辑,您可以查询和计数。
  2. 使用Postgres视图,该视图根据票数汇总行。 然后,您可以向该视图添加手动关系,并使用它来创建权限。

我通常选择选项2,因为我需要编写的代码越少,向项目引入错误的机会就越少。

从创建视图开始。

转到“ 数据”菜单,然后单击“ SQL” 。 粘贴以下SQL以创建视图:

CREATE OR REPLACE VIEW "public" . "vote_count" AS 
  SELECT count (vote.id) AS count ,
    vote.user_id,
    vote.post_id
    FROM vote
  GROUP BY vote.user_id, vote.post_id;

这是一个指导您的屏幕截图:

您应该看到,这会在表列表中添加一个新的表(视图),称为vote_count

为确保其正常运行,请两次运行以下GraphQL查询:

mutation MyMutation {
  insert _vote( objects : { user_id : 2, post_id : 1}) {
    affected_rows
  }
}

就像它出现在这里:

现在,让我们看一下vote表,您应该看到我们有2票关联于同一用户和同一帖子:

但是有趣的是,当您查看vote_count视图时:

现在,让我们在vote表上添加一个关系,以指向vote_count视图。 由于Views从技术上讲不是表,因此我们必须在Hasura上使用手动关系。 手动关系告诉Hasura将这些实体视为关联,即使它们不是两个表也是如此。

要创建手动关系,请转到投票表,选择“关系”选项卡,然后单击“ 配置” 。 接下来,使用以下值填写表单:

使用此视图设置,您可以使用它来确定某人是否可以投票,方法是确保该人尚未投票。

由于我们已经拥有检查该帖子是否不属于该用户的第一条权限规则,因此我们必须使用运算符来添加多个规则。 此运算符是_and ,在编程语言中的行为类似于和运算符。

如框1所示, _and运算符是一个数组。 在可以授予权限之前,此数组中的每个项目都必须解析为true。 这是有道理的,因为我们希望在用户投票之前满足以下条件:

  1. 尝试投票的用户未创建该帖子(已实现)
  2. 用户只能投票一次

第二条规则是我们将在框2中突出显示的字段中实现的规则。在此之前,我们需要讨论可能的情况:

  1. 如果视图中的计数字段小于1,则用户可以投票。
  2. 如果用户不在视图中,则该用户可以投票。

似乎我们在说同样的话,但事实并非如此。 如果您熟悉JavaScript,它就像一个变量,值为0 vs undefined 。 情况1的值为0; 情况2是未定义的值。 如果仅占1,则将出现未处理的边缘情况,这将导致错误。 我们需要确保,如果发生以上任何一种情况,用户都应该能够投票。

这是编程语言中的经典或操作员用例。 我们也可以在Hasura中使用此运算符:

这是这里发生的事情的完整分解:

一个 and 经营者的检查:

  1. 选民不是该职位的所有者
  2. or运算符解析为true

or 任一,如果任何属实将解析为真正的运营商检查:

  1. 用户的投票数小于1(所以0)
  2. 或该行为空(_not)

使用此设置,清除投票表,然后尝试使用GraphQL查询窗口以不同于创建帖子的用户的用户身份进行投票。 请注意,投票是第一次进行。 现在再试一次,您将得到一个权限错误。

您只需在GraphQL中实现一些功能强大的权限策略,而无需一行代码。 我并不感到意外; 是2020年!

这只是使用Hasura可以使用权限进行操作的冰山一角。 您可以看一下有关可能的文档 。 您也可以探索这个Github规模的权限场景。

先前发布在https://dev.to/codebeast/handling-complex-user-permissions-in-graphql-538p

From: https://hackernoon.com/how-to-handle-complex-user-permissions-in-graphql-kke53yfg

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值