基于 Graphql Absinthe Elixir 权限的可访问字段
Graphql Absinthe Elixir permission based accessible fields
定义并非所有用户都可以访问的字段的正确方法是什么。
例如,一般用户可以查询用户,查出其他用户的句柄,但只有admin用户可以查到他们的邮箱地址。用户类型将其定义为一个字段,但它可能无法访问。一般用户可以看到的内容是否应该有一个单独的类型?你如何定义它?
抱歉,如果不是那么清楚,我只是不具备词汇。
编辑: 警告: Graphql documentation不同意这种做法。谨慎使用。无论您在哪里需要私有字段,都必须包含适当的中间件。
这里是一些代码如何做到这一点。在此示例中,经过身份验证的用户可以看到电子邮件地址。匿名用户不能。您可以调整逻辑以要求任何您想要的权限。
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
然后定义对象:
object :user do
field :id, :id
field :username, :string
field :email, :string do
middleware MySite.Middleware.RequireAuthenticated
middleware Absinthe.Middleware.MapGet, :email
end
end
因此我们的现场电子邮件受到 RequireAuthenticated 中间件的保护。但是根据上面的link
One use of middleware/3 is setting the default middleware on a field,
replacing the default_resolver macro.
在字段上使用 middleware/2 宏也会发生这种情况。这就是为什么我们还需要添加
middleware Absinthe.Middleware.MapGet, :email
到字段中的中间件列表。
最后当我们执行查询时
query {
user(id: 1){
username
email
id
}
}
我们收到填充了开放字段并取消了受保护字段的响应
{
"errors": [
{
"message": "In field \"email\": unauthenticated",
"locations": [
{
"line": 4,
"column": 0
}
]
}
],
"data": {
"user": {
"username": "MyAwesomeUsername",
"id": "1",
"email": null
}
}
}
您还可以使用 middleware/3 回调,这样您的对象就不会变得太冗长
def middleware(middleware, %{identifier: :email} = field, _object) do
[MySite.Middleware.RequireAuthenticated] ++
[{Absinthe.Middleware.MapGet, :email}] ++
middleware
end
通过创造性地使用 __using__/1 回调,您可以从主架构文件中获得大量此类函数。
@voger 给出了一个很棒的答案,我只是想 post 基于已接受问题的宏观样本。我目前正在使用它来验证我的架构中的每个字段。
这是一个宏定义:
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
然后在你的类型定义中,你可以这样做。
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
解释:
它添加了一个我称之为 viewers
的参数,我只是转发到我的中间件以检查用户类型是否正确。在这种情况下,我实际上有不同的模型,我称之为 Admin
和 User
,我可以检查当前用户。这只是一种方法的示例,因此您的解决方案可能会有所不同。对于 :public
字段,我有一个特例,它只是一个直通。
这很棒,因为我可以用额外的参数注入中间件,并将其他所有内容转发给原始 field
定义。
我希望这有助于补充答案。
定义并非所有用户都可以访问的字段的正确方法是什么。
例如,一般用户可以查询用户,查出其他用户的句柄,但只有admin用户可以查到他们的邮箱地址。用户类型将其定义为一个字段,但它可能无法访问。一般用户可以看到的内容是否应该有一个单独的类型?你如何定义它?
抱歉,如果不是那么清楚,我只是不具备词汇。
编辑: 警告: Graphql documentation不同意这种做法。谨慎使用。无论您在哪里需要私有字段,都必须包含适当的中间件。
这里是一些代码如何做到这一点。在此示例中,经过身份验证的用户可以看到电子邮件地址。匿名用户不能。您可以调整逻辑以要求任何您想要的权限。
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
然后定义对象:
object :user do
field :id, :id
field :username, :string
field :email, :string do
middleware MySite.Middleware.RequireAuthenticated
middleware Absinthe.Middleware.MapGet, :email
end
end
因此我们的现场电子邮件受到 RequireAuthenticated 中间件的保护。但是根据上面的link
One use of middleware/3 is setting the default middleware on a field, replacing the default_resolver macro.
在字段上使用 middleware/2 宏也会发生这种情况。这就是为什么我们还需要添加
middleware Absinthe.Middleware.MapGet, :email
到字段中的中间件列表。
最后当我们执行查询时
query {
user(id: 1){
username
email
id
}
}
我们收到填充了开放字段并取消了受保护字段的响应
{
"errors": [
{
"message": "In field \"email\": unauthenticated",
"locations": [
{
"line": 4,
"column": 0
}
]
}
],
"data": {
"user": {
"username": "MyAwesomeUsername",
"id": "1",
"email": null
}
}
}
您还可以使用 middleware/3 回调,这样您的对象就不会变得太冗长
def middleware(middleware, %{identifier: :email} = field, _object) do
[MySite.Middleware.RequireAuthenticated] ++
[{Absinthe.Middleware.MapGet, :email}] ++
middleware
end
通过创造性地使用 __using__/1 回调,您可以从主架构文件中获得大量此类函数。
@voger 给出了一个很棒的答案,我只是想 post 基于已接受问题的宏观样本。我目前正在使用它来验证我的架构中的每个字段。
这是一个宏定义:
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
然后在你的类型定义中,你可以这样做。
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
解释:
它添加了一个我称之为 viewers
的参数,我只是转发到我的中间件以检查用户类型是否正确。在这种情况下,我实际上有不同的模型,我称之为 Admin
和 User
,我可以检查当前用户。这只是一种方法的示例,因此您的解决方案可能会有所不同。对于 :public
字段,我有一个特例,它只是一个直通。
这很棒,因为我可以用额外的参数注入中间件,并将其他所有内容转发给原始 field
定义。
我希望这有助于补充答案。