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

Possibility to Generate For AuthProtect endpoints? #11

Open
erewok opened this issue Oct 14, 2016 · 7 comments
Open

Possibility to Generate For AuthProtect endpoints? #11

erewok opened this issue Oct 14, 2016 · 7 comments

Comments

@erewok
Copy link

erewok commented Oct 14, 2016

Hello,

I have been using this library in a personal project and it's pretty damn cool. Thank you for writing it and for releasing it.

I did have a question, though, about AuthProtect endpoints. I have been using the servant-auth-cookie library to generate sessions and enforce authentication for certain endpoints, but I have not been able to use servant-elm to generate Api calls for these endpoints.

Maybe an example would be illustrative. With an endpoint like this:

type AdminApi = 
  "admin" :> AuthProtect "cookie-auth" :> Get '[HTML] Html
  :<|> "admin" :> "post" :> ReqBody '[JSON] Post.BlogPost :> AuthProtect "cookie-auth" :> Post '[JSON] Post.BlogPost

If I try to generate Elm for this using servant-elm, I get a compiler error like this:

client/GenerateElm.hs:21:9: error:
     No instance for (Servant.Foreign.Internal.HasForeign
                         servant-elm-0.1.0.2:Servant.Elm.Foreign.LangElm
                         elm-export-0.3.0.0:Elm.Type.ElmTypeExpr
                         (Servant.API.Experimental.Auth.AuthProtect "cookie-auth"
                          Servant.API.Sub.:> Servant.API.Verbs.Post
                                               '[Servant.API.ContentTypes.JSON]
                                               Post.BlogPost))
        arising from a use of generateElmForAPI
     In the second argument of (:), namely
        generateElmForAPI adminProxyApi
      In the second argument of (:), namely
        defElmImports : generateElmForAPI adminProxyApi
      In the second argument of (:), namely
        "import Exts.Date exposing (..)"
         : defElmImports : generateElmForAPI adminProxyApi
make: *** [Api.elm] Error 1

Am I doing something wrong or must there be an instance defined to be able to handle AuthProtected endpoints?

Thanks for any suggestions.

@mattjbray
Copy link
Collaborator

Hi,

Thanks - glad you like the library!

We're waiting for servant-foreign to add support for the AuthProtect combinator, then we can think about how to support auth in servant-elm.

However, if your Elm requests don't need to do anything special (I'm guessing the browser automatically adds your auth cookie to the requests?), in the meantime you can just add an instance for AuthProtect that does nothing:

instance (KnownSymbol sym, HasForeign lang ftype sublayout)
    => HasForeign lang ftype (AuthProtect sym :> sublayout) where
    type Foreign ftype (AuthProtect sym :> sublayout) = Foreign ftype sublayout

    foreignFor lang ftype Proxy req =
      foreignFor lang ftype (Proxy :: Proxy sublayout) req

@erewok
Copy link
Author

erewok commented Oct 16, 2016

Nice! I'll give it a shot and report back. Thanks for the suggestion!

@erewok
Copy link
Author

erewok commented Oct 16, 2016

Well, I got stuck on an ambiguous type variable problem and was unfortunately at a loss as to how to begin debugging it, but it's alright if this doesn't work because I can just generate the Elm by hand and return to this problem once the servant-foreign library supports the AuthProtect combinator.

I'll post my attempt and the compiler error anyway in case anyone is working on something similar and stumbles across this discussion:

{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}

import           Data.List
import           Data.Proxy
import           GHC.TypeLits    (KnownSymbol)
import           Servant.Elm
import           Servant.Foreign

import           Api

instance (KnownSymbol sym, HasForeign lang ftype sublayout)
    => HasForeign lang ftype (AuthProtect sym :> sublayout) where
    type Foreign ftype (AuthProtect sym :> sublayout) = Foreign ftype sublayout

    foreignFor lang ftype Proxy req =
      foreignFor lang ftype (Proxy :: Proxy sublayout) req

Results in:

     Couldn't match type Foreign ftype api0
                     with Foreign ftype sublayout
      Expected type: Foreign ftype (AuthProtect sym :> sublayout)
        Actual type: Foreign ftype api0
      NB: Foreign is a type function, and may not be injective
      The type variable api0 is ambiguous
     In the expression:
        foreignFor lang ftype (Proxy :: Proxy sublayout) req
      In an equation for foreignFor’:
          foreignFor lang ftype Proxy req
            = foreignFor lang ftype (Proxy :: Proxy sublayout) req
      In the instance declaration for
        HasForeign lang ftype (AuthProtect sym :> sublayout)
     Relevant bindings include
        req :: Req ftype (bound at client/GenerateElm.hs:19:33)
        ftype :: Proxy ftype (bound at client/GenerateElm.hs:19:21)
        foreignFor :: Proxy lang
                      -> Proxy ftype
                      -> Proxy (AuthProtect sym :> sublayout)
                      -> Req ftype
                      -> Foreign ftype (AuthProtect sym :> sublayout)
          (bound at client/GenerateElm.hs:19:5)

@garetht
Copy link

garetht commented Oct 17, 2016

I just happened to be working on this and using {-# LANGUAGE ScopedTypeVariables #-} solved the ambiguous type variable problem.

@erewok
Copy link
Author

erewok commented Oct 17, 2016

@garetht's suggestion worked for me.

@mattjbray
Copy link
Collaborator

Great! Sorry, it would have been helpful for me to put the language pragmas and imports in my snippet...

@erewok
Copy link
Author

erewok commented Oct 17, 2016

Oh, no worries. I should understand the language pragmas better than I do if I'm going to be using them.

tekul added a commit to ThreeMinuteLearning/my3ml that referenced this issue Mar 15, 2017
Using AuthProtect to implement custom authentication for an API won't work with
servant-elm (see haskell-servant/servant-elm#11).

This change is simlilar to the one suggested in that issue, but rather than
doing nothing, it adds an Authorization header to the request (which results
in another parameter being passed into the generated API request functions
to set the header value). Elm doesn't provide a way to modify the request data
once it has been created so without this it would be impossible to set a
header.
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

3 participants