Hello !
I'm trying to make various bits of cool Servant features work together, but I'm hitting a wall.
I need to implement a cookie-based authentication with various permissions stored on the cookie.
I use a AuthHandler from `Servant.Server.Experimental.Auth`. I know about Servant-Auth, and I realize that the Experimental.Auth package is... experimental, but I'm a bit constrained by the Stack LTS we're using (8.22, which mean we use servant-0.9.1.1.). I'd like to know if there's a solution before trying to upgrade things.
The underlying authHandler function will return a custom type that will reflect permissions and information stored on the cookie. So basically, my authentication combinators, in itself, will only check if the cookie is valid or not; permission management is left to the routes implementation.
However, I want the permission checking functions to be abstract enough to be reused in various APIs.
So, imagine that we have a function like
onlyRealm :: (MonadError ServantErr m, MonadIO m) => Realm -> MyCookie -> m a -> m a
It is basically a function that will check the the cookie has been issued with the proper realm, and execute the last parameter as a continuation if so; if not, we will throw a 403.
This works very well with simple APIs. However, it cannot compile on complex cases like this mock API:
type API = CookieProtected :> "route" :> (GetAPI :<|> PutAndDeleteAPI)
type GetAPI = Capture "stuff" T.Text :> Capture "stuff2" T.Text :> Get '[JSON] BusinessObject
type PutAndDeleteAPI =
Capture "id" T.Text :> "route2" :> (ReqBody '[JSON] BusinessObject :> PutNoContent '[JSON] NoContent
:<|> DeleteNoContent '[JSON] NoContent)
I cannot make `onlyRealm` work with a server implementing this API.
server :: SomeConfig -> Server API
server conf cookie = limitToRealm "someRealm" cookie $ go env
go :: SomeConfig -> Server FullAPI
go conf =
enter (naturalTransformation env) (get :<|> change)
where change value = put value :<|> delete value
get :: T.Text -> T.Text -> MyHandler BusinessObject
get = undefined
put :: T.Text -> BusinessObject -> MyHandler NoContent
put = undefined
delete :: T.Text -> MyHandler NoContent
delete = undefined
This doesn't compile : the "go" function does not match the type constraint requested by my limitToRealm (I don't really understand why, I admit).
I know that a potential solution would be to make the permission checks as the authHandler level, but I might want to make various different checks later and this would make compositions rather difficult. So what I really want is a way of writing an abstract function able to prevent going on in the route. How can I write a simple continuation function for this kind of APIs ?