{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE OverloadedStrings #-} {-| Description: Authentication logic for Pocket. This module contains functions to deal with the authentication process of Pocket. Read https://getpocket.com/developer/docs/authentication to have all the details of this process. A user friendly method to interact with the authentication process is through the command: @ stack exec follow_pocket_auth @ -} module Follow.Digesters.Pocket.Auth ( requestTokenStep , accessTokenStep , jsonPostResponseBody , jsonHeaders , consumerKey ) where import Control.Monad.Catch (Exception, MonadThrow, throwM) import Data.Aeson (ToJSON, Value (..), object, (.=)) import qualified Data.HashMap.Strict as HS import Data.Text (Text) import qualified Data.Text as T (concat) import GHC.Generics (Generic) import HTTP.Follow import Network.HTTP.Req (MonadHttp, Option, POST (..), ReqBodyJson (..), Url, header, https, jsonResponse, req, responseBody, (/:)) -- | Errors authenticating in pocket API. data PocketError = PocketTokenNotFound | PocketTokenDecodingError deriving (Eq, Show, Exception) -- | Consumer key for the Follow application in Pocket. consumerKey :: Text consumerKey = "80296-6e350545b4382d839b3aa5df" -- | First auth step. Asks Pocket for a request token. It returns the -- token itself and the URL that the user must visit in order to grant -- access before proceeding to the second step. requestTokenStep :: (MonadThrow m, MonadHttp m) => m (Text, Text) requestTokenStep = do token <- getRequestToken processTokenStep token $ \token -> ( token , T.concat [ "https://getpocket.com/auth/authorize?request_token=" , token , "&redirect_uri=" , "https://getpocket.com" ]) -- | Second auth step. Once a user has granted permission to Follow, -- it exchanges a request token for an access token. The access token -- is what it needs to be used in any other Pocket API call. accessTokenStep :: (MonadHttp m, MonadThrow m) => Text -> m Text accessTokenStep rToken = do token <- getAccessToken rToken processTokenStep token id processTokenStep :: (MonadHttp m, MonadThrow m) => Maybe Value -> (Text -> a) -> m a processTokenStep token f = case token of Nothing -> throwM PocketTokenNotFound Just (String token) -> return $ f token Just _ -> throwM PocketTokenDecodingError -- | Gets the JSON response body of a POST request. jsonPostResponseBody :: (ToJSON b, MonadHttp m, MonadThrow m) => Url scheme -> b -> Option scheme -> m (HS.HashMap Text Value) jsonPostResponseBody url body options = responseBody <$> req POST url (ReqBodyJson body) jsonResponse options -- | Pocket expected headers for a JSON interaction. jsonHeaders = header "Content-Type" "application/json; charset=UTF-8" <> header "X-Accept" "application/json" getRequestToken :: (MonadHttp m, MonadThrow m) => m (Maybe Value) getRequestToken = parseValue "code" <$> jsonPostResponseBody url body jsonHeaders where url = https "getpocket.com" /: "v3" /: "oauth" /: "request" body = object [ "consumer_key" .= String consumerKey , "redirect_uri" .= String "https://getpocket.com" ] getAccessToken :: (MonadHttp m, MonadThrow m) => Text -> m (Maybe Value) getAccessToken rToken = parseValue "access_token" <$> jsonPostResponseBody url body jsonHeaders where url = https "getpocket.com" /: "v3" /: "oauth" /: "authorize" body = object ["consumer_key" .= String consumerKey, "code" .= rToken] parseValue :: Text -> HS.HashMap Text Value -> Maybe Value parseValue = HS.lookup