packages feed

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

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators       #-}
module AwsMfaCredentials.Interpreters.CredentialsWriter where

import Control.Lens.Operators ((^.))
import Control.Monad.Freer (Eff, Member, handleRelay, send)
import Control.Monad.Freer.Exception (Exc, throwError)
import Control.Monad.Freer.Writer (Writer(..))
import Data.Ini (Ini(..), readIniFile, writeIniFile)
import qualified Data.HashMap.Strict as M
import Data.Text (Text)
import Network.AWS.Auth (credFile)
import Network.AWS.STS.Types
  (Credentials, cAccessKeyId, cSecretAccessKey, cSessionToken)
import System.FileLock (SharedExclusive(..), withFileLock)
import System.FilePath (replaceBaseName)

newtype CredentialsFileParseError = CredentialsFileParseError String

-- | Write out credentials to the AWS credentials file.
--
-- This is atomic if all accesses use our locking protocol.
writeCredentials
  :: forall r a . ( Member IO r
                  , Member (Exc CredentialsFileParseError) r
                  )
  => Eff (Writer (Text, Credentials) ': r) a
  -> Eff r a
writeCredentials = handleRelay pure bind
  where
    bind :: Writer (Text, Credentials) x
         -> (x -> Eff r a)
         -> Eff r a
    bind (Writer (profile, creds)) cont =
      (send $ write profile creds) >>= \case
        Left e -> throwError e
        Right _ -> cont ()

    write profile creds = do
      file <- credFile
      let lockFile = replaceBaseName ".credentials.lock" file
      withFileLock lockFile Exclusive $ \_ -> do
        readIniFile file >>= \case
          Left s -> return . Left $ CredentialsFileParseError s
          Right (Ini ini) -> do
            writeIniFile file . Ini $ M.insert profile (toSection creds) ini
            return $ Right ()

    toSection creds =
      M.fromList [ ("aws_access_key_id", creds ^. cAccessKeyId)
                 , ("aws_secret_access_key", creds ^. cSecretAccessKey)
                 , ("aws_session_token", creds ^. cSessionToken)
                 ]