module Network.OAuth.OAuth2.TokenRequest where
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Trans.Except (ExceptT (..), throwE)
import Data.Aeson
import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KeyMap
import Data.ByteString.Lazy.Char8 qualified as BSL
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as T
import Network.HTTP.Conduit
import Network.HTTP.Types qualified as HT
import Network.HTTP.Types.URI (parseQuery)
import Network.OAuth.OAuth2.Internal
import URI.ByteString
import Prelude hiding (error)
data TokenResponseError = TokenResponseError
{ TokenResponseError -> TokenResponseErrorCode
tokenResponseError :: TokenResponseErrorCode
, TokenResponseError -> Maybe Text
tokenResponseErrorDescription :: Maybe Text
, TokenResponseError -> Maybe (URIRef Absolute)
tokenResponseErrorUri :: Maybe (URIRef Absolute)
}
deriving (Int -> TokenResponseError -> ShowS
[TokenResponseError] -> ShowS
TokenResponseError -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TokenResponseError] -> ShowS
$cshowList :: [TokenResponseError] -> ShowS
show :: TokenResponseError -> String
$cshow :: TokenResponseError -> String
showsPrec :: Int -> TokenResponseError -> ShowS
$cshowsPrec :: Int -> TokenResponseError -> ShowS
Show, TokenResponseError -> TokenResponseError -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TokenResponseError -> TokenResponseError -> Bool
$c/= :: TokenResponseError -> TokenResponseError -> Bool
== :: TokenResponseError -> TokenResponseError -> Bool
$c== :: TokenResponseError -> TokenResponseError -> Bool
Eq)
data TokenResponseErrorCode
= InvalidRequest
| InvalidClient
| InvalidGrant
| UnauthorizedClient
| UnsupportedGrantType
| InvalidScope
| UnknownErrorCode Text
deriving (Int -> TokenResponseErrorCode -> ShowS
[TokenResponseErrorCode] -> ShowS
TokenResponseErrorCode -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TokenResponseErrorCode] -> ShowS
$cshowList :: [TokenResponseErrorCode] -> ShowS
show :: TokenResponseErrorCode -> String
$cshow :: TokenResponseErrorCode -> String
showsPrec :: Int -> TokenResponseErrorCode -> ShowS
$cshowsPrec :: Int -> TokenResponseErrorCode -> ShowS
Show, TokenResponseErrorCode -> TokenResponseErrorCode -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TokenResponseErrorCode -> TokenResponseErrorCode -> Bool
$c/= :: TokenResponseErrorCode -> TokenResponseErrorCode -> Bool
== :: TokenResponseErrorCode -> TokenResponseErrorCode -> Bool
$c== :: TokenResponseErrorCode -> TokenResponseErrorCode -> Bool
Eq)
instance FromJSON TokenResponseErrorCode where
parseJSON :: Value -> Parser TokenResponseErrorCode
parseJSON = forall a. String -> (Text -> Parser a) -> Value -> Parser a
withText String
"parseJSON TokenResponseErrorCode" forall a b. (a -> b) -> a -> b
$ \Text
t ->
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ case Text
t of
Text
"invalid_request" -> TokenResponseErrorCode
InvalidRequest
Text
"invalid_client" -> TokenResponseErrorCode
InvalidClient
Text
"invalid_grant" -> TokenResponseErrorCode
InvalidGrant
Text
"unauthorized_client" -> TokenResponseErrorCode
UnauthorizedClient
Text
"unsupported_grant_type" -> TokenResponseErrorCode
UnsupportedGrantType
Text
"invalid_scope" -> TokenResponseErrorCode
InvalidScope
Text
_ -> Text -> TokenResponseErrorCode
UnknownErrorCode Text
t
instance FromJSON TokenResponseError where
parseJSON :: Value -> Parser TokenResponseError
parseJSON = forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"parseJSON TokenResponseError" forall a b. (a -> b) -> a -> b
$ \Object
t -> do
TokenResponseErrorCode
tokenResponseError <- Object
t forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"error"
Maybe Text
tokenResponseErrorDescription <- Object
t forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"error_description"
Maybe (URIRef Absolute)
tokenResponseErrorUri <- Object
t forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"error_uri"
forall (f :: * -> *) a. Applicative f => a -> f a
pure TokenResponseError {Maybe Text
Maybe (URIRef Absolute)
TokenResponseErrorCode
tokenResponseErrorUri :: Maybe (URIRef Absolute)
tokenResponseErrorDescription :: Maybe Text
tokenResponseError :: TokenResponseErrorCode
tokenResponseErrorUri :: Maybe (URIRef Absolute)
tokenResponseErrorDescription :: Maybe Text
tokenResponseError :: TokenResponseErrorCode
..}
parseTokeResponseError :: BSL.ByteString -> TokenResponseError
parseTokeResponseError :: ByteString -> TokenResponseError
parseTokeResponseError ByteString
string =
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (ByteString -> String -> TokenResponseError
mkDecodeOAuth2Error ByteString
string) forall a. a -> a
id (forall a. FromJSON a => ByteString -> Either String a
eitherDecode ByteString
string)
where
mkDecodeOAuth2Error :: BSL.ByteString -> String -> TokenResponseError
mkDecodeOAuth2Error :: ByteString -> String -> TokenResponseError
mkDecodeOAuth2Error ByteString
response String
err =
TokenResponseErrorCode
-> Maybe Text -> Maybe (URIRef Absolute) -> TokenResponseError
TokenResponseError
(Text -> TokenResponseErrorCode
UnknownErrorCode Text
"")
(forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack forall a b. (a -> b) -> a -> b
$ String
"Decode TokenResponseError failed: " forall a. Semigroup a => a -> a -> a
<> String
err forall a. Semigroup a => a -> a -> a
<> String
"\n Original Response:\n" forall a. Semigroup a => a -> a -> a
<> forall a. Show a => a -> String
show (ByteString -> Text
T.decodeUtf8 forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
BSL.toStrict ByteString
response))
forall a. Maybe a
Nothing
accessTokenUrl ::
OAuth2 ->
ExchangeToken ->
(URI, PostBody)
accessTokenUrl :: OAuth2 -> ExchangeToken -> (URIRef Absolute, PostBody)
accessTokenUrl OAuth2
oa ExchangeToken
code =
let uri :: URIRef Absolute
uri = OAuth2 -> URIRef Absolute
oauth2TokenEndpoint OAuth2
oa
body :: PostBody
body =
[ (ByteString
"code", Text -> ByteString
T.encodeUtf8 forall a b. (a -> b) -> a -> b
$ ExchangeToken -> Text
extoken ExchangeToken
code)
, (ByteString
"redirect_uri", forall a. URIRef a -> ByteString
serializeURIRef' forall a b. (a -> b) -> a -> b
$ OAuth2 -> URIRef Absolute
oauth2RedirectUri OAuth2
oa)
, (ByteString
"grant_type", ByteString
"authorization_code")
]
in (URIRef Absolute
uri, PostBody
body)
refreshAccessTokenUrl ::
OAuth2 ->
RefreshToken ->
(URI, PostBody)
refreshAccessTokenUrl :: OAuth2 -> RefreshToken -> (URIRef Absolute, PostBody)
refreshAccessTokenUrl OAuth2
oa RefreshToken
token = (URIRef Absolute
uri, PostBody
body)
where
uri :: URIRef Absolute
uri = OAuth2 -> URIRef Absolute
oauth2TokenEndpoint OAuth2
oa
body :: PostBody
body =
[ (ByteString
"grant_type", ByteString
"refresh_token")
, (ByteString
"refresh_token", Text -> ByteString
T.encodeUtf8 forall a b. (a -> b) -> a -> b
$ RefreshToken -> Text
rtoken RefreshToken
token)
]
fetchAccessToken ::
MonadIO m =>
Manager ->
OAuth2 ->
ExchangeToken ->
ExceptT TokenResponseError m OAuth2Token
fetchAccessToken :: forall (m :: * -> *).
MonadIO m =>
Manager
-> OAuth2
-> ExchangeToken
-> ExceptT TokenResponseError m OAuth2Token
fetchAccessToken = forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> ExchangeToken
-> ExceptT TokenResponseError m OAuth2Token
fetchAccessTokenWithAuthMethod ClientAuthenticationMethod
ClientSecretBasic
fetchAccessToken2 ::
MonadIO m =>
Manager ->
OAuth2 ->
ExchangeToken ->
ExceptT TokenResponseError m OAuth2Token
fetchAccessToken2 :: forall (m :: * -> *).
MonadIO m =>
Manager
-> OAuth2
-> ExchangeToken
-> ExceptT TokenResponseError m OAuth2Token
fetchAccessToken2 = forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> ExchangeToken
-> ExceptT TokenResponseError m OAuth2Token
fetchAccessTokenWithAuthMethod ClientAuthenticationMethod
ClientSecretPost
{-# DEPRECATED fetchAccessToken2 "use 'fetchAccessTokenWithAuthMethod'" #-}
fetchAccessTokenInternal ::
MonadIO m =>
ClientAuthenticationMethod ->
Manager ->
OAuth2 ->
ExchangeToken ->
ExceptT TokenResponseError m OAuth2Token
fetchAccessTokenInternal :: forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> ExchangeToken
-> ExceptT TokenResponseError m OAuth2Token
fetchAccessTokenInternal = forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> ExchangeToken
-> ExceptT TokenResponseError m OAuth2Token
fetchAccessTokenWithAuthMethod
{-# DEPRECATED fetchAccessTokenInternal "use 'fetchAccessTokenWithAuthMethod'" #-}
fetchAccessTokenWithAuthMethod ::
MonadIO m =>
ClientAuthenticationMethod ->
Manager ->
OAuth2 ->
ExchangeToken ->
ExceptT TokenResponseError m OAuth2Token
fetchAccessTokenWithAuthMethod :: forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> ExchangeToken
-> ExceptT TokenResponseError m OAuth2Token
fetchAccessTokenWithAuthMethod ClientAuthenticationMethod
authMethod Manager
manager OAuth2
oa ExchangeToken
code = do
let (URIRef Absolute
uri, PostBody
body) = OAuth2 -> ExchangeToken -> (URIRef Absolute, PostBody)
accessTokenUrl OAuth2
oa ExchangeToken
code
let extraBody :: PostBody
extraBody = if ClientAuthenticationMethod
authMethod forall a. Eq a => a -> a -> Bool
== ClientAuthenticationMethod
ClientSecretPost then OAuth2 -> PostBody
clientSecretPost OAuth2
oa else []
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
Manager
-> OAuth2
-> URIRef Absolute
-> PostBody
-> ExceptT TokenResponseError m a
doJSONPostRequest Manager
manager OAuth2
oa URIRef Absolute
uri (PostBody
body forall a. [a] -> [a] -> [a]
++ PostBody
extraBody)
refreshAccessToken ::
MonadIO m =>
Manager ->
OAuth2 ->
RefreshToken ->
ExceptT TokenResponseError m OAuth2Token
refreshAccessToken :: forall (m :: * -> *).
MonadIO m =>
Manager
-> OAuth2
-> RefreshToken
-> ExceptT TokenResponseError m OAuth2Token
refreshAccessToken = forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> RefreshToken
-> ExceptT TokenResponseError m OAuth2Token
refreshAccessTokenWithAuthMethod ClientAuthenticationMethod
ClientSecretBasic
refreshAccessToken2 ::
MonadIO m =>
Manager ->
OAuth2 ->
RefreshToken ->
ExceptT TokenResponseError m OAuth2Token
refreshAccessToken2 :: forall (m :: * -> *).
MonadIO m =>
Manager
-> OAuth2
-> RefreshToken
-> ExceptT TokenResponseError m OAuth2Token
refreshAccessToken2 = forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> RefreshToken
-> ExceptT TokenResponseError m OAuth2Token
refreshAccessTokenWithAuthMethod ClientAuthenticationMethod
ClientSecretPost
{-# DEPRECATED refreshAccessToken2 "use 'refreshAccessTokenWithAuthMethod'" #-}
refreshAccessTokenInternal ::
MonadIO m =>
ClientAuthenticationMethod ->
Manager ->
OAuth2 ->
RefreshToken ->
ExceptT TokenResponseError m OAuth2Token
refreshAccessTokenInternal :: forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> RefreshToken
-> ExceptT TokenResponseError m OAuth2Token
refreshAccessTokenInternal = forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> RefreshToken
-> ExceptT TokenResponseError m OAuth2Token
refreshAccessTokenWithAuthMethod
{-# DEPRECATED refreshAccessTokenInternal "use 'refreshAccessTokenWithAuthMethod'" #-}
refreshAccessTokenWithAuthMethod ::
MonadIO m =>
ClientAuthenticationMethod ->
Manager ->
OAuth2 ->
RefreshToken ->
ExceptT TokenResponseError m OAuth2Token
refreshAccessTokenWithAuthMethod :: forall (m :: * -> *).
MonadIO m =>
ClientAuthenticationMethod
-> Manager
-> OAuth2
-> RefreshToken
-> ExceptT TokenResponseError m OAuth2Token
refreshAccessTokenWithAuthMethod ClientAuthenticationMethod
authMethod Manager
manager OAuth2
oa RefreshToken
token = do
let (URIRef Absolute
uri, PostBody
body) = OAuth2 -> RefreshToken -> (URIRef Absolute, PostBody)
refreshAccessTokenUrl OAuth2
oa RefreshToken
token
let extraBody :: PostBody
extraBody = if ClientAuthenticationMethod
authMethod forall a. Eq a => a -> a -> Bool
== ClientAuthenticationMethod
ClientSecretPost then OAuth2 -> PostBody
clientSecretPost OAuth2
oa else []
forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
Manager
-> OAuth2
-> URIRef Absolute
-> PostBody
-> ExceptT TokenResponseError m a
doJSONPostRequest Manager
manager OAuth2
oa URIRef Absolute
uri (PostBody
body forall a. [a] -> [a] -> [a]
++ PostBody
extraBody)
doJSONPostRequest ::
(MonadIO m, FromJSON a) =>
Manager ->
OAuth2 ->
URI ->
PostBody ->
ExceptT TokenResponseError m a
doJSONPostRequest :: forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
Manager
-> OAuth2
-> URIRef Absolute
-> PostBody
-> ExceptT TokenResponseError m a
doJSONPostRequest Manager
manager OAuth2
oa URIRef Absolute
uri PostBody
body = do
ByteString
resp <- forall (m :: * -> *).
MonadIO m =>
Manager
-> OAuth2
-> URIRef Absolute
-> PostBody
-> ExceptT TokenResponseError m ByteString
doSimplePostRequest Manager
manager OAuth2
oa URIRef Absolute
uri PostBody
body
case forall a. FromJSON a => ByteString -> Either TokenResponseError a
parseResponseFlexible ByteString
resp of
Right a
obj -> forall (m :: * -> *) a. Monad m => a -> m a
return a
obj
Left TokenResponseError
e -> forall (m :: * -> *) e a. Monad m => e -> ExceptT e m a
throwE TokenResponseError
e
doSimplePostRequest ::
MonadIO m =>
Manager ->
OAuth2 ->
URI ->
PostBody ->
ExceptT TokenResponseError m BSL.ByteString
doSimplePostRequest :: forall (m :: * -> *).
MonadIO m =>
Manager
-> OAuth2
-> URIRef Absolute
-> PostBody
-> ExceptT TokenResponseError m ByteString
doSimplePostRequest Manager
manager OAuth2
oa URIRef Absolute
url PostBody
body =
forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Response ByteString -> Either TokenResponseError ByteString
handleOAuth2TokenResponse IO (Response ByteString)
go
where
go :: IO (Response ByteString)
go = do
Request
req <- forall (m :: * -> *). MonadThrow m => URIRef Absolute -> m Request
uriToRequest URIRef Absolute
url
let req' :: Request
req' = (OAuth2 -> Request -> Request
addBasicAuth OAuth2
oa forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> Request
addDefaultRequestHeaders) Request
req
forall (m :: * -> *).
MonadIO m =>
Request -> Manager -> m (Response ByteString)
httpLbs (PostBody -> Request -> Request
urlEncodedBody PostBody
body Request
req') Manager
manager
handleOAuth2TokenResponse :: Response BSL.ByteString -> Either TokenResponseError BSL.ByteString
handleOAuth2TokenResponse :: Response ByteString -> Either TokenResponseError ByteString
handleOAuth2TokenResponse Response ByteString
rsp =
if Status -> Bool
HT.statusIsSuccessful (forall body. Response body -> Status
responseStatus Response ByteString
rsp)
then forall a b. b -> Either a b
Right forall a b. (a -> b) -> a -> b
$ forall body. Response body -> body
responseBody Response ByteString
rsp
else forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$ ByteString -> TokenResponseError
parseTokeResponseError (forall body. Response body -> body
responseBody Response ByteString
rsp)
parseResponseFlexible ::
FromJSON a =>
BSL.ByteString ->
Either TokenResponseError a
parseResponseFlexible :: forall a. FromJSON a => ByteString -> Either TokenResponseError a
parseResponseFlexible ByteString
r = case forall a. FromJSON a => ByteString -> Either String a
eitherDecode ByteString
r of
Left String
_ -> forall a. FromJSON a => ByteString -> Either TokenResponseError a
parseResponseString ByteString
r
Right a
x -> forall a b. b -> Either a b
Right a
x
parseResponseString ::
FromJSON a =>
BSL.ByteString ->
Either TokenResponseError a
parseResponseString :: forall a. FromJSON a => ByteString -> Either TokenResponseError a
parseResponseString ByteString
b = case ByteString -> Query
parseQuery forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
BSL.toStrict ByteString
b of
[] -> forall a b. a -> Either a b
Left TokenResponseError
errorMessage
Query
a -> case forall a. FromJSON a => Value -> Result a
fromJSON forall a b. (a -> b) -> a -> b
$ Query -> Value
queryToValue Query
a of
Error String
_ -> forall a b. a -> Either a b
Left TokenResponseError
errorMessage
Success a
x -> forall a b. b -> Either a b
Right a
x
where
queryToValue :: Query -> Value
queryToValue = Object -> Value
Object forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall v. [(Key, v)] -> KeyMap v
KeyMap.fromList forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (ByteString, Maybe ByteString) -> (Key, Value)
paramToPair
paramToPair :: (ByteString, Maybe ByteString) -> (Key, Value)
paramToPair (ByteString
k, Maybe ByteString
mv) = (Text -> Key
Key.fromText forall a b. (a -> b) -> a -> b
$ ByteString -> Text
T.decodeUtf8 ByteString
k, forall b a. b -> (a -> b) -> Maybe a -> b
maybe Value
Null (Text -> Value
String forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Text
T.decodeUtf8) Maybe ByteString
mv)
errorMessage :: TokenResponseError
errorMessage = ByteString -> TokenResponseError
parseTokeResponseError ByteString
b
addBasicAuth :: OAuth2 -> Request -> Request
addBasicAuth :: OAuth2 -> Request -> Request
addBasicAuth OAuth2
oa =
ByteString -> ByteString -> Request -> Request
applyBasicAuth
(Text -> ByteString
T.encodeUtf8 forall a b. (a -> b) -> a -> b
$ OAuth2 -> Text
oauth2ClientId OAuth2
oa)
(Text -> ByteString
T.encodeUtf8 forall a b. (a -> b) -> a -> b
$ OAuth2 -> Text
oauth2ClientSecret OAuth2
oa)
addDefaultRequestHeaders :: Request -> Request
Request
req =
let headers :: [(HeaderName, ByteString)]
headers = [(HeaderName, ByteString)]
defaultRequestHeaders forall a. [a] -> [a] -> [a]
++ Request -> [(HeaderName, ByteString)]
requestHeaders Request
req
in Request
req {requestHeaders :: [(HeaderName, ByteString)]
requestHeaders = [(HeaderName, ByteString)]
headers}
clientSecretPost :: OAuth2 -> PostBody
clientSecretPost :: OAuth2 -> PostBody
clientSecretPost OAuth2
oa =
[ (ByteString
"client_id", Text -> ByteString
T.encodeUtf8 forall a b. (a -> b) -> a -> b
$ OAuth2 -> Text
oauth2ClientId OAuth2
oa)
, (ByteString
"client_secret", Text -> ByteString
T.encodeUtf8 forall a b. (a -> b) -> a -> b
$ OAuth2 -> Text
oauth2ClientSecret OAuth2
oa)
]