Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: please advise on how to combine RawM with servant-auth's Auth combinator #7

Open
adetokunbo opened this issue Sep 7, 2018 · 5 comments

Comments

@adetokunbo
Copy link

Hi, and thank you for producing a really useful extension to servant

I'm trying to use it with servant-auth. I declare a route that uses both RawM and Auth in a custom Monad e.g.

type ProtectedRaw = (Auth '[Cookie, JWT] Account) :> "protected" :> RawM

protectedRawR
  :: CookieSettings
  -> JWTSettings -> ServerT ProtectedRaw MainApp
protectedRawR = undefined

-- | The application-level Monad, provides acccess to the configuration
-- information via the Reader Monad.
type MainApp = ReaderT Env (ExceptT ServantErr IO)

-- | Convert a MainApp to a Handler
appToHandler :: Env -> MainApp a -> Handler a
appToHandler env action = do
  res <- liftIO $ runExceptT $ runReaderT action env
  liftEither res

testServer env = serveWithContext @ProtectedRaw Proxy theContext server'
  where
    xsrfs = def { xsrfExcludeGet = True }
    cs = defaultCookieSettings { cookieXsrfSetting = Just xsrfs }
    jwts =  defaultJWTSettings $ envSigningKey env
    theContext = cs :. jwts :. EmptyContext
    server' =
      hoistServerWithContext
      @ProtectedRaw Proxy
      (Proxy :: Proxy '[CookieSettings, JWTSettings])
      (appToHandler env) $ protectedRawR cs jwts

this fails to compile with the following message:

     No instance for (HasServer
                         (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookieApi
                            (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookieApi
                               (Servant.RawM.Internal.API.RawM'
                                  Servant.RawM.Internal.API.FileServer)))
                         '[CookieSettings, JWTSettings])
        arising from a use of hoistServerWithContext
     In the expression:
        hoistServerWithContext
          @ProtectedRaw
          Proxy
          (Proxy :: Proxy '[CookieSettings, JWTSettings])
          (appToHandler env)
      In the expression:
        hoistServerWithContext
          @ProtectedRaw
          Proxy
          (Proxy :: Proxy '[CookieSettings, JWTSettings])
          (appToHandler env)
          $ protectedRawR cs jwts
      In an equation for server'’:
          server'
            = hoistServerWithContext
                @ProtectedRaw
                Proxy
                (Proxy :: Proxy '[CookieSettings, JWTSettings])
                (appToHandler env)
                $ protectedRawR cs jwts
    |
139 |       hoistServerWithContext
    |       ^^^^^^^^^^^^^^^^^^^^^^...

Is that expected ?
Is there something wrong with how hoistServerWithContext is used here?
Will it be necessary to write a distinct HasServer instance that combines the two combinators ?

@cdepillabout
Copy link
Owner

You can simplify the error message to something like the following:

No instance for (HasServer
                         (AddSetCookieApi (AddSetCookieApi RawM))
                         '[CookieSettings, JWTSettings])

If you look at AddSetCookieApi, you can see what is going on:

https://hackage.haskell.org/package/servant-auth-server-0.4.0.0/docs/Servant-Auth-Server-Internal-AddSetCookie.html#t:AddSetCookieApi

AddSetCookieApi is a type family. It has an instance for Raw (and some other things), but no instance for RawM.

I think this error is occurring because there is no instance for RawM.

I think there are a couple ways to fix this. The "best" way to fix it would be create a package called servant-auth-rawm that defines the AddSetCookieApi instance for RawM. When users want to use servant-auth and servant-rawm together, they would need to also use the package servant-auth-rawm.

Another solution would be to just add this instance to servant-rawm itself. servant-rawm would get a dependency on servant-auth, but I don't know if this is that bad of a thing. Are there that many users that want to use servant-server and servant-rawm, but absolutely don't want to have servant-auth as a dependency? I dunno, but I wouldn't think there are too many. If one of them are unhappy about this, they could always send a PR creating a servant-auth-rawm` package.

The hackiest (but easiest) solution would just be to create the type instance in your own code.

I think it should look like this:

type instance AddSetCookieApi (RawM' a) = RawM' a

@adetokunbo
Copy link
Author

Thank you for the quick response!

I have just tried this out, and I get a different error, which I think may also be related to type family instances.

     Couldn't match type Network.Wai.Internal.Request
                           -> (Network.Wai.Internal.Response
                               -> IO Network.Wai.Internal.ResponseReceived)
                           -> IO Network.Wai.Internal.ResponseReceived
                     with Headers '[Header "Set-Cookie" SetCookie] cookied1
        arising from a use of hoistServerWithContext
     In the expression:
        hoistServerWithContext
          @ProtectedRaw
          Proxy
          (Proxy :: Proxy '[CookieSettings, JWTSettings])
          (appToHandler env)
      In the expression:
        hoistServerWithContext
          @ProtectedRaw
          Proxy
          (Proxy :: Proxy '[CookieSettings, JWTSettings])
          (appToHandler env)
          $ protectedRawR cs jwts
      In an equation for server'’:
          server'
            = hoistServerWithContext
                @ProtectedRaw
                Proxy
                (Proxy :: Proxy '[CookieSettings, JWTSettings])
                (appToHandler env)
                $ protectedRawR cs jwts

This looks like it's just a type mismatch; it seems that the type computed for hoistServerWithContext is inconsistent between the HasServer instances of the RawM and Auth combinators.

Thanks for the pointer about the type families, there are additional ones used in the constraint pf the Auth HasServer instance, so I'm going to take a look at those to see if there additional type instances that need to be declared.

@cdepillabout
Copy link
Owner

cdepillabout commented Sep 8, 2018

Yeah, I'm not sure what this error is caused by!

If you have any trouble figuring it out, you can post a link to a repo and I'll dig in a little and try to figure it out. (If the code is private, then a minimal repro would work too.)

If you can figure out what the problem is, then packaging the fix up in a new repo or PR, or even just posting it here would probably be helpful for other people!

@adetokunbo
Copy link
Author

Thanks! I also had to an AddSetCookies instance for Functor Application, which works because servant-auth already provides an instance for Application.

-- Type instance for RawM for SetCookie API
type instance AddSetCookieApi (RawM' a) = RawM' a

-- Provide an @AddSetCookies@ instance for functors of Application.
instance (Functor m)
  => AddSetCookies ('S n) (m Application) (m Application) where
  addSetCookies cookies = (fmap $ addSetCookies cookies)

I don't mind putting this into a package called servant-auth-rawm if you are happy to accept a PR.

@cdepillabout
Copy link
Owner

@adetokunbo Yeah, that works for me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants