Search code examples
databasehaskellauthenticationpoolservant

Haskell Servant passing custom data to auth handler


I'm using custom monad (with reader) to easily pass data like DB pool to my handlers (before using custom monad I used to pass connection as fn argument).

This is how I've defined my custom monad :

newtype Controller a = Controller
    { runController :: ReaderT ServerEnvironment Handler a
    } deriving ( Functor, Applicative, Monad, MonadReader ServerEnvironment, 
                 MonadError ServantErr, MonadIO )

This ServerEnvironment is just custom datatype I use to carry my data.

Problem is that for my AuthHandler I have to specifically use function with:

r -> Handler usr

as authentication handler, I can't use my custom handler which would be :

r -> Controller usr

and I also have no way to pass in my ConnectionPool because signature can't be :

ConnPool -> r -> Handler usr

So, how does one pass extra data to authentication handler in servant without using global IO state?


Solution

  • The AuthHandler you put into the context doesn't have to be defined at the top-level! Generally, you'll want to do it in main so you have access to the database connections etc. which you've created:

    type API = 
      ... :<|> (AuthProtect "myProtection" :> ...) :<|> ...
    
    type instance AuthServerData (AuthProtect "myProtection") = User
    
    server :: ServerEnvironment -> Server API
    server env = ...
    
    setupEnv :: IO ServerEnvironment
    setupEnv = ..
    
    -- This is essentially a 'Controller'.
    authenticate :: ServerEnvironment -> Handler User
    authenticate conn = ...
    
    main :: IO ()
    main = do
      env <- setupEnv
      -- Now, because we have access to the env, we can turn our
      -- 'authenticate' into the right type before putting it
      -- in the context
      let ctx = authenticate env :. EmptyContext
      run 8080 $ serveWithContext myAPI (server conn) ctx