【问题标题】:Deny Authentication in Servant.Auth with RIO使用 RIO 在 Servant.Auth 中拒绝身份验证
【发布时间】:2021-04-26 07:06:49
【问题描述】:

我正在尝试将 Servant 身份验证(servant-auth-server 包)与 RIO 结合为我的处理程序 monad,以避免出现异常反模式。但是,我无法正确排列类型以处理被拒绝的身份验证。

我的(简化的)API 端点是

type UserEndpoint = "user" :> (
              Get '[JSON] User                                       
        :<|>  ReqBody '[JSON] UpdatedUser :> Put '[JSON] User        
    )

以及对应的服务器

protectedServer
  :: HasLogFunc m
  => AuthResult AuthUserId
  -> ServerT UserEndpoint (RIO m)
protectedServer (Authenticated authUser) =
  getUser authUser :<|> updateUser authUser
-- Otherwise, we return a 401.
protectedServer _ = throwIO err401

拒绝身份验证的分支中出现类型错误:

    Could not deduce (MonadIO ((:<|>) (RIO m User)))
      arising from a use of ‘throwIO’
    [..]

我不理解这种类型的错误。据我了解(并给出protectedServer 的签名),返回类型应该是ServerT UserEndpoint (RIO m),它应该有一个MonadIO 的实例,因此根据exceptions tutorial 的异常处理应该使用throwIO 而不是throwAll 来自 Servant.Auth.Server。看来我还没有完全理解Servant的类机,我的错在哪里?

两个处理函数定义为

updateUser :: HasLogFunc m => AuthUserId -> UpdatedUser -> RIO m User
updateUser authUser updateUser = ...

getUser :: HasLogFunc m => AuthUserId -> RIO m User
getUser authUser = ...

【问题讨论】:

  • 如果在throwIO err401 的位置放置一个类型化的孔_,那么推断的孔类型是什么?另外,如果你尝试在 ghci 中运行:kind! ServerT UserEndpoint (RIO ()),返回的扩展类型是什么?
  • @danidiaz,推断的类型是_ :: RIO m User :&lt;|&gt; (UpdatedUser -&gt; RIO m User) 其中:“m”是受protectedServer :: forall m. HasLogFunc m =&gt; AuthResult AuthUserId -&gt; ServerT UserEndpoint (RIO m) 的类型签名绑定的刚性类型变量。 :&lt;|&gt;不应该将处理程序类型组合成输出类型,这里是ServerT UserEndpoint (RIO m)吗?
  • 单个throwIO err401 就像您要返回的那样没有_ :&lt;|&gt; _ 类型。看来您必须使用:&lt;|&gt; 组合端点实现,就像您在已验证的分支中所做的那样,只是这次返回 401 代码。
  • 在为服务器提供实现时,如果您的 API 中有多个端点,您必须为每个端点提供一个处理函数,并将它们与:&lt;|&gt; 结合起来。当您编写单个 throwIO err401 时,即不是 实现的组合,它是单个处理程序!因此,您需要像在经过身份验证的分支中所做的那样:为每个端点提供一个处理程序,并将它们与 (:&lt;|&gt;) 结合起来。
  • @danidiaz 感谢您的提示。对于未经身份验证的情况,我使用以下定义修复了类型错误:protectedServer _ = throwIO err401 :&lt;|&gt; (\_ -&gt; throwIO err401) 它会进行类型检查,但看起来相当麻烦,尤其是对于更复杂的 API :-(

标签: authentication haskell servant rio


【解决方案1】:

问题在于throwIO err401 是一个单个 RIO 操作。但是当一个服务服务器有多个端点时,每个不同的处理程序都必须由:&lt;|&gt; 组合子组成。

如果您的 API 有许多端点,为每个端点编写返回 401 的处理程序很快就会变得很烦人。幸运的是,servant-auth-server 似乎提供了一个 throwAll 辅助函数,它可以自动为整个 API 构建返回错误的处理程序。

编辑:正如 Ulrich 所指出的,throwAll 的问题在于它仅适用于 MonadError monad,而 RIO 不是 MonadError 的实例。但是应该可以修改类型类,使其支持RIO

首先,一些导入和辅助数据类型:

{-# LANGUAGE UndecidableInstances, TypeOperators, FlexibleInstances,
             TypeFamilies, DataKinds, ImportQualifiedPost
             #-}
module Main where

import RIO (RIO) -- rio
import RIO qualified
import Data.Tagged               (Tagged (..)) -- package tagged
import Servant                   ((:<|>) (..), ServerError(..))
import Network.HTTP.Types -- package http-types
import Network.Wai -- package wai
import Data.ByteString.Char8 qualified as BS

这是主要的RIOThrowAll 类型类:

class RIOThrowAll a where
    rioThrowAll :: ServerError -> a

-- for a composition of endpoints
instance (RIOThrowAll a, RIOThrowAll b) => RIOThrowAll (a :<|> b) where
    rioThrowAll e = rioThrowAll e :<|> rioThrowAll e

-- if we have a function, we ignore the argument and delegate on the result
instance (RIOThrowAll b) => RIOThrowAll (a -> b) where
    rioThrowAll e = \_ -> rioThrowAll e

-- if we reach a RIO action at the tip of a function
instance RIOThrowAll (RIO.RIO env x) where
    rioThrowAll e = RIO.throwIO e

-- this is only for Raw endpoints which embed a WAI app directly
instance RIOThrowAll (Tagged (RIO.RIO env x) Application) where
  rioThrowAll e = Tagged $ \_req respond ->
      respond $ responseLBS (mkStatus (errHTTPCode e) (BS.pack $ errReasonPhrase e))
                            (errHeaders e)
                            (errBody e)

【讨论】:

  • throwAll 助手的问题在于它只能在 Servant 自己的 Handler monad 中工作,这是一个异常堆栈。我还没有找到一种方法让它与 RIO 的方法一起在 IO 中引发异常。
  • @UlrichSchuster 添加了ThrowAll 类型类的一个版本,它应该与RIO 一起使用。
  • 哦,哇,非常感谢您的详尽回答!我需要一些时间来了解它并将其与我的项目集成。一旦我弄清楚了,我会在这里报告我的成功。
  • 我实现了上面@danidiaz 提供的类型类,它就像一个魅力。我的 RIO/Servant 探索可以在这里找到:github.com/uliSchuster/servant-auth-rio
猜你喜欢
  • 2019-12-12
  • 2021-04-25
  • 2016-09-26
  • 2013-03-28
  • 1970-01-01
  • 1970-01-01
  • 2017-12-15
  • 2020-11-03
  • 2020-09-08
相关资源
最近更新 更多