简体   繁体   English

Graphql Absinthe基于Elixir权限的可访问字段

[英]Graphql Absinthe Elixir permission based accessible fields

What is the proper way to define fields that may not be accessible to all users. 定义所有用户可能无法访问的字段的正确方法是什么。

For example, a general user can query the users and find out another users handle, but only admin users can find out their email address. 例如,普通用户可以查询用户并查找其他用户句柄,但只有管理员用户才能找到他们的电子邮件地址。 The user type defines it as a field but it may not be accessible. 用户类型将其定义为字段,但可能无法访问。 Should there be a separate type for what a general user can see? 是否应该有一般用户可以看到的单独类型? How would you define it? 你会如何定义它?

Sorry if that isn't that clear I just don't possess the vocabulary. 对不起,如果不是那么清楚,我就是不具备词汇量。

Edit: Caution: Graphql documentation disagrees with this approach. 编辑: 警告: Graphql文档不同意此方法。 Use with caution. 谨慎使用。 Wherever you need a private field you must include the appropriate middlewares. 无论您需要私有字段,都必须包含适当的中间件。

Use absinthe middleware . 使用苦艾酒中间件

Here is some code how to do it. 这是一些代码如何做到这一点。 In this example the authenticated user can see the email addresses. 在此示例中,经过身份验证的用户可以查看电子邮件地址。 The anonymous user can't. 匿名用户不能。 You can adjust the logic to require whatever permissions you want. 您可以调整逻辑以要求您需要的任何权限。

defmodule MySite.Middleware.RequireAuthenticated do
  @behaviour Absinthe.Middleware

  @moduledoc """
  Middleware to require authenticated user
  """

  def call(resolution, config) do
    case resolution.context do
      %{current_user: _} ->
        resolution
      _ ->
        Absinthe.Resolution.put_result(resolution, {:error, "unauthenticated"})
    end
  end
end

and then you define your object: 然后你定义你的对象:

  object :user do
    field :id, :id
    field :username, :string 
    field :email, :string do
      middleware MySite.Middleware.RequireAuthenticated
      middleware Absinthe.Middleware.MapGet, :email
    end
  end

So our field email is protected by the RequireAuthenticated middleware. 因此,我们的现场电子邮件受RequireAuthenticated中间件保护。 But according to the link above 但根据上面的链接

One use of middleware/3 is setting the default middleware on a field, replacing the default_resolver macro. 中间件/ 3的一个用途是在字段上设置默认中间件,替换default_resolver宏。

This happens also by using the middleware/2 macro on the field. 通过在字段上使用中间件/ 2宏也会发生这种情况。 This is why we need to also add 这就是我们需要添加的原因

  middleware Absinthe.Middleware.MapGet, :email

to the list of middlewares on the field. 到该领域的中间件列表。

Finally when we perform a query 最后,当我们执行查询时

query {
  user(id: 1){
    username
    email
    id
  }
}

We get the response with the open fields filled and the protected fields nullified 我们得到响应,填充的开放字段和受保护的字段无效

{
  "errors": [
    {
      "message": "In field \"email\": unauthenticated",
      "locations": [
        {
          "line": 4,
          "column": 0
        }
      ]
    }
  ],
  "data": {
    "user": {
      "username": "MyAwesomeUsername",
      "id": "1",
      "email": null
    }
  }
}

You can also use the middleware/3 callback so your object don't get too verbose 您还可以使用中间件/ 3回调,因此您的对象不会太冗长

  def middleware(middleware, %{identifier: :email} = field, _object) do
    [MySite.Middleware.RequireAuthenticated] ++
      [{Absinthe.Middleware.MapGet, :email}] ++
      middleware
  end

With a bit of creative use of the __using__/1 callback you can get a bunch of such functions out of your main schema file. 通过对__using __ / 1回调的一些创造性使用,您可以从主模式文件中获取大量此类函数。

@voger gave an awesome answer and I just wanted to post a macro sample based on the accepted question. @voger给出了一个很棒的答案,我只是想根据接受的问题发布宏观样本。 I'm currently using it to authenticate every field in my schema. 我目前正在使用它来验证我的架构中的每个字段。

Here's a macro definition: 这是一个宏定义:

defmodule MyApp.Notation do
  defmacro protected_field(field, type, viewers, opts \\ []) do
    {ast, other_opts} =
      case Keyword.split(opts, [:do]) do
        {[do: ast], other_opts} ->
          {ast, other_opts}

        {_, other_opts} ->
          {[], other_opts}
      end

    auth_middleware =
      if viewers !== :public do
        quote do
          middleware(MyApp.Middleware.ProtectedField, unquote(viewers))
        end
      end

    quote do
      field(unquote(field), unquote(type), unquote(other_opts)) do
        unquote(auth_middleware)
        middleware(Absinthe.Middleware.MapGet, unquote(field))
        unquote(ast)
      end
    end
  end
end

And then inside of your type definitions, you can do this. 然后在您的类型定义中,您可以执行此操作。

import MyApp.Notation

# ...

object :an_object do
  protected_field(:description, :string, [User, Admin]) do
    middleware(OtherMiddleware)
    resolve(fn _, _, _ ->
      # Custom Resolve
    end)
  end

  protected_field(:name, :stirng, :public, resolve: &custom_resolve/2)
end

Explanation: 说明:

It adds an argument that I call viewers that I just forward to my middleware to check if the user type is correct. 它添加了一个参数,我称之为viewers ,我只是转发到我的中间件来检查用户类型是否正确。 In this scenario, I actually have different models that I call Admin and User that I can check the current user against. 在这种情况下,我实际上有不同的模型,我称之为AdminUser ,我可以检查当前用户。 This is just an example of one way to do it, so your solution might be different. 这只是一种方法的示例,因此您的解决方案可能会有所不同。 I have a special case for :public fields that are just a passthrough. 我有一个特殊情况:public领域只是一个直通。

This is great because I can inject middleware with the extra argument and forward everything else to the original field definition. 这很好,因为我可以使用额外的参数注入中间件并将其他所有内容转发到原始field定义。

I hope this helps add to the answer. 我希望这有助于增加答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM