google-oauth2-0.3.0.0: src/Network/Google/OAuth2.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Network.Google.OAuth2
( getAccessToken
) where
import Control.Exception.Safe (handleIO, throwString)
import Control.Monad ((<=<))
import Control.Monad.Trans.Maybe
import qualified Data.ByteString.Char8 as C8
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Network.HTTP.Client.TLS (getGlobalManager)
import Network.OAuth.OAuth2
import System.IO (hFlush, stdout)
import Text.Read (readMaybe)
import URI.ByteString (serializeURIRef')
import URI.ByteString.QQ (uri)
deriving instance Read IdToken
deriving instance Read AccessToken
deriving instance Read RefreshToken
deriving instance Read OAuth2Token
getAccessToken
:: Text -- ^ Client Id
-> Text -- ^ Client Secret
-> [Text] -- ^ Scopes
-> Maybe FilePath -- ^ File in which to cache the token
-> IO OAuth2Token -- ^ Refreshed token
getAccessToken clientId clientSecret scopes mPath = do
mgr <- getGlobalManager
token <- cached mPath $ do
code <- prompt $ unlines
[ ""
, "Visit the following URL to retrieve a verification code:"
, ""
, C8.unpack $ serializeURIRef' $ authorizationUrl oauth2
, ""
, "Verification code: "
]
fetchAccessToken' mgr $ ExchangeToken $ T.pack code
maybe (pure token) (refreshAccessToken' mgr) $ refreshToken token
where
oauth2 = OAuth2
{ oauthClientId = clientId
, oauthClientSecret = clientSecret
, oauthOAuthorizeEndpoint =
appendQueryParams
[ ("redirect_uri", "urn:ietf:wg:oauth:2.0:oob")
, ("scope", C8.intercalate " " $ map encodeUtf8 scopes)
]
[uri|https://accounts.google.com/o/oauth2/auth|]
, oauthAccessTokenEndpoint =
appendQueryParams
[ ("redirect_uri", "urn:ietf:wg:oauth:2.0:oob")
]
[uri|https://www.googleapis.com/oauth2/v3/token|]
, oauthCallback = Nothing
}
fetchAccessToken' m = fromEither <=< fetchAccessToken m oauth2
refreshAccessToken' m = fromEither <=< refreshAccessToken m oauth2
fromEither = either (throwString . show) pure
prompt :: String -> IO String
prompt msg = do
putStr msg
hFlush stdout
getLine
cached :: (Read a, Show a) => Maybe FilePath -> IO a -> IO a
cached Nothing act = act
cached (Just fp) act = do
mResult <- runMaybeT $ do
c <- MaybeT $ readFileSafe fp
MaybeT $ pure $ readMaybe c
case mResult of
Just x -> pure x
_ -> do
x <- act
x <$ writeFileSafe fp (show x)
readFileSafe :: FilePath -> IO (Maybe String)
readFileSafe = handleIO (const $ pure Nothing) . (Just <$>) . readFile
writeFileSafe :: FilePath -> String -> IO ()
writeFileSafe fp = handleIO (const $ pure ()) . writeFile fp