packages feed

aws-mfa-credentials-0.1.0.0: AwsMfaCredentials/Interpreters/PasswordPrompt.hs

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators       #-}
module AwsMfaCredentials.Interpreters.PasswordPrompt
  (RunAskPassFailure(..), runWithAskPass) where

import AwsMfaCredentials.Effects.PasswordPrompt (PasswordPrompt(..))
import Control.Lens.Operators ((<&>))
import Control.Monad.Freer (Eff, Member, handleRelay, send)
import Control.Monad.Freer.Exception (Exc, throwError)
import Data.Text (Text, strip)
import qualified Data.Text.IO as T
import System.Exit (ExitCode(..))
import System.Process
  ( proc, withCreateProcess, waitForProcess
  , std_in, std_out, StdStream(..)
  )
import System.Timeout (timeout)

data RunAskPassFailure = RunAskPassTimeout
                       | RunAskPassFailure !ExitCode

runAskPassWithTimeout :: String -> IO (Either RunAskPassFailure Text)
runAskPassWithTimeout prompt =
    timeout (120 * 10 ^ (6 :: Int)) run <&> \case
      Just (Left code) -> Left $ RunAskPassFailure code
      Just (Right pass) -> Right pass
      Nothing -> Left RunAskPassTimeout
  where
    procSpec =
      (proc "ssh-askpass" [ prompt ]) { std_in = NoStream
                                      , std_out = CreatePipe
                                      }

    run = withCreateProcess procSpec $ \_ (Just out) _ p -> do
      pass <- T.hGetContents out
      waitForProcess p <&> \case
        ExitSuccess -> Right $ strip pass
        e -> Left e

-- | Run the PasswordPrompt effect using ssh-askpass.
runWithAskPass :: forall r a . ( Member IO r
                               , Member (Exc RunAskPassFailure) r
                               )
               => Eff (PasswordPrompt String Text ': r) a
               -> Eff r a
runWithAskPass = handleRelay pure bind
  where
    bind :: PasswordPrompt String Text x
         -> (x -> Eff r a)
         -> Eff r a
    bind (PasswordPrompt prompt) cont =
      (send $ runAskPassWithTimeout prompt) >>= \case
        Left err -> throwError err
        Right pass -> cont pass