packages feed

google-oauth2-easy-0.0.0: library/Google/Authentication/Easy.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Google.Authentication.Easy
  ( Service(..)
  , callPostTokenInfo'
  , callPostToken'
  , AccessTokenForm(..)
  , PostTokenBody(..)
  , PostTokenInfoResponse(..)
  , RefreshTokenResp(..)
  , RefreshTokenBody(..)
  , AuthorizationCodeResp(..)
  , AuthorizationCodeBody(..)
  , ServantError
  , Manager
  ) where

import qualified Data.HashMap.Lazy as HML (singleton, fromList)
import Control.Applicative ((<|>))
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Reader (MonadReader, asks)
import Data.Aeson
import Data.Text
import Data.Text.Conversions
import Data.Proxy
import GHC.Generics (Generic)
import Network.HTTP.Client (Manager)
import Servant.API
import Servant.Client
import Web.FormUrlEncoded (Form(..), ToForm(..))

apiBaseUrl :: BaseUrl
apiBaseUrl = BaseUrl Https "www.googleapis.com" 443 ""

type Api = "oauth2" :> (("v3" :> PostTokenInfo) :<|> ("v4" :> PostToken))

type PostTokenInfo = "tokeninfo" :> ReqBody '[FormUrlEncoded] AccessTokenForm :> Post '[JSON] PostTokenInfoResponse
type PostToken = "token" :> ReqBody '[FormUrlEncoded] PostTokenBody :> Post '[JSON] PostTokenResponse

data AccessTokenForm = AccessTokenForm
  { access_token :: Text
  } deriving (Show, Eq)

instance ToForm AccessTokenForm where
  toForm form = Form $ HML.singleton "access_token" [toText $ (access_token :: AccessTokenForm -> Text) form]

data PostTokenInfoResponse = PostTokenInfoResponse
  { azp :: Text
  , aud :: Text
  , sub :: Text
  , scope :: Text
  , exp :: Text
  , expires_in :: Text
  , email :: Text
  , email_verified :: Text
  , access_type :: Text
  } deriving (Show, Eq, Generic)

instance FromJSON PostTokenInfoResponse

data PostTokenBody
  = PostTokenBody'AuthorizationCodeBody AuthorizationCodeBody
  | PostTokenBody'RefreshTokenBody RefreshTokenBody
  deriving (Show, Eq)

instance ToForm PostTokenBody where
  toForm (PostTokenBody'AuthorizationCodeBody body) = toForm body
  toForm (PostTokenBody'RefreshTokenBody body) = toForm body

data AuthorizationCodeBody = AuthorizationCodeBody
  { code :: Text
  , client_id :: Text
  , client_secret :: Text
  , redirect_uri :: Text -- "https://www.camp47.com"
  , grant_type :: Text -- "authorization_code"
  } deriving (Show, Eq, Generic)

instance ToJSON AuthorizationCodeBody

instance ToForm AuthorizationCodeBody where
  toForm AuthorizationCodeBody{code,client_id,client_secret,redirect_uri,grant_type} = Form $ HML.fromList
    [ ("code", [toText code])
    , ("client_id", [toText client_id])
    , ("client_secret", [toText client_secret])
    , ("redirect_uri", [toText redirect_uri])
    , ("grant_type", [toText grant_type])
    ]

data RefreshTokenBody = RefreshTokenBody
  { refresh_token :: Text
  , client_id :: Text
  , client_secret :: Text
  , grant_type :: Text -- "refresh_token"
  } deriving (Show, Eq, Generic)

instance ToForm RefreshTokenBody where
  toForm RefreshTokenBody{refresh_token,client_id,client_secret,grant_type} = Form $ HML.fromList
    [ ("refresh_token", [toText refresh_token])
    , ("client_id", [toText client_id])
    , ("client_secret", [toText client_secret])
    , ("grant_type", [toText grant_type])
    ]

data PostTokenResponse
  = PostTokenResponse'AuthorizationCodeResp AuthorizationCodeResp
  | PostTokenResponse'RefreshTokenResp RefreshTokenResp
  deriving (Show, Eq)

instance FromJSON PostTokenResponse where
  parseJSON v =
    (PostTokenResponse'AuthorizationCodeResp <$> parseJSON v) <|>
    (PostTokenResponse'RefreshTokenResp <$> parseJSON v)

data AuthorizationCodeResp = AuthorizationCodeResp
  { access_token :: Text
  , expires_in :: Int
  , token_type :: Text
  , refresh_token :: Text
  , id_token :: Text
  } deriving (Show, Eq, Generic)

instance FromJSON AuthorizationCodeResp

data RefreshTokenResp = RefreshTokenResp
  { access_token :: Text
  , expires_in :: Int
  , token_type :: Text
  } deriving (Show, Eq, Generic)

instance FromJSON RefreshTokenResp

api :: Proxy Api
api = Proxy

postTokenInfo :: AccessTokenForm -> ClientM PostTokenInfoResponse
postToken :: PostTokenBody -> ClientM PostTokenResponse
postTokenInfo :<|> postToken = client api

class Monad m => Service m where
  callPostTokenInfo :: AccessTokenForm -> m (Either ServantError PostTokenInfoResponse)
  callPostToken :: PostTokenBody -> m (Either ServantError PostTokenResponse)


callPostTokenInfo' :: (MonadIO m, MonadReader r m) => (r -> Manager) -> AccessTokenForm -> m (Either ServantError PostTokenInfoResponse)
callPostTokenInfo' getManager accessTokenForm = do
  manager <- asks getManager
  liftIO $ runClientM (postTokenInfo accessTokenForm) (ClientEnv manager apiBaseUrl)

callPostToken' :: (MonadIO m, MonadReader r m) => (r -> Manager) -> PostTokenBody -> m (Either ServantError PostTokenResponse)
callPostToken' getManager authorizationCodeForm = do
  manager <- asks getManager
  liftIO $ runClientM (postToken authorizationCodeForm) (ClientEnv manager apiBaseUrl)