Permission checking and continuation functions on API using :<|>

Next Topic
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view

Permission checking and continuation functions on API using :<|>

Adrien Duclos
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- 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)
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

:: SomeConfig -> Server FullAPI
go conf
(naturalTransformation env) (get :<|> change)
where change value = put value :<|> delete value

get :: T.Text -> T.Text -> MyHandler BusinessObject
get = undefined

:: T.Text -> BusinessObject -> MyHandler NoContent
= 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 ?