packages feed

hoauth2 2.5.0 → 2.6.0

raw patch · 13 files changed

+1284/−269 lines, 13 filesdep +base64dep +cryptonitedep +memorydep ~binarydep ~containersdep ~data-defaultsetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies added: base64, cryptonite, memory

Dependency ranges changed: binary, containers, data-default, microlens, transformers, uri-bytestring-aeson

API changes (from Hackage documentation)

- Network.OAuth.OAuth2.HttpClient: authPostBS1 :: Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO ByteString
- Network.OAuth.OAuth2.Internal: AuthInRequestBody :: APIAuthenticationMethod
- Network.OAuth.OAuth2.Internal: AuthInRequestHeader :: APIAuthenticationMethod
- Network.OAuth.OAuth2.Internal: AuthInRequestQuery :: APIAuthenticationMethod
- Network.OAuth.OAuth2.Internal: data APIAuthenticationMethod
- Network.OAuth.OAuth2.Internal: instance GHC.Classes.Eq Network.OAuth.OAuth2.Internal.APIAuthenticationMethod
- Network.OAuth.OAuth2.Internal: instance GHC.Classes.Ord Network.OAuth.OAuth2.Internal.APIAuthenticationMethod
- Network.OAuth.OAuth2.Internal: parseIntFlexible :: Value -> Parser Int
+ Network.OAuth.OAuth2: authorizationUrlWithParams :: QueryParams -> OAuth2 -> URI
+ Network.OAuth.OAuth2: fetchAccessTokenWithAuthMethod :: MonadIO m => ClientAuthenticationMethod -> Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
+ Network.OAuth.OAuth2: refreshAccessTokenWithAuthMethod :: MonadIO m => ClientAuthenticationMethod -> Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
+ Network.OAuth.OAuth2.AuthorizationRequest: authorizationUrlWithParams :: QueryParams -> OAuth2 -> URI
+ Network.OAuth.OAuth2.HttpClient: AuthInRequestBody :: APIAuthenticationMethod
+ Network.OAuth.OAuth2.HttpClient: AuthInRequestHeader :: APIAuthenticationMethod
+ Network.OAuth.OAuth2.HttpClient: AuthInRequestQuery :: APIAuthenticationMethod
+ Network.OAuth.OAuth2.HttpClient: authGetBSWithAuthMethod :: MonadIO m => APIAuthenticationMethod -> Manager -> AccessToken -> URI -> ExceptT ByteString m ByteString
+ Network.OAuth.OAuth2.HttpClient: authGetJSONWithAuthMethod :: (MonadIO m, FromJSON a) => APIAuthenticationMethod -> Manager -> AccessToken -> URI -> ExceptT ByteString m a
+ Network.OAuth.OAuth2.HttpClient: authPostBSWithAuthMethod :: MonadIO m => APIAuthenticationMethod -> Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString m ByteString
+ Network.OAuth.OAuth2.HttpClient: authPostJSONWithAuthMethod :: (FromJSON a, MonadIO m) => APIAuthenticationMethod -> Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString m a
+ Network.OAuth.OAuth2.HttpClient: data APIAuthenticationMethod
+ Network.OAuth.OAuth2.HttpClient: instance GHC.Classes.Eq Network.OAuth.OAuth2.HttpClient.APIAuthenticationMethod
+ Network.OAuth.OAuth2.HttpClient: instance GHC.Classes.Ord Network.OAuth.OAuth2.HttpClient.APIAuthenticationMethod
+ Network.OAuth.OAuth2.TokenRequest: fetchAccessTokenWithAuthMethod :: MonadIO m => ClientAuthenticationMethod -> Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: refreshAccessTokenWithAuthMethod :: MonadIO m => ClientAuthenticationMethod -> Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
+ Network.OAuth2.Experiment.Pkce: CodeChallenge :: Text -> CodeChallenge
+ Network.OAuth2.Experiment.Pkce: CodeVerifier :: Text -> CodeVerifier
+ Network.OAuth2.Experiment.Pkce: PkceRequestParam :: CodeVerifier -> CodeChallenge -> CodeChallengeMethod -> PkceRequestParam
+ Network.OAuth2.Experiment.Pkce: S256 :: CodeChallengeMethod
+ Network.OAuth2.Experiment.Pkce: [codeChallengeMethod] :: PkceRequestParam -> CodeChallengeMethod
+ Network.OAuth2.Experiment.Pkce: [codeChallenge] :: PkceRequestParam -> CodeChallenge
+ Network.OAuth2.Experiment.Pkce: [codeVerifier] :: PkceRequestParam -> CodeVerifier
+ Network.OAuth2.Experiment.Pkce: [unCodeChallenge] :: CodeChallenge -> Text
+ Network.OAuth2.Experiment.Pkce: [unCodeVerifier] :: CodeVerifier -> Text
+ Network.OAuth2.Experiment.Pkce: data CodeChallengeMethod
+ Network.OAuth2.Experiment.Pkce: data PkceRequestParam
+ Network.OAuth2.Experiment.Pkce: instance GHC.Show.Show Network.OAuth2.Experiment.Pkce.CodeChallengeMethod
+ Network.OAuth2.Experiment.Pkce: instance GHC.Show.Show Network.OAuth2.Experiment.Pkce.CodeVerifier
+ Network.OAuth2.Experiment.Pkce: mkPkceParam :: MonadIO m => m PkceRequestParam
+ Network.OAuth2.Experiment.Pkce: newtype CodeChallenge
+ Network.OAuth2.Experiment.Pkce: newtype CodeVerifier
+ Network.OAuth2.Experiment.Types: -- WithExchangeToken a b = b' implies no exchange token v.s. 'type
+ Network.OAuth2.Experiment.Types: -- client credentials) will use <a>ExchangeToken</a> in the token request
+ Network.OAuth2.Experiment.Types: -- create type family to be explicit on it. with 'type instance
+ Network.OAuth2.Experiment.Types: -- endpoint.
+ Network.OAuth2.Experiment.Types: -- instance WithExchangeToken a b = ExchangeToken -&gt; b' implies
+ Network.OAuth2.Experiment.Types: -- needing an exchange token
+ Network.OAuth2.Experiment.Types: -- | <a>https://www.rfc-editor.org/rfc/rfc6749#page-47</a>
+ Network.OAuth2.Experiment.Types: AuthorizationCode :: GrantTypeFlow
+ Network.OAuth2.Experiment.Types: AuthorizeState :: Text -> AuthorizeState
+ Network.OAuth2.Experiment.Types: ClientCredentials :: GrantTypeFlow
+ Network.OAuth2.Experiment.Types: ClientId :: Text -> ClientId
+ Network.OAuth2.Experiment.Types: ClientSecret :: Text -> ClientSecret
+ Network.OAuth2.Experiment.Types: GTAuthorizationCode :: GrantTypeValue
+ Network.OAuth2.Experiment.Types: GTClientCredentials :: GrantTypeValue
+ Network.OAuth2.Experiment.Types: GTPassword :: GrantTypeValue
+ Network.OAuth2.Experiment.Types: GTRefreshToken :: GrantTypeValue
+ Network.OAuth2.Experiment.Types: Idp :: URI -> URI -> URI -> (forall m. (FromJSON (IdpUserInfo a), MonadIO m) => Manager -> AccessToken -> URI -> ExceptT ByteString m (IdpUserInfo a)) -> Idp a
+ Network.OAuth2.Experiment.Types: Password :: Text -> Password
+ Network.OAuth2.Experiment.Types: RedirectUri :: URI -> RedirectUri
+ Network.OAuth2.Experiment.Types: ResourceOwnerPassword :: GrantTypeFlow
+ Network.OAuth2.Experiment.Types: Scope :: Text -> Scope
+ Network.OAuth2.Experiment.Types: Username :: Text -> Username
+ Network.OAuth2.Experiment.Types: [$sel:idpAuthorizeEndpoint:Idp] :: Idp a -> URI
+ Network.OAuth2.Experiment.Types: [$sel:idpFetchUserInfo:Idp] :: Idp a -> forall m. (FromJSON (IdpUserInfo a), MonadIO m) => Manager -> AccessToken -> URI -> ExceptT ByteString m (IdpUserInfo a)
+ Network.OAuth2.Experiment.Types: [$sel:idpTokenEndpoint:Idp] :: Idp a -> URI
+ Network.OAuth2.Experiment.Types: [$sel:idpUserInfoEndpoint:Idp] :: Idp a -> URI
+ Network.OAuth2.Experiment.Types: [$sel:unAuthorizeState:AuthorizeState] :: AuthorizeState -> Text
+ Network.OAuth2.Experiment.Types: [$sel:unClientId:ClientId] :: ClientId -> Text
+ Network.OAuth2.Experiment.Types: [$sel:unClientSecret:ClientSecret] :: ClientSecret -> Text
+ Network.OAuth2.Experiment.Types: [$sel:unPassword:Password] :: Password -> Text
+ Network.OAuth2.Experiment.Types: [$sel:unRedirectUri:RedirectUri] :: RedirectUri -> URI
+ Network.OAuth2.Experiment.Types: [$sel:unScope:Scope] :: Scope -> Text
+ Network.OAuth2.Experiment.Types: [$sel:unUsername:Username] :: Username -> Text
+ Network.OAuth2.Experiment.Types: class HasAuthorizeRequest (a :: GrantTypeFlow) where {
+ Network.OAuth2.Experiment.Types: class HasIdpAppName (a :: GrantTypeFlow)
+ Network.OAuth2.Experiment.Types: class HasPkceAuthorizeRequest (a :: GrantTypeFlow)
+ Network.OAuth2.Experiment.Types: class HasPkceTokenRequest (b :: GrantTypeFlow)
+ Network.OAuth2.Experiment.Types: class HasRefreshTokenRequest (a :: GrantTypeFlow) where {
+ Network.OAuth2.Experiment.Types: class HasTokenRequest (a :: GrantTypeFlow) where {
+ Network.OAuth2.Experiment.Types: class HasUserInfoRequest (a :: GrantTypeFlow)
+ Network.OAuth2.Experiment.Types: class ToQueryParam a
+ Network.OAuth2.Experiment.Types: class ToResponseTypeValue (a :: GrantTypeFlow)
+ Network.OAuth2.Experiment.Types: conduitPkceTokenRequest :: (HasPkceTokenRequest b, MonadIO m) => IdpApplication b i -> Manager -> (ExchangeToken, CodeVerifier) -> ExceptT (OAuth2Error Errors) m OAuth2Token
+ Network.OAuth2.Experiment.Types: conduitRefreshTokenRequest :: (HasRefreshTokenRequest a, MonadIO m) => IdpApplication a i -> Manager -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
+ Network.OAuth2.Experiment.Types: conduitTokenRequest :: (HasTokenRequest a, MonadIO m) => IdpApplication a i -> Manager -> WithExchangeToken a (ExceptT (OAuth2Error Errors) m OAuth2Token)
+ Network.OAuth2.Experiment.Types: conduitUserInfoRequest :: (HasUserInfoRequest a, FromJSON (IdpUserInfo i)) => IdpApplication a i -> Manager -> AccessToken -> ExceptT ByteString IO (IdpUserInfo i)
+ Network.OAuth2.Experiment.Types: data AuthorizationRequest a;
+ Network.OAuth2.Experiment.Types: data GrantTypeFlow
+ Network.OAuth2.Experiment.Types: data GrantTypeValue
+ Network.OAuth2.Experiment.Types: data Idp a
+ Network.OAuth2.Experiment.Types: data RefreshTokenRequest a;
+ Network.OAuth2.Experiment.Types: data TokenRequest a;
+ Network.OAuth2.Experiment.Types: data family IdpApplication (a :: GrantTypeFlow) (i :: Type)
+ Network.OAuth2.Experiment.Types: getIdpAppName :: HasIdpAppName a => IdpApplication a i -> Text
+ Network.OAuth2.Experiment.Types: instance Data.String.IsString Network.OAuth2.Experiment.Types.AuthorizeState
+ Network.OAuth2.Experiment.Types: instance Data.String.IsString Network.OAuth2.Experiment.Types.ClientId
+ Network.OAuth2.Experiment.Types: instance Data.String.IsString Network.OAuth2.Experiment.Types.ClientSecret
+ Network.OAuth2.Experiment.Types: instance Data.String.IsString Network.OAuth2.Experiment.Types.Password
+ Network.OAuth2.Experiment.Types: instance Data.String.IsString Network.OAuth2.Experiment.Types.Scope
+ Network.OAuth2.Experiment.Types: instance Data.String.IsString Network.OAuth2.Experiment.Types.Username
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Eq Network.OAuth2.Experiment.Types.AuthorizeState
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Eq Network.OAuth2.Experiment.Types.ClientId
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Eq Network.OAuth2.Experiment.Types.ClientSecret
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Eq Network.OAuth2.Experiment.Types.GrantTypeValue
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Eq Network.OAuth2.Experiment.Types.Password
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Eq Network.OAuth2.Experiment.Types.RedirectUri
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Eq Network.OAuth2.Experiment.Types.Scope
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Eq Network.OAuth2.Experiment.Types.Username
+ Network.OAuth2.Experiment.Types: instance GHC.Classes.Ord Network.OAuth2.Experiment.Types.Scope
+ Network.OAuth2.Experiment.Types: instance GHC.Show.Show Network.OAuth2.Experiment.Types.ClientId
+ Network.OAuth2.Experiment.Types: instance GHC.Show.Show Network.OAuth2.Experiment.Types.GrantTypeValue
+ Network.OAuth2.Experiment.Types: instance GHC.Show.Show Network.OAuth2.Experiment.Types.Scope
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasAuthorizeRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasIdpAppName 'Network.OAuth2.Experiment.Types.AuthorizationCode
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasIdpAppName 'Network.OAuth2.Experiment.Types.ClientCredentials
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasIdpAppName 'Network.OAuth2.Experiment.Types.ResourceOwnerPassword
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasPkceAuthorizeRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasPkceTokenRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasRefreshTokenRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasRefreshTokenRequest 'Network.OAuth2.Experiment.Types.ResourceOwnerPassword
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasTokenRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasTokenRequest 'Network.OAuth2.Experiment.Types.ClientCredentials
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasTokenRequest 'Network.OAuth2.Experiment.Types.ResourceOwnerPassword
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasUserInfoRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.HasUserInfoRequest 'Network.OAuth2.Experiment.Types.ResourceOwnerPassword
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam (Data.Set.Internal.Set Network.OAuth2.Experiment.Types.Scope)
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam (Network.OAuth2.Experiment.Types.AuthorizationRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode)
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam (Network.OAuth2.Experiment.Types.RefreshTokenRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode)
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam (Network.OAuth2.Experiment.Types.TokenRequest 'Network.OAuth2.Experiment.Types.AuthorizationCode)
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam (Network.OAuth2.Experiment.Types.TokenRequest 'Network.OAuth2.Experiment.Types.ClientCredentials)
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam (Network.OAuth2.Experiment.Types.TokenRequest 'Network.OAuth2.Experiment.Types.ResourceOwnerPassword)
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth.OAuth2.Internal.ExchangeToken
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth.OAuth2.Internal.RefreshToken
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Pkce.CodeChallenge
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Pkce.CodeChallengeMethod
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Pkce.CodeVerifier
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Types.AuthorizeState
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Types.ClientId
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Types.ClientSecret
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Types.GrantTypeValue
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Types.Password
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Types.RedirectUri
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam Network.OAuth2.Experiment.Types.Username
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToQueryParam a => Network.OAuth2.Experiment.Types.ToQueryParam (GHC.Maybe.Maybe a)
+ Network.OAuth2.Experiment.Types: instance Network.OAuth2.Experiment.Types.ToResponseTypeValue 'Network.OAuth2.Experiment.Types.AuthorizationCode
+ Network.OAuth2.Experiment.Types: mkAuthorizeRequest :: HasAuthorizeRequest a => IdpApplication a i -> MkAuthorizationRequestResponse a
+ Network.OAuth2.Experiment.Types: mkAuthorizeRequestParameter :: HasAuthorizeRequest a => IdpApplication a i -> AuthorizationRequest a
+ Network.OAuth2.Experiment.Types: mkPkceAuthorizeRequest :: (HasPkceAuthorizeRequest a, MonadIO m) => IdpApplication a i -> m (Text, CodeVerifier)
+ Network.OAuth2.Experiment.Types: mkRefreshTokenRequest :: HasRefreshTokenRequest a => IdpApplication a i -> RefreshToken -> RefreshTokenRequest a
+ Network.OAuth2.Experiment.Types: mkTokenRequest :: HasTokenRequest a => IdpApplication a i -> WithExchangeToken a (TokenRequest a)
+ Network.OAuth2.Experiment.Types: newtype AuthorizeState
+ Network.OAuth2.Experiment.Types: newtype ClientId
+ Network.OAuth2.Experiment.Types: newtype ClientSecret
+ Network.OAuth2.Experiment.Types: newtype Password
+ Network.OAuth2.Experiment.Types: newtype RedirectUri
+ Network.OAuth2.Experiment.Types: newtype Scope
+ Network.OAuth2.Experiment.Types: newtype Username
+ Network.OAuth2.Experiment.Types: toOAuth2Key :: ClientId -> ClientSecret -> OAuth2
+ Network.OAuth2.Experiment.Types: toQueryParam :: ToQueryParam a => a -> Map Text Text
+ Network.OAuth2.Experiment.Types: toResponseTypeParam :: forall a b req. (ToResponseTypeValue a, IsString b) => req a -> Map b b
+ Network.OAuth2.Experiment.Types: toResponseTypeValue :: (ToResponseTypeValue a, IsString b) => b
+ Network.OAuth2.Experiment.Types: type MkAuthorizationRequestResponse a;
+ Network.OAuth2.Experiment.Types: type WithExchangeToken a b;
+ Network.OAuth2.Experiment.Types: type family IdpUserInfo a
+ Network.OAuth2.Experiment.Types: }
+ Network.OAuth2.Experiment.Utils: bs8ToLazyText :: ByteString -> Text
+ Network.OAuth2.Experiment.Utils: mapsToParams :: [Map Text Text] -> [(ByteString, ByteString)]
+ Network.OAuth2.Experiment.Utils: tlToBS :: Text -> ByteString
- Network.OAuth.OAuth2: doJSONPostRequest :: (FromJSON err, FromJSON a) => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO a
+ Network.OAuth.OAuth2: doJSONPostRequest :: (MonadIO m, FromJSON err, FromJSON a) => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) m a
- Network.OAuth.OAuth2: doSimplePostRequest :: FromJSON err => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO ByteString
+ Network.OAuth.OAuth2: doSimplePostRequest :: (MonadIO m, FromJSON err) => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) m ByteString
- Network.OAuth.OAuth2: fetchAccessToken :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: fetchAccessToken :: MonadIO m => Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2: fetchAccessToken2 :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: fetchAccessToken2 :: MonadIO m => Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2: fetchAccessTokenInternal :: ClientAuthenticationMethod -> Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: fetchAccessTokenInternal :: MonadIO m => ClientAuthenticationMethod -> Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2: refreshAccessToken :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: refreshAccessToken :: MonadIO m => Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2: refreshAccessToken2 :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: refreshAccessToken2 :: MonadIO m => Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2: refreshAccessTokenInternal :: ClientAuthenticationMethod -> Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2: refreshAccessTokenInternal :: MonadIO m => ClientAuthenticationMethod -> Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2.HttpClient: authGetBS :: Manager -> AccessToken -> URI -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authGetBS :: MonadIO m => Manager -> AccessToken -> URI -> ExceptT ByteString m ByteString
- Network.OAuth.OAuth2.HttpClient: authGetBS2 :: Manager -> AccessToken -> URI -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authGetBS2 :: MonadIO m => Manager -> AccessToken -> URI -> ExceptT ByteString m ByteString
- Network.OAuth.OAuth2.HttpClient: authGetBSInternal :: Set APIAuthenticationMethod -> Manager -> AccessToken -> URI -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authGetBSInternal :: MonadIO m => APIAuthenticationMethod -> Manager -> AccessToken -> URI -> ExceptT ByteString m ByteString
- Network.OAuth.OAuth2.HttpClient: authGetJSON :: FromJSON b => Manager -> AccessToken -> URI -> ExceptT ByteString IO b
+ Network.OAuth.OAuth2.HttpClient: authGetJSON :: (FromJSON a, MonadIO m) => Manager -> AccessToken -> URI -> ExceptT ByteString m a
- Network.OAuth.OAuth2.HttpClient: authGetJSONInternal :: FromJSON b => Set APIAuthenticationMethod -> Manager -> AccessToken -> URI -> ExceptT ByteString IO b
+ Network.OAuth.OAuth2.HttpClient: authGetJSONInternal :: (FromJSON a, MonadIO m) => APIAuthenticationMethod -> Manager -> AccessToken -> URI -> ExceptT ByteString m a
- Network.OAuth.OAuth2.HttpClient: authPostBS :: Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authPostBS :: MonadIO m => Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString m ByteString
- Network.OAuth.OAuth2.HttpClient: authPostBS2 :: Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authPostBS2 :: MonadIO m => Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString m ByteString
- Network.OAuth.OAuth2.HttpClient: authPostBS3 :: Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authPostBS3 :: MonadIO m => Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString m ByteString
- Network.OAuth.OAuth2.HttpClient: authPostBSInternal :: Set APIAuthenticationMethod -> Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO ByteString
+ Network.OAuth.OAuth2.HttpClient: authPostBSInternal :: MonadIO m => APIAuthenticationMethod -> Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString m ByteString
- Network.OAuth.OAuth2.HttpClient: authPostJSON :: FromJSON b => Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO b
+ Network.OAuth.OAuth2.HttpClient: authPostJSON :: (FromJSON a, MonadIO m) => Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString m a
- Network.OAuth.OAuth2.HttpClient: authPostJSONInternal :: FromJSON a => Set APIAuthenticationMethod -> Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString IO a
+ Network.OAuth.OAuth2.HttpClient: authPostJSONInternal :: (FromJSON a, MonadIO m) => APIAuthenticationMethod -> Manager -> AccessToken -> URI -> PostBody -> ExceptT ByteString m a
- Network.OAuth.OAuth2.TokenRequest: doJSONPostRequest :: (FromJSON err, FromJSON a) => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO a
+ Network.OAuth.OAuth2.TokenRequest: doJSONPostRequest :: (MonadIO m, FromJSON err, FromJSON a) => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) m a
- Network.OAuth.OAuth2.TokenRequest: doSimplePostRequest :: FromJSON err => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) IO ByteString
+ Network.OAuth.OAuth2.TokenRequest: doSimplePostRequest :: (MonadIO m, FromJSON err) => Manager -> OAuth2 -> URI -> PostBody -> ExceptT (OAuth2Error err) m ByteString
- Network.OAuth.OAuth2.TokenRequest: fetchAccessToken :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: fetchAccessToken :: MonadIO m => Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2.TokenRequest: fetchAccessToken2 :: Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: fetchAccessToken2 :: MonadIO m => Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2.TokenRequest: fetchAccessTokenInternal :: ClientAuthenticationMethod -> Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: fetchAccessTokenInternal :: MonadIO m => ClientAuthenticationMethod -> Manager -> OAuth2 -> ExchangeToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2.TokenRequest: refreshAccessToken :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: refreshAccessToken :: MonadIO m => Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2.TokenRequest: refreshAccessToken2 :: Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: refreshAccessToken2 :: MonadIO m => Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token
- Network.OAuth.OAuth2.TokenRequest: refreshAccessTokenInternal :: ClientAuthenticationMethod -> Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) IO OAuth2Token
+ Network.OAuth.OAuth2.TokenRequest: refreshAccessTokenInternal :: MonadIO m => ClientAuthenticationMethod -> Manager -> OAuth2 -> RefreshToken -> ExceptT (OAuth2Error Errors) m OAuth2Token

Files

LICENSE view
@@ -1,30 +1,21 @@-Copyright (c)2012-present, Haisheng Wu--All rights reserved.--Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions are met:+MIT License -    * Redistributions of source code must retain the above copyright-      notice, this list of conditions and the following disclaimer.+Copyright (c) 2022 Haisheng Wu -    * Redistributions in binary form must reproduce the above-      copyright notice, this list of conditions and the following-      disclaimer in the documentation and/or other materials provided-      with the distribution.+Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions: -    * Neither the name of Haisheng Wu nor the names of other-      contributors may be used to endorse or promote products derived-      from this software without specific prior written permission.+The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
README.org view
@@ -1,3 +1,8 @@ * Introduction -A mixed Haskell binding of [OAuth2 spec](https://datatracker.ietf.org/doc/html/rfc6749) and a little bit [OIDC spec](https://openid.net/specs/openid-connect-core-1_0.html).+Lightweight Haskell binding for++- [The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749)+  - If the provider does implement [OIDC spec](https://openid.net/specs/openid-connect-core-1_0.html),+    ID Token would also be included in token response (see `OAuth2Token`).+- [The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://www.rfc-editor.org/rfc/rfc6750)
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
hoauth2.cabal view
@@ -1,70 +1,75 @@-Cabal-version: 2.4-Name:                hoauth2--- http://wiki.haskell.org/Package_versioning_policy-Version:             2.5.0--Synopsis:            Haskell OAuth2 authentication client+cabal-version:      2.4+name:               hoauth2 -Description: Haskell OAuth2 authentication client. Tested with the following services:-             .-             * AzureAD: <https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code>-             .-             * Google: <https://developers.google.com/accounts/docs/OAuth2WebServer>-             .-             * Github: <http://developer.github.com/v3/oauth/>-             .-             * Facebook: <http://developers.facebook.com/docs/facebook-login/>-             .-             * Fitbit: <http://dev.fitbit.com/docs/oauth2/>-             .-             * StackExchange: <https://api.stackexchange.com/docs/authentication>-             .-             * DropBox: <https://www.dropbox.com/developers/reference/oauth-guide>-             .-             * Weibo: <http://open.weibo.com/wiki/Oauth2>-             .-             * Douban: <http://developers.douban.com/wiki/?title=oauth2>+-- http://wiki.haskell.org/Package_versioning_policy+version:            2.6.0+synopsis:           Haskell OAuth2 authentication client+description:+  Haskell OAuth2 authentication client.+  .+  Tutorial <https://github.com/freizl/hoauth2/tree/main/hoauth2-tutorial/>+  .+  Demo application <https://github.com/freizl/hoauth2-demo/tree/main/hoauth2-demo/> -Homepage:            https://github.com/freizl/hoauth2-License:             BSD-3-Clause-License-file:        LICENSE-Author:              Haisheng Wu-Maintainer:          Haisheng Wu <freizl@gmail.com>-Copyright:           Haisheng Wu-Category:            Network-Build-type:          Simple-Stability:           Beta-Tested-With:         GHC <= 8.10.7+homepage:           https://github.com/freizl/hoauth2+license:            MIT+license-file:       LICENSE+author:             Haisheng Wu+maintainer:         Haisheng Wu <freizl@gmail.com>+copyright:          Haisheng Wu+category:           Network+build-type:         Simple+stability:          Beta+tested-with:        GHC <=9.2.2+extra-source-files: README.org -Extra-source-files: README.org+source-repository head+  type:     git+  location: git://github.com/freizl/hoauth2.git -Source-Repository head-  Type:     git-  Location: git://github.com/freizl/hoauth2.git+library+  hs-source-dirs:     src+  default-language:   Haskell2010+  exposed-modules:+    Network.OAuth.OAuth2+    Network.OAuth.OAuth2.AuthorizationRequest+    Network.OAuth.OAuth2.HttpClient+    Network.OAuth.OAuth2.Internal+    Network.OAuth.OAuth2.TokenRequest+    Network.OAuth2.Experiment+    Network.OAuth2.Experiment.Pkce+    Network.OAuth2.Experiment.Types+    Network.OAuth2.Experiment.Utils -Library-  hs-source-dirs:   src-  default-language: Haskell2010-  Exposed-modules: Network.OAuth.OAuth2.HttpClient-                   Network.OAuth.OAuth2.Internal-                   Network.OAuth.OAuth2-                   Network.OAuth.OAuth2.TokenRequest-                   Network.OAuth.OAuth2.AuthorizationRequest+  default-extensions:+    DataKinds+    DeriveGeneric+    GeneralizedNewtypeDeriving+    ImportQualifiedPost+    OverloadedStrings+    RecordWildCards+    TypeApplications+    TypeFamilies -  Build-Depends: base                 >= 4     && < 5,-                 data-default         >= 0.7   && < 0.8,-                 binary               >= 0.8   && < 0.9,-                 containers           >= 0.6   && < 0.7,-                 text                 >= 0.11  && < 1.3,-                 bytestring           >= 0.9   && < 0.12,-                 http-conduit         >= 2.1   && < 2.4,-                 http-types           >= 0.11  && < 0.13,-                 aeson                >= 2.0   && < 2.2,-                 transformers         >= 0.5   && < 0.6,-                 uri-bytestring       >= 0.2.3 && < 0.4,-                 uri-bytestring-aeson >= 0.1   && < 0.2,-                 microlens            >= 0.4.0 && < 0.5,-                 exceptions           >= 0.8.3 && < 0.11+  build-depends:+    , aeson                 >=2.0   && <2.2+    , base                  >=4     && <5+    , base64                ^>=0.4+    , binary                ^>=0.8+    , bytestring            >=0.9   && <0.12+    , containers            ^>=0.6+    , cryptonite            ^>=0.30+    , data-default          ^>=0.7+    , exceptions            >=0.8.3 && <0.11+    , http-conduit          >=2.1   && <2.4+    , http-types            >=0.11  && <0.13+    , memory                ^>=0.17+    , microlens             ^>=0.4.0+    , text                  >=0.11  && <1.3+    , transformers          ^>=0.5+    , uri-bytestring        >=0.2.3 && <0.4+    , uri-bytestring-aeson  ^>=0.1 -  ghc-options: -Wall -fwarn-tabs -funbox-strict-fields-               -fno-warn-unused-do-bind -Wunused-packages+  ghc-options:+    -Wall -Wtabs -Wno-unused-do-bind -Wunused-packages -Wpartial-fields+    -Wwarnings-deprecations
src/Network/OAuth/OAuth2.hs view
@@ -1,17 +1,6 @@------------------------------------------------------------------------------------------------------------------------------- |--- Module      :  Network.OAuth.OAuth2--- Description :  OAuth2 client--- Copyright   :  (c) 2012 Haisheng Wu--- License     :  BSD-style (see the file LICENSE)--- Maintainer  :  Haisheng Wu <freizl@gmail.com>--- Stability   :  Beta--- Portability :  portable+-- | A lightweight oauth2 Haskell binding.+-- See Readme for more details ----- A lightweight oauth2 haskell binding. module Network.OAuth.OAuth2   ( module Network.OAuth.OAuth2.HttpClient,     module Network.OAuth.OAuth2.AuthorizationRequest,@@ -22,7 +11,7 @@  {-   Hiding Errors data type from default.-  Shall qualified import given the naming conflicts.+  Shall qualified import given the naming collision. -} import Network.OAuth.OAuth2.AuthorizationRequest hiding (Errors(..)) import Network.OAuth.OAuth2.HttpClient
src/Network/OAuth/OAuth2/AuthorizationRequest.hs view
@@ -1,12 +1,16 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} +-- | Bindings Authorization part of The OAuth 2.0 Authorization Framework+-- RFC6749 <https://www.rfc-editor.org/rfc/rfc6749> module Network.OAuth.OAuth2.AuthorizationRequest where  import Data.Aeson+import Data.Function (on)+import qualified Data.List as List import qualified Data.Text.Encoding as T-import GHC.Generics-import Lens.Micro+import GHC.Generics (Generic)+import Lens.Micro (over) import Network.OAuth.OAuth2.Internal import URI.ByteString @@ -23,7 +27,11 @@   toEncoding = genericToEncoding defaultOptions {constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True}  -- | Authorization Code Grant Error Responses https://tools.ietf.org/html/rfc6749#section-4.1.2.1--- Implicit Grant Error Responses https://tools.ietf.org/html/rfc6749#section-4.2.2.1+-- I found hard time to figure a way to test the authorization error flow+-- When anything wrong in @/authorize@ request (redirect to OAuth2 provider),+-- it will end-up at the Provider page hence no way for this library to parse error response.+-- In other words, @/authorize@ ends up with 4xx or 5xx.+-- Revisit this whenever find a case OAuth2 provider redirects back to Relying party with errors. data Errors   = InvalidRequest   | UnauthorizedClient@@ -40,13 +48,21 @@  -------------------------------------------------- +-- | See 'authorizationUrlWithParams'+authorizationUrl :: OAuth2 -> URI+authorizationUrl = authorizationUrlWithParams []+ -- | Prepare the authorization URL.  Redirect to this URL -- asking for user interactive authentication.-authorizationUrl :: OAuth2 -> URI-authorizationUrl oa = over (queryL . queryPairsL) (++ queryParts) (oauth2AuthorizeEndpoint oa)+--+-- @since 2.6.0+authorizationUrlWithParams :: QueryParams -> OAuth2 -> URI+authorizationUrlWithParams qs oa = over (queryL . queryPairsL) (++ queryParts) (oauth2AuthorizeEndpoint oa)   where     queryParts =-      [ ("client_id", T.encodeUtf8 $ oauth2ClientId oa),-        ("response_type", "code"),-        ("redirect_uri", serializeURIRef' $ oauth2RedirectUri oa)-      ]+      List.nubBy ((==) `on` fst) $+        qs+          ++ [ ("client_id", T.encodeUtf8 $ oauth2ClientId oa),+               ("response_type", "code"),+               ("redirect_uri", serializeURIRef' $ oauth2RedirectUri oa)+             ]
src/Network/OAuth/OAuth2/HttpClient.hs view
@@ -2,37 +2,43 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} --- | A simple http client to request OAuth2 tokens and several utils.+-- | Bindings for The OAuth 2.0 Authorization Framework: Bearer Token Usage+-- RFC6750 <https://www.rfc-editor.org/rfc/rfc6750> module Network.OAuth.OAuth2.HttpClient   ( -- * AUTH requests     authGetJSON,     authGetBS,     authGetBS2,+    authGetJSONWithAuthMethod,     authGetJSONInternal,+    authGetBSWithAuthMethod,     authGetBSInternal,     authPostJSON,     authPostBS,-    authPostBS1,     authPostBS2,     authPostBS3,+    authPostJSONWithAuthMethod,     authPostJSONInternal,+    authPostBSWithAuthMethod,     authPostBSInternal,++    -- * Types+    APIAuthenticationMethod (..),   ) where -import qualified Data.Set as Set-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Except-import Data.Aeson+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..), throwE)+import Data.Aeson (FromJSON, eitherDecode) import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as BSL-import Data.Maybe+import Data.Maybe (fromJust, isJust) import qualified Data.Text.Encoding as T-import Lens.Micro+import Lens.Micro (over) import Network.HTTP.Conduit import qualified Network.HTTP.Types as HT import Network.OAuth.OAuth2.Internal-import URI.ByteString+import URI.ByteString (URI, URIRef, queryL, queryPairsL)  -------------------------------------------------- @@ -45,67 +51,97 @@ -- | Conduct an authorized GET request and return response as JSON. --   Inject Access Token to Authorization Header. authGetJSON ::-  (FromJSON b) =>+  (FromJSON a, MonadIO m) =>   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   -- | Response as JSON-  ExceptT BSL.ByteString IO b-authGetJSON = authGetJSONInternal (Set.fromList [AuthInRequestHeader])-{-# DEPRECATED authGetJSON "use authGetJSONInternal" #-}+  ExceptT BSL.ByteString m a+authGetJSON = authGetJSONWithAuthMethod AuthInRequestHeader +authGetJSONInternal ::+  (FromJSON a, MonadIO m) =>+  APIAuthenticationMethod ->+  -- | HTTP connection manager.+  Manager ->+  AccessToken ->+  URI ->+  -- | Response as JSON+  ExceptT BSL.ByteString m a+authGetJSONInternal = authGetJSONWithAuthMethod+{-# DEPRECATED authGetJSONInternal "use authGetJSONWithAuthMethod" #-}+ -- | Conduct an authorized GET request and return response as JSON. --   Allow to specify how to append AccessToken.-authGetJSONInternal ::-  (FromJSON b) =>-  Set.Set APIAuthenticationMethod ->+--+-- @since 2.6.0+authGetJSONWithAuthMethod ::+  (MonadIO m, FromJSON a) =>+  APIAuthenticationMethod ->   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   -- | Response as JSON-  ExceptT BSL.ByteString IO b-authGetJSONInternal authTypes manager t uri = do-  resp <- authGetBSInternal authTypes manager t uri+  ExceptT BSL.ByteString m a+authGetJSONWithAuthMethod authTypes manager t uri = do+  resp <- authGetBSWithAuthMethod authTypes manager t uri   either (throwE . BSL.pack) return (eitherDecode resp)  -- | Conduct an authorized GET request. --   Inject Access Token to Authorization Header. authGetBS ::+  (MonadIO m) =>   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO BSL.ByteString-authGetBS = authGetBSInternal $ Set.fromList [AuthInRequestHeader]+  ExceptT BSL.ByteString m BSL.ByteString+authGetBS = authGetBSWithAuthMethod AuthInRequestHeader  -- | Same to 'authGetBS' but set access token to query parameter rather than header authGetBS2 ::+  (MonadIO m) =>   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO BSL.ByteString-authGetBS2 = authGetBSInternal $ Set.fromList [AuthInRequestQuery]-{-# DEPRECATED authGetBS2 "use authGetBSInternal" #-}+  ExceptT BSL.ByteString m BSL.ByteString+authGetBS2 = authGetBSWithAuthMethod AuthInRequestQuery+{-# DEPRECATED authGetBS2 "use authGetBSWithAuthMethod" #-} +authGetBSInternal ::+  (MonadIO m) =>+  APIAuthenticationMethod ->+  -- | HTTP connection manager.+  Manager ->+  AccessToken ->+  URI ->+  -- | Response as ByteString+  ExceptT BSL.ByteString m BSL.ByteString+authGetBSInternal = authGetBSWithAuthMethod+{-# DEPRECATED authGetBSInternal "use authGetBSWithAuthMethod" #-}+ -- | Conduct an authorized GET request and return response as ByteString. --   Allow to specify how to append AccessToken.-authGetBSInternal ::-  -- |-  Set.Set APIAuthenticationMethod ->+--+-- @since 2.6.0+authGetBSWithAuthMethod ::+  (MonadIO m) =>+  -- | Specify the way that how to append the 'AccessToken' in the request+  APIAuthenticationMethod ->   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO BSL.ByteString-authGetBSInternal authTypes manager token url = do-  let appendToUrl = AuthInRequestQuery `Set.member` authTypes-  let appendToHeader = AuthInRequestHeader `Set.member` authTypes+  ExceptT BSL.ByteString m BSL.ByteString+authGetBSWithAuthMethod authTypes manager token url = do+  let appendToUrl = AuthInRequestQuery == authTypes+  let appendToHeader = AuthInRequestHeader == authTypes   let uri = if appendToUrl then url `appendAccessToken` token else url   let upReq = updateRequestHeaders (if appendToHeader then Just token else Nothing) . setMethod HT.GET   req <- liftIO $ uriToRequest uri@@ -114,96 +150,116 @@ -- | Conduct POST request and return response as JSON. --   Inject Access Token to Authorization Header. authPostJSON ::-  (FromJSON b) =>+  (FromJSON a, MonadIO m) =>   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   PostBody ->   -- | Response as JSON-  ExceptT BSL.ByteString IO b-authPostJSON = authPostJSONInternal $ Set.fromList [AuthInRequestHeader]-{-# DEPRECATED authPostJSON "use authPostJSONInternal" #-}+  ExceptT BSL.ByteString m a+authPostJSON = authPostJSONWithAuthMethod AuthInRequestHeader --- | Conduct POST request and return response as JSON.---   Allow to specify how to append AccessToken. authPostJSONInternal ::-  FromJSON a =>-  Set.Set APIAuthenticationMethod ->+  (FromJSON a, MonadIO m) =>+  APIAuthenticationMethod ->   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   PostBody ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO a-authPostJSONInternal authTypes manager token url body = do-  resp <- authPostBSInternal authTypes manager token url body-  either (throwE . BSL.pack) return (eitherDecode resp)+  ExceptT BSL.ByteString m a+authPostJSONInternal = authPostJSONWithAuthMethod+{-# DEPRECATED authPostJSONInternal "use 'authPostJSONWithAuthMethod'" #-} --- | Conduct POST request.---   Inject Access Token to http header (Authorization)-authPostBS ::+-- | Conduct POST request and return response as JSON.+--   Allow to specify how to append AccessToken.+--+-- @since 2.6.0+authPostJSONWithAuthMethod ::+  (FromJSON a, MonadIO m) =>+  APIAuthenticationMethod ->   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   PostBody ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO BSL.ByteString-authPostBS = authPostBSInternal $ Set.fromList [AuthInRequestHeader]+  ExceptT BSL.ByteString m a+authPostJSONWithAuthMethod authTypes manager token url body = do+  resp <- authPostBSWithAuthMethod authTypes manager token url body+  either (throwE . BSL.pack) return (eitherDecode resp)  -- | Conduct POST request.---   Inject Access Token to both http header (Authorization) and request body.-authPostBS1 ::+--   Inject Access Token to http header (Authorization)+authPostBS ::+  (MonadIO m) =>   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   PostBody ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO BSL.ByteString-authPostBS1 = authPostBSInternal $ Set.fromList [AuthInRequestBody, AuthInRequestHeader]-{-# DEPRECATED authPostBS1 "use authPostBSInternal" #-}+  ExceptT BSL.ByteString m BSL.ByteString+authPostBS = authPostBSWithAuthMethod AuthInRequestHeader  -- | Conduct POST request with access token only in the request body but header. authPostBS2 ::+  (MonadIO m) =>   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   PostBody ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO BSL.ByteString-authPostBS2 = authPostBSInternal $ Set.fromList [AuthInRequestBody]-{-# DEPRECATED authPostBS2 "use authPostBSInternal" #-}+  ExceptT BSL.ByteString m BSL.ByteString+authPostBS2 = authPostBSWithAuthMethod AuthInRequestBody+{-# DEPRECATED authPostBS2 "use 'authPostBSWithAuthMethod'" #-}  -- | Conduct POST request with access token only in the header and not in body authPostBS3 ::+  (MonadIO m) =>   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   PostBody ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO BSL.ByteString-authPostBS3 = authPostBSInternal $ Set.fromList [AuthInRequestHeader]-{-# DEPRECATED authPostBS3 "use authPostBSInternal" #-}+  ExceptT BSL.ByteString m BSL.ByteString+authPostBS3 = authPostBSWithAuthMethod AuthInRequestHeader+{-# DEPRECATED authPostBS3 "use 'authPostBSWithAuthMethod'" #-} +authPostBSInternal ::+  (MonadIO m) =>+  APIAuthenticationMethod ->+  -- | HTTP connection manager.+  Manager ->+  AccessToken ->+  URI ->+  PostBody ->+  -- | Response as ByteString+  ExceptT BSL.ByteString m BSL.ByteString+authPostBSInternal = authPostBSWithAuthMethod+{-# DEPRECATED authPostBSInternal "use 'authPostBSWithAuthMethod'" #-}+ -- | Conduct POST request and return response as ByteString. --   Allow to specify how to append AccessToken.-authPostBSInternal ::-  Set.Set APIAuthenticationMethod ->+--+-- @since 2.6.0+authPostBSWithAuthMethod ::+  (MonadIO m) =>+  APIAuthenticationMethod ->   -- | HTTP connection manager.   Manager ->   AccessToken ->   URI ->   PostBody ->   -- | Response as ByteString-  ExceptT BSL.ByteString IO BSL.ByteString-authPostBSInternal authTypes manager token url body = do-  let appendToBody = AuthInRequestBody `Set.member` authTypes-  let appendToHeader = AuthInRequestHeader `Set.member` authTypes+  ExceptT BSL.ByteString m BSL.ByteString+authPostBSWithAuthMethod authTypes manager token url body = do+  let appendToBody = AuthInRequestBody == authTypes+  let appendToHeader = AuthInRequestHeader == authTypes   let reqBody = if appendToBody then body ++ accessTokenToParam token else body   -- TODO: urlEncodedBody send request as 'application/x-www-form-urlencoded'   -- seems shall go with application/json which is more common?@@ -211,27 +267,44 @@   let upHeaders = updateRequestHeaders (if appendToHeader then Just token else Nothing) . setMethod HT.POST   let upReq = upHeaders . upBody -  req <- uriToRequest url+  req <- liftIO $ uriToRequest url   authRequest req upReq manager  -------------------------------------------------- +-- * Types++--------------------------------------------------++-- | https://www.rfc-editor.org/rfc/rfc6750#section-2+data APIAuthenticationMethod+  = -- | Provides in Authorization header+    AuthInRequestHeader+  | -- | Provides in request body+    AuthInRequestBody+  | -- | Provides in request query parameter+    AuthInRequestQuery+  deriving (Eq, Ord)++--------------------------------------------------+ -- * Utilities  --------------------------------------------------  -- | Send an HTTP request. authRequest ::+  (MonadIO m) =>   -- | Request to perform   Request ->   -- | Modify request before sending   (Request -> Request) ->   -- | HTTP connection manager.   Manager ->-  ExceptT BSL.ByteString IO BSL.ByteString+  ExceptT BSL.ByteString m BSL.ByteString authRequest req upReq manage = ExceptT $ handleResponse <$> httpLbs (upReq req) manage --- | Parses a @Response@ to to @OAuth2Result@+-- | Get response body out of a @Response@ handleResponse :: Response BSL.ByteString -> Either BSL.ByteString BSL.ByteString handleResponse rsp =   if HT.statusIsSuccessful (responseStatus rsp)
src/Network/OAuth/OAuth2/Internal.hs view
@@ -3,10 +3,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-}-{-# OPTIONS_HADDOCK -ignore-exports #-} --- | A simple OAuth2 Haskell binding.  (This is supposed to be--- independent of the http client used.) module Network.OAuth.OAuth2.Internal where  import Control.Applicative@@ -63,36 +60,41 @@  newtype IdToken = IdToken {idtoken :: Text} deriving (Binary, Eq, Show, FromJSON, ToJSON) +-- | Authorization Code newtype ExchangeToken = ExchangeToken {extoken :: Text} deriving (Show, FromJSON, ToJSON) --- | The gained Access Token. Use @Data.Aeson.decode@ to--- decode string to @AccessToken@.  The @refreshToken@ is--- special in some cases,--- e.g. <https://developers.google.com/accounts/docs/OAuth2>+-- | https://www.rfc-editor.org/rfc/rfc6749#section-4.1.4 data OAuth2Token = OAuth2Token   { accessToken :: AccessToken,+    -- | Exists when @offline_access@ scope is in the 'authorizeUrl' and the provider supports Refresh Access Token.     refreshToken :: Maybe RefreshToken,     expiresIn :: Maybe Int,+    -- | See https://www.rfc-editor.org/rfc/rfc6749#section-5.1. It's required per spec. But OAuth2 provider implementation are vary. Maybe will remove 'Maybe' in future release.     tokenType :: Maybe Text,+    -- | Exists when @openid@ scope is in the 'authorizeUrl' and the provider supports OpenID.     idToken :: Maybe IdToken   }   deriving (Eq, Show, Generic)  instance Binary OAuth2Token -parseIntFlexible :: Value -> Parser Int-parseIntFlexible (String s) = pure . read $ unpack s-parseIntFlexible v = parseJSON v- -- | Parse JSON data into 'OAuth2Token' instance FromJSON OAuth2Token where   parseJSON = withObject "OAuth2Token" $ \v ->     OAuth2Token-      <$> v .: "access_token"-      <*> v .:? "refresh_token"+      <$> v+      .: "access_token"+      <*> v+      .:? "refresh_token"       <*> explicitParseFieldMaybe parseIntFlexible v "expires_in"-      <*> v .:? "token_type"-      <*> v .:? "id_token"+      <*> v+      .:? "token_type"+      <*> v+      .:? "id_token"+    where+      parseIntFlexible :: Value -> Parser Int+      parseIntFlexible (String s) = pure . read $ unpack s+      parseIntFlexible v = parseJSON v  instance ToJSON OAuth2Token where   toJSON = genericToJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}@@ -129,15 +131,18 @@     (Just $ pack $ "Error: " <> err <> "\n Original Response:\n" <> show (decodeUtf8 $ BSL.toStrict response))     Nothing -data APIAuthenticationMethod-  = -- | Provides in Authorization header-    AuthInRequestHeader-  | -- | Provides in request body-    AuthInRequestBody-  | -- | Provides in request query parameter-    AuthInRequestQuery-  deriving (Eq, Ord)-+-- | https://www.rfc-editor.org/rfc/rfc6749#section-2.3+-- According to spec:+--+-- The client MUST NOT use more than one authentication method in each request.+--+-- Which means use Authorization header or Post body.+--+-- However, in reality, I always have to include authentication in the header.+--+-- In other words, 'ClientSecrectBasic' is always assured. 'ClientSecretPost' is optional.+--+-- Maybe consider an alternative implementation that boolean kind of data type is good enough. data ClientAuthenticationMethod   = ClientSecretBasic   | ClientSecretPost
src/Network/OAuth/OAuth2/TokenRequest.hs view
@@ -1,20 +1,23 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} +-- | Bindings Access Token and Refresh Token part of The OAuth 2.0 Authorization Framework+-- RFC6749 <https://www.rfc-editor.org/rfc/rfc6749> module Network.OAuth.OAuth2.TokenRequest where -import Control.Monad.Trans.Except+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..), throwE) import Data.Aeson import qualified Data.Aeson.Key as Key import qualified Data.Aeson.KeyMap as KeyMap import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.Text.Encoding as T-import GHC.Generics+import GHC.Generics (Generic) import Network.HTTP.Conduit import qualified Network.HTTP.Types as HT import Network.HTTP.Types.URI (parseQuery) import Network.OAuth.OAuth2.Internal-import URI.ByteString+import URI.ByteString (URI, serializeURIRef')  -------------------------------------------------- @@ -60,13 +63,12 @@         ]    in (uri, body) --- | Using a Refresh Token.  Obtain a new access token by--- sending a refresh token to the Authorization server.+-- | Obtain a new access token by sending a Refresh Token to the Authorization server. refreshAccessTokenUrl ::   OAuth2 ->-  -- | refresh token gained via authorization URL+  -- | Refresh Token gained via authorization URL   RefreshToken ->-  -- | refresh token request URL plus the request body.+  -- | Refresh Token request URL plus the request body.   (URI, PostBody) refreshAccessTokenUrl oa token = (uri, body)   where@@ -76,29 +78,15 @@         ("refresh_token", T.encodeUtf8 $ rtoken token)       ] -clientSecretPost :: OAuth2 -> PostBody-clientSecretPost oa =-  [ ("client_id", T.encodeUtf8 $ oauth2ClientId oa),-    ("client_secret", T.encodeUtf8 $ oauth2ClientSecret oa)-  ]- --------------------------------------------------  -- * Token management  -------------------------------------------------- --- | Fetch OAuth2 Token with authenticate in request header.------ OAuth2 spec allows `client_id` and `client_secret` to--- either be sent in the header (as basic authentication)--- OR as form/url params.--- The OAuth server can choose to implement only one, or both.--- Unfortunately, there is no way for the OAuth client (i.e. this library) to--- know which method to use.--- Please take a look at the documentation of the--- service that you are integrating with and either use `fetchAccessToken` or `fetchAccessToken2`+-- | Exchange @code@ for an Access Token with authenticate in request header. fetchAccessToken ::+  (MonadIO m) =>   -- | HTTP connection manager   Manager ->   -- | OAuth Data@@ -106,79 +94,124 @@   -- | OAuth2 Code   ExchangeToken ->   -- | Access Token-  ExceptT (OAuth2Error Errors) IO OAuth2Token-fetchAccessToken = fetchAccessTokenInternal ClientSecretBasic+  ExceptT (OAuth2Error Errors) m OAuth2Token+fetchAccessToken = fetchAccessTokenWithAuthMethod ClientSecretBasic  fetchAccessToken2 ::+  (MonadIO m) =>   -- | HTTP connection manager   Manager ->   -- | OAuth Data   OAuth2 ->-  -- | OAuth 2 Tokens+  -- | Authorization Code   ExchangeToken ->   -- | Access Token-  ExceptT (OAuth2Error Errors) IO OAuth2Token-fetchAccessToken2 = fetchAccessTokenInternal ClientSecretPost-{-# DEPRECATED fetchAccessToken2 "renamed to fetchAccessTokenInternal" #-}+  ExceptT (OAuth2Error Errors) m OAuth2Token+fetchAccessToken2 = fetchAccessTokenWithAuthMethod ClientSecretPost+{-# DEPRECATED fetchAccessToken2 "use 'fetchAccessTokenWithAuthMethod'" #-}  fetchAccessTokenInternal ::+  (MonadIO m) =>   ClientAuthenticationMethod ->   -- | HTTP connection manager   Manager ->   -- | OAuth Data   OAuth2 ->-  -- | OAuth 2 Tokens+  -- | Authorization Code   ExchangeToken ->   -- | Access Token-  ExceptT (OAuth2Error Errors) IO OAuth2Token-fetchAccessTokenInternal authMethod manager oa code = do+  ExceptT (OAuth2Error Errors) m OAuth2Token+fetchAccessTokenInternal = fetchAccessTokenWithAuthMethod+{-# DEPRECATED fetchAccessTokenInternal "use 'fetchAccessTokenWithAuthMethod'" #-}++-- | Exchange @code@ for an Access Token+--+-- OAuth2 spec allows credential (`client_id`, `client_secret`) to be sent+-- either in the header (a.k.a 'ClientSecretBasic').+-- or as form/url params (a.k.a 'ClientSecretPost').+--+-- The OAuth provider can choose to implement only one, or both.+-- Look for API document from the OAuth provider you're dealing with.+-- If you're uncertain, try 'fetchAccessToken' which sends credential+-- in authorization http header, which is common case.+--+-- @since 2.6.0+fetchAccessTokenWithAuthMethod ::+  (MonadIO m) =>+  ClientAuthenticationMethod ->+  -- | HTTP connection manager+  Manager ->+  -- | OAuth Data+  OAuth2 ->+  -- | Authorization Code+  ExchangeToken ->+  -- | Access Token+  ExceptT (OAuth2Error Errors) m OAuth2Token+fetchAccessTokenWithAuthMethod authMethod manager oa code = do   let (uri, body) = accessTokenUrl oa code   let extraBody = if authMethod == ClientSecretPost then clientSecretPost oa else []   doJSONPostRequest manager oa uri (body ++ extraBody) --- doJSONPostRequest append client secret to header which is needed for both--- client_secret_post and client_secret_basic---- | Fetch a new AccessToken with the Refresh Token with authentication in request header.------ OAuth2 spec allows `client_id` and `client_secret` to--- either be sent in the header (as basic authentication)--- OR as form/url params.--- The OAuth server can choose to implement only one, or both.--- Unfortunately, there is no way for the OAuth client (i.e. this library) to--- know which method to use. Please take a look at the documentation of the--- service that you are integrating with and either use `refreshAccessToken` or `refreshAccessToken2`+-- | Fetch a new AccessToken using the Refresh Token with authentication in request header. refreshAccessToken ::+  (MonadIO m) =>   -- | HTTP connection manager.   Manager ->   -- | OAuth context   OAuth2 ->-  -- | refresh token gained after authorization+  -- | Refresh Token gained after authorization   RefreshToken ->-  ExceptT (OAuth2Error Errors) IO OAuth2Token-refreshAccessToken = refreshAccessTokenInternal ClientSecretBasic+  ExceptT (OAuth2Error Errors) m OAuth2Token+refreshAccessToken = refreshAccessTokenWithAuthMethod ClientSecretBasic  refreshAccessToken2 ::+  (MonadIO m) =>   -- | HTTP connection manager.   Manager ->   -- | OAuth context   OAuth2 ->-  -- | refresh token gained after authorization+  -- | Refresh Token gained after authorization   RefreshToken ->-  ExceptT (OAuth2Error Errors) IO OAuth2Token-refreshAccessToken2 = refreshAccessTokenInternal ClientSecretPost-{-# DEPRECATED refreshAccessToken2 "renamed to fetchAccessTokenInternal" #-}+  ExceptT (OAuth2Error Errors) m OAuth2Token+refreshAccessToken2 = refreshAccessTokenWithAuthMethod ClientSecretPost+{-# DEPRECATED refreshAccessToken2 "use 'refreshAccessTokenWithAuthMethod'" #-}  refreshAccessTokenInternal ::+  (MonadIO m) =>   ClientAuthenticationMethod ->   -- | HTTP connection manager.   Manager ->   -- | OAuth context   OAuth2 ->-  -- | refresh token gained after authorization+  -- | Refresh Token gained after authorization   RefreshToken ->-  ExceptT (OAuth2Error Errors) IO OAuth2Token-refreshAccessTokenInternal authMethod manager oa token = do+  ExceptT (OAuth2Error Errors) m OAuth2Token+refreshAccessTokenInternal = refreshAccessTokenWithAuthMethod+{-# DEPRECATED refreshAccessTokenInternal "use 'refreshAccessTokenWithAuthMethod'" #-}++-- | Fetch a new AccessToken using the Refresh Token.+--+-- OAuth2 spec allows credential (`client_id`, `client_secret`) to be sent+-- either in the header (a.k.a 'ClientSecretBasic').+-- or as form/url params (a.k.a 'ClientSecretPost').+--+-- The OAuth provider can choose to implement only one, or both.+-- Look for API document from the OAuth provider you're dealing with.+-- If you're uncertain, try 'refreshAccessToken' which sends credential+-- in authorization http header, which is common case.+--+-- @since 2.6.0+refreshAccessTokenWithAuthMethod ::+  (MonadIO m) =>+  ClientAuthenticationMethod ->+  -- | HTTP connection manager.+  Manager ->+  -- | OAuth context+  OAuth2 ->+  -- | Refresh Token gained after authorization+  RefreshToken ->+  ExceptT (OAuth2Error Errors) m OAuth2Token+refreshAccessTokenWithAuthMethod authMethod manager oa token = do   let (uri, body) = refreshAccessTokenUrl oa token   let extraBody = if authMethod == ClientSecretPost then clientSecretPost oa else []   doJSONPostRequest manager oa uri (body ++ extraBody)@@ -191,7 +224,7 @@  -- | Conduct post request and return response as JSON. doJSONPostRequest ::-  (FromJSON err, FromJSON a) =>+  (MonadIO m, FromJSON err, FromJSON a) =>   -- | HTTP connection manager.   Manager ->   -- | OAuth options@@ -201,7 +234,7 @@   -- | request body   PostBody ->   -- | Response as JSON-  ExceptT (OAuth2Error err) IO a+  ExceptT (OAuth2Error err) m a doJSONPostRequest manager oa uri body = do   resp <- doSimplePostRequest manager oa uri body   case parseResponseFlexible resp of@@ -210,7 +243,7 @@  -- | Conduct post request. doSimplePostRequest ::-  FromJSON err =>+  (MonadIO m, FromJSON err) =>   -- | HTTP connection manager.   Manager ->   -- | OAuth options@@ -220,9 +253,9 @@   -- | Request body.   PostBody ->   -- | Response as ByteString-  ExceptT (OAuth2Error err) IO BSL.ByteString+  ExceptT (OAuth2Error err) m BSL.ByteString doSimplePostRequest manager oa url body =-  ExceptT $ fmap handleOAuth2TokenResponse go+  ExceptT . liftIO $ fmap handleOAuth2TokenResponse go   where     addBasicAuth = applyBasicAuth (T.encodeUtf8 $ oauth2ClientId oa) (T.encodeUtf8 $ oauth2ClientSecret oa)     go = do@@ -230,14 +263,14 @@       let req' = (addBasicAuth . addDefaultRequestHeaders) req       httpLbs (urlEncodedBody body req') manager --- | Parses a @Response@ to to @OAuth2Result@+-- | Gets response body from a @Response@ if 200 otherwise assume 'OAuth2Error' handleOAuth2TokenResponse :: FromJSON err => Response BSL.ByteString -> Either (OAuth2Error err) BSL.ByteString handleOAuth2TokenResponse rsp =   if HT.statusIsSuccessful (responseStatus rsp)     then Right $ responseBody rsp     else Left $ parseOAuth2Error (responseBody rsp) --- | Try 'parseResponseJSON', if failed then parses the @OAuth2Result BSL.ByteString@ that contains not JSON but a Query String.+-- | Try to parses response as JSON, if failed, try to parse as like query string. parseResponseFlexible ::   (FromJSON err, FromJSON a) =>   BSL.ByteString ->@@ -246,7 +279,7 @@   Left _ -> parseResponseString r   Right x -> Right x --- | Parses a @OAuth2Result BSL.ByteString@ that contains not JSON but a Query String+-- | Parses the response that contains not JSON but a Query String parseResponseString ::   (FromJSON err, FromJSON a) =>   BSL.ByteString ->@@ -268,3 +301,10 @@ addDefaultRequestHeaders req =   let headers = defaultRequestHeaders ++ requestHeaders req    in req {requestHeaders = headers}++-- | Add Credential (client_id, client_secret) to the request post body.+clientSecretPost :: OAuth2 -> PostBody+clientSecretPost oa =+  [ ("client_id", T.encodeUtf8 $ oauth2ClientId oa),+    ("client_secret", T.encodeUtf8 $ oauth2ClientSecret oa)+  ]
+ src/Network/OAuth2/Experiment.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-- | This module contains a new way of doing OAuth2 authorization and authentication+-- in order to obtain Access Token and maybe Refresh Token base on rfc6749.+--+-- This module will become default in future release. (TBD but likely 3.0).+--+-- The key concept/change is to introduce the 'GrantTypeFlow', which determines the entire work flow per spec.+-- Each work flow will have slight different request parameters, which often time you'll see+-- different configuration when creating OAuth2 application in the IdP developer application page.+--+-- Here are supported flows+--+-- 1. Authorization Code. This flow requires authorize call to obtain an authorize code,+-- then exchange the code for tokens.+--+-- 2. Resource Owner Password. This flow only requires to hit token endpoint with, of course,+-- username and password, to obtain tokens.+--+-- 3. Client Credentials. This flow also only requires to hit token endpoint but with different parameters.+-- Client credentials flow does not involve an end user hence you won't be able to hit userinfo endpoint+-- with access token obtained.+--+-- 5. PKCE (rfc7636). This is enhancement on top of authorization code flow.+--+-- Implicit flow is not supported because it is more for SPA (single page app)+-- and more or less obsolete by Authorization Code flow with PKCE.+--+-- Here is quick sample for how to use vocabularies from this new module.+--+-- Firstly, initialize your IdP (use google as example) and the application.+--+-- @+-- data Google = Google deriving (Eq, Show)+-- googleIdp = Idp Google+--   Idp+--     { idpFetchUserInfo = authGetJSON @(IdpUserInfo Google),+--       idpAuthorizeEndpoint = [uri|https:\/\/accounts.google.com\/o\/oauth2\/v2\/auth|],+--       idpTokenEndpoint = [uri|https:\/\/oauth2.googleapis.com\/token|],+--       idpUserInfoEndpoint = [uri|https:\/\/www.googleapis.com\/oauth2\/v2\/userinfo|]+--     }+--+-- fooApp :: IdpApplication 'AuthorizationCode Google+-- fooApp =+--   AuthorizationCodeIdpApplication+--     { idpAppClientId = "xxxxx",+--       idpAppClientSecret = "xxxxx",+--       idpAppScope =+--         Set.fromList+--           [ \"https:\/\/www.googleapis.com\/auth\/userinfo.email\",+--             \"https:\/\/www.googleapis.com\/auth\/userinfo.profile\"+--           ],+--       idpAppAuthorizeState = \"CHANGE_ME\",+--       idpAppAuthorizeExtraParams = Map.empty,+--       idpAppRedirectUri = [uri|http:\/\/localhost\/oauth2\/callback|],+--       idpAppName = "default-google-App",+--       idpAppTokenRequestAuthenticationMethod = ClientSecretBasic,+--       idp = googleIdp+--     }+-- @+--+-- Secondly, construct the authorize URL.+--+-- @+-- authorizeUrl = mkAuthorizeRequest fooApp+-- @+--+-- Thirdly, after a successful redirect with authorize code,+-- you could exchange for access token+--+-- @+-- mgr <- liftIO $ newManager tlsManagerSettings+-- tokenResp <- conduitTokenRequest fooApp mgr authorizeCode+-- @+--+-- Lastly, you probably like to fetch user info+--+-- @+-- conduitUserInfoRequest fooApp mgr (accessToken tokenResp)+-- @+--+-- Also you could find example from @hoauth2-providers-tutorials@ module.+--+module Network.OAuth2.Experiment+  ( module Network.OAuth2.Experiment.Types,+    module Network.OAuth2.Experiment.Pkce+  )+where++import Network.OAuth2.Experiment.Pkce+import Network.OAuth2.Experiment.Types
+ src/Network/OAuth2/Experiment/Pkce.hs view
@@ -0,0 +1,82 @@+module Network.OAuth2.Experiment.Pkce+  ( mkPkceParam,+    CodeChallenge (..),+    CodeVerifier (..),+    CodeChallengeMethod (..),+    PkceRequestParam (..),+  )+where++import Control.Monad.IO.Class+import Crypto.Hash qualified as H+import Crypto.Random qualified as Crypto+import Data.ByteArray qualified as ByteArray+import Data.ByteString qualified as BS+import Data.ByteString.Base64.URL qualified as B64+import Data.Text (Text)+import Data.Text.Encoding qualified as T+import Data.Word++newtype CodeChallenge = CodeChallenge {unCodeChallenge :: Text}++newtype CodeVerifier = CodeVerifier {unCodeVerifier :: Text} deriving (Show)++data CodeChallengeMethod = S256+  deriving (Show)++data PkceRequestParam = PkceRequestParam+  { codeVerifier :: CodeVerifier,+    codeChallenge :: CodeChallenge,+    -- | spec says optional but really it shall be s256 or can be omitted?+    -- https://datatracker.ietf.org/doc/html/rfc7636#section-4.3+    codeChallengeMethod :: CodeChallengeMethod+  }++mkPkceParam :: MonadIO m => m PkceRequestParam+mkPkceParam = do+  codeV <- genCodeVerifier+  pure+    PkceRequestParam+      { codeVerifier = CodeVerifier (T.decodeUtf8 codeV),+        codeChallenge = CodeChallenge (encodeCodeVerifier codeV),+        codeChallengeMethod = S256+      }++encodeCodeVerifier :: BS.ByteString -> Text+encodeCodeVerifier = B64.encodeBase64Unpadded . BS.pack . ByteArray.unpack . hashSHA256++genCodeVerifier :: MonadIO m => m BS.ByteString+genCodeVerifier = liftIO $ getBytesInternal BS.empty++cvMaxLen :: Int+cvMaxLen = 128++-- The default 'getRandomBytes' generates bytes out of unreverved characters scope.+-- code-verifier = 43*128unreserved+--   unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"+--   ALPHA = %x41-5A / %x61-7A+--   DIGIT = %x30-39+getBytesInternal :: BS.ByteString -> IO BS.ByteString+getBytesInternal ba+  | BS.length ba >= cvMaxLen = pure (BS.take cvMaxLen ba)+  | otherwise = do+      bs <- Crypto.getRandomBytes cvMaxLen+      let bsUnreserved = ba `BS.append` BS.filter isUnreversed bs+      getBytesInternal bsUnreserved++hashSHA256 :: BS.ByteString -> H.Digest H.SHA256+hashSHA256 = H.hash++isUnreversed :: Word8 -> Bool+isUnreversed w = w `BS.elem` unreverseBS++{-+a-z: 97-122+A-Z: 65-90+-: 45+.: 46+_: 95+~: 126+-}+unreverseBS :: BS.ByteString+unreverseBS = BS.pack $ [97 .. 122] ++ [65 .. 90] ++ [45, 46, 95, 126]
+ src/Network/OAuth2/Experiment/Types.hs view
@@ -0,0 +1,693 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Network.OAuth2.Experiment.Types where++import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT)+import Data.Aeson (FromJSON)+import Data.Bifunctor+import Data.ByteString.Lazy.Char8 qualified as BSL+import Data.Default (Default (def))+import Data.Kind+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Set (Set)+import Data.Set qualified as Set+import Data.String+import Data.Text.Encoding qualified as T+import Data.Text.Lazy (Text)+import Data.Text.Lazy qualified as TL+import Network.HTTP.Conduit (Manager)+import Network.OAuth.OAuth2 hiding (RefreshToken)+import Network.OAuth.OAuth2 qualified as OAuth2+import Network.OAuth.OAuth2.TokenRequest qualified as TR+import Network.OAuth2.Experiment.Pkce+import Network.OAuth2.Experiment.Utils+import URI.ByteString hiding (UserInfo)++{- NOTE+  1. shall I lift the constrain of all 'a :: GrantTypeFlow' so that user has max customization/flexibility?+-}++-------------------------------------------------------------------------------++-- * Grant Type++-------------------------------------------------------------------------------++data GrantTypeFlow = AuthorizationCode | ResourceOwnerPassword | ClientCredentials++-------------------------------------------------------------------------------++-- * Response Type value++-------------------------------------------------------------------------------++class ToResponseTypeValue (a :: GrantTypeFlow) where+  toResponseTypeValue :: IsString b => b++instance ToResponseTypeValue 'AuthorizationCode where+  -- https://www.rfc-editor.org/rfc/rfc6749#section-3.1.1+  -- Only support "authorization code" flow+  toResponseTypeValue :: IsString b => b+  toResponseTypeValue = "code"++toResponseTypeParam :: forall a b req. (ToResponseTypeValue a, IsString b) => req a -> Map b b+toResponseTypeParam _ = Map.singleton "response_type" (toResponseTypeValue @a)++-------------------------------------------------------------------------------++-- * Grant Type value++-------------------------------------------------------------------------------++-- | Grant type query parameter has association with 'GrantTypeFlow' but not completely strict.+--+-- e.g. Both 'AuthorizationCode' and 'ResourceOwnerPassword' flow could support refresh token flow.+data GrantTypeValue = GTAuthorizationCode | GTPassword | GTClientCredentials | GTRefreshToken+  deriving (Eq, Show)++-------------------------------------------------------------------------------++-- * Scope++-------------------------------------------------------------------------------++-- TODO: following data type is not ideal as Idp would have lots of 'Custom Text'+--+-- @+-- data Scope = OPENID | PROFILE | EMAIL | OFFLINE_ACCESS | Custom Text+-- @+--+-- Would be nice to define Enum for standard Scope, plus allow user to define their own define (per Idp) and plugin somehow.+newtype Scope = Scope {unScope :: Text}+  deriving (Show, Eq, Ord)++instance IsString Scope where+  fromString :: String -> Scope+  fromString = Scope . TL.pack++-------------------------------------------------------------------------------++-- * Credentials++-------------------------------------------------------------------------------+newtype ClientId = ClientId {unClientId :: Text}+  deriving (Show, Eq, IsString)++newtype ClientSecret = ClientSecret {unClientSecret :: Text}+  deriving (Eq, IsString)++-- | In order to reuse some methods from legacy "Network.OAuth.OAuth2".+-- Will be removed when Experiment module becomes default.+toOAuth2Key :: ClientId -> ClientSecret -> OAuth2+toOAuth2Key cid csecret =+  def+    { oauth2ClientId = TL.toStrict $ unClientId cid,+      oauth2ClientSecret = TL.toStrict $ unClientSecret csecret+    }++newtype RedirectUri = RedirectUri {unRedirectUri :: URI}+  deriving (Eq)++newtype AuthorizeState = AuthorizeState {unAuthorizeState :: Text}+  deriving (Eq)++instance IsString AuthorizeState where+  fromString :: String -> AuthorizeState+  fromString = AuthorizeState . TL.pack++newtype Username = Username {unUsername :: Text}+  deriving (Eq)++instance IsString Username where+  fromString :: String -> Username+  fromString = Username . TL.pack++newtype Password = Password {unPassword :: Text}+  deriving (Eq)++instance IsString Password where+  fromString :: String -> Password+  fromString = Password . TL.pack++-------------------------------------------------------------------------------++-- * Query parameters++-------------------------------------------------------------------------------+class ToQueryParam a where+  toQueryParam :: a -> Map Text Text++instance ToQueryParam a => ToQueryParam (Maybe a) where+  toQueryParam :: ToQueryParam a => Maybe a -> Map Text Text+  toQueryParam Nothing = Map.empty+  toQueryParam (Just a) = toQueryParam a++instance ToQueryParam GrantTypeValue where+  toQueryParam :: GrantTypeValue -> Map Text Text+  toQueryParam x = Map.singleton "grant_type" (val x)+    where+      val :: GrantTypeValue -> Text+      val GTAuthorizationCode = "authorization_code"+      val GTPassword = "password"+      val GTClientCredentials = "client_credentials"+      val GTRefreshToken = "refresh_token"++instance ToQueryParam ClientId where+  toQueryParam :: ClientId -> Map Text Text+  toQueryParam (ClientId i) = Map.singleton "client_id" i++instance ToQueryParam ClientSecret where+  toQueryParam :: ClientSecret -> Map Text Text+  toQueryParam (ClientSecret x) = Map.singleton "client_secret" x++instance ToQueryParam Username where+  toQueryParam :: Username -> Map Text Text+  toQueryParam (Username x) = Map.singleton "username" x++instance ToQueryParam Password where+  toQueryParam :: Password -> Map Text Text+  toQueryParam (Password x) = Map.singleton "password" x++instance ToQueryParam AuthorizeState where+  toQueryParam :: AuthorizeState -> Map Text Text+  toQueryParam (AuthorizeState x) = Map.singleton "state" x++instance ToQueryParam RedirectUri where+  toQueryParam (RedirectUri uri) = Map.singleton "redirect_uri" (bs8ToLazyText $ serializeURIRef' uri)++instance ToQueryParam (Set Scope) where+  toQueryParam :: Set Scope -> Map Text Text+  toQueryParam = toScopeParam . Set.map unScope+    where+      toScopeParam :: (IsString a) => Set Text -> Map a Text+      toScopeParam scope = Map.singleton "scope" (TL.intercalate " " $ Set.toList scope)++instance ToQueryParam CodeVerifier where+  toQueryParam :: CodeVerifier -> Map Text Text+  toQueryParam (CodeVerifier x) = Map.singleton "code_verifier" (TL.fromStrict x)++instance ToQueryParam CodeChallenge where+  toQueryParam :: CodeChallenge -> Map Text Text+  toQueryParam (CodeChallenge x) = Map.singleton "code_challenge" (TL.fromStrict x)++instance ToQueryParam CodeChallengeMethod where+  toQueryParam :: CodeChallengeMethod -> Map Text Text+  toQueryParam x = Map.singleton "code_challenge_method" (TL.pack $ show x)++instance ToQueryParam ExchangeToken where+  toQueryParam :: ExchangeToken -> Map Text Text+  toQueryParam (ExchangeToken x) = Map.singleton "code" (TL.fromStrict x)++instance ToQueryParam OAuth2.RefreshToken where+  toQueryParam :: OAuth2.RefreshToken -> Map Text Text+  toQueryParam (OAuth2.RefreshToken x) = Map.singleton "refresh_token" (TL.fromStrict x)++-------------------------------------------------------------------------------++-- * Authorization and Token Requests types++-------------------------------------------------------------------------------++class HasIdpAppName (a :: GrantTypeFlow) where+  getIdpAppName :: IdpApplication a i -> Text++class HasAuthorizeRequest (a :: GrantTypeFlow) where+  data AuthorizationRequest a+  type MkAuthorizationRequestResponse a+  mkAuthorizeRequestParameter :: IdpApplication a i -> AuthorizationRequest a+  mkAuthorizeRequest :: IdpApplication a i -> MkAuthorizationRequestResponse a++class HasTokenRequest (a :: GrantTypeFlow) where+  -- | Each GrantTypeFlow has slightly different request parameter to /token endpoint.+  data TokenRequest a++  -- | Only 'AuthorizationCode flow (but not resource owner password nor client credentials) will use 'ExchangeToken' in the token request+  -- create type family to be explicit on it.+  -- with 'type instance WithExchangeToken a b = b' implies no exchange token+  -- v.s. 'type instance WithExchangeToken a b = ExchangeToken -> b' implies needing an exchange token+  type WithExchangeToken a b++  mkTokenRequest ::+    IdpApplication a i ->+    WithExchangeToken a (TokenRequest a)++  conduitTokenRequest ::+    (MonadIO m) =>+    IdpApplication a i ->+    Manager ->+    WithExchangeToken a (ExceptT (OAuth2Error TR.Errors) m OAuth2Token)++class HasPkceAuthorizeRequest (a :: GrantTypeFlow) where+  mkPkceAuthorizeRequest :: MonadIO m => IdpApplication a i -> m (TL.Text, CodeVerifier)++class HasPkceTokenRequest (b :: GrantTypeFlow) where+  conduitPkceTokenRequest ::+    (MonadIO m) =>+    IdpApplication b i ->+    Manager ->+    (ExchangeToken, CodeVerifier) ->+    ExceptT (OAuth2Error TR.Errors) m OAuth2Token++class HasRefreshTokenRequest (a :: GrantTypeFlow) where+  -- | https://www.rfc-editor.org/rfc/rfc6749#page-47+  data RefreshTokenRequest a++  mkRefreshTokenRequest :: IdpApplication a i -> OAuth2.RefreshToken -> RefreshTokenRequest a+  conduitRefreshTokenRequest ::+    (MonadIO m) =>+    IdpApplication a i ->+    Manager ->+    OAuth2.RefreshToken ->+    ExceptT (OAuth2Error TR.Errors) m OAuth2Token++-------------------------------------------------------------------------------++-- * User Info types++-------------------------------------------------------------------------------++type family IdpUserInfo a++class HasUserInfoRequest (a :: GrantTypeFlow) where+  conduitUserInfoRequest ::+    FromJSON (IdpUserInfo i) =>+    IdpApplication a i ->+    Manager ->+    AccessToken ->+    ExceptT BSL.ByteString IO (IdpUserInfo i)++-------------------------------------------------------------------------------++-- * Idp App++-------------------------------------------------------------------------------++-- | Shall IdpApplication has a field of 'Idp a'??+data Idp a = Idp+  { idpUserInfoEndpoint :: URI,+    -- NOTE: maybe worth data type to distinguish authorize and token endpoint+    -- as I made mistake at passing to Authorize and Token Request+    idpAuthorizeEndpoint :: URI,+    idpTokenEndpoint :: URI,+    idpFetchUserInfo ::+      forall m.+      (FromJSON (IdpUserInfo a), MonadIO m) =>+      Manager ->+      AccessToken ->+      URI ->+      ExceptT BSL.ByteString m (IdpUserInfo a)+  }++-------------------------------------------------------------------------------++-- * Idp App Config++-------------------------------------------------------------------------------++data family IdpApplication (a :: GrantTypeFlow) (i :: Type)++-------------------------------------------------------------------------------++-- * Authorization Code flow++-------------------------------------------------------------------------------++-- | An Application that supports "Authorization code" flow+data instance IdpApplication 'AuthorizationCode i = AuthorizationCodeIdpApplication+  { idpAppName :: Text,+    idpAppClientId :: ClientId,+    idpAppClientSecret :: ClientSecret,+    idpAppScope :: Set Scope,+    idpAppRedirectUri :: URI,+    idpAppAuthorizeState :: AuthorizeState,+    -- | Though technically one key can have multiple value in query, but who actually does it?!+    idpAppAuthorizeExtraParams :: Map Text Text,+    idpAppTokenRequestAuthenticationMethod :: ClientAuthenticationMethod,+    idp :: Idp i+  }++-- NOTE: maybe add function for parase authorization response+-- though seems overkill. https://github.com/freizl/hoauth2/issues/149+-- parseAuthorizationResponse :: String -> AuthorizationResponse+-- parseAuthorizationResponse :: ( String, String ) -> AuthorizationResponse++instance HasIdpAppName 'AuthorizationCode where+  getIdpAppName :: IdpApplication 'AuthorizationCode i -> Text+  getIdpAppName AuthorizationCodeIdpApplication {..} = idpAppName++instance HasAuthorizeRequest 'AuthorizationCode where+  -- \| https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1+  data AuthorizationRequest 'AuthorizationCode = AuthorizationCodeAuthorizationRequest+    { scope :: Set Scope,+      state :: AuthorizeState,+      clientId :: ClientId,+      redirectUri :: Maybe RedirectUri+    }+  type MkAuthorizationRequestResponse 'AuthorizationCode = Text++  mkAuthorizeRequestParameter :: IdpApplication 'AuthorizationCode i -> AuthorizationRequest 'AuthorizationCode+  mkAuthorizeRequestParameter AuthorizationCodeIdpApplication {..} =+    AuthorizationCodeAuthorizationRequest+      { scope = if null idpAppScope then Set.empty else idpAppScope,+        state = idpAppAuthorizeState,+        clientId = idpAppClientId,+        redirectUri = Just (RedirectUri idpAppRedirectUri)+      }++  mkAuthorizeRequest :: IdpApplication 'AuthorizationCode i -> Text+  mkAuthorizeRequest idpAppConfig@AuthorizationCodeIdpApplication {..} =+    let req = mkAuthorizeRequestParameter idpAppConfig+        allParams =+          map (bimap tlToBS tlToBS) $+            Map.toList $+              Map.unions [idpAppAuthorizeExtraParams, toQueryParam req]+     in TL.fromStrict $+          T.decodeUtf8 $+            serializeURIRef' $+              appendQueryParams allParams $+                idpAuthorizeEndpoint idp++instance HasTokenRequest 'AuthorizationCode where+  -- \| https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3+  data TokenRequest 'AuthorizationCode = AuthorizationCodeTokenRequest+    { code :: ExchangeToken,+      clientId :: ClientId,+      grantType :: GrantTypeValue,+      redirectUri :: RedirectUri+    }+  type WithExchangeToken 'AuthorizationCode a = ExchangeToken -> a++  mkTokenRequest ::+    IdpApplication 'AuthorizationCode i ->+    ExchangeToken ->+    TokenRequest 'AuthorizationCode+  mkTokenRequest AuthorizationCodeIdpApplication {..} authCode =+    AuthorizationCodeTokenRequest+      { code = authCode,+        clientId = idpAppClientId,+        grantType = GTAuthorizationCode,+        redirectUri = RedirectUri idpAppRedirectUri+      }+  conduitTokenRequest ::+    forall m i.+    (MonadIO m) =>+    IdpApplication 'AuthorizationCode i ->+    Manager ->+    ExchangeToken ->+    ExceptT (OAuth2Error TR.Errors) m OAuth2Token+  conduitTokenRequest idpAppConfig@AuthorizationCodeIdpApplication {..} mgr exchangeToken =+    let req = mkTokenRequest idpAppConfig exchangeToken+        key = toOAuth2Key idpAppClientId idpAppClientSecret+        body =+          mapsToParams+            [ toQueryParam req,+              toQueryParam+                ( if idpAppTokenRequestAuthenticationMethod == ClientSecretPost+                    then Just idpAppClientSecret+                    else Nothing+                )+            ]+     in doJSONPostRequest mgr key (idpTokenEndpoint idp) body++instance HasPkceAuthorizeRequest 'AuthorizationCode where+  mkPkceAuthorizeRequest :: MonadIO m => IdpApplication 'AuthorizationCode i -> m (Text, CodeVerifier)+  mkPkceAuthorizeRequest idpAppConfig@AuthorizationCodeIdpApplication {..} = do+    PkceRequestParam {..} <- mkPkceParam+    let req = mkAuthorizeRequestParameter idpAppConfig+    let allParams =+          mapsToParams+            [ idpAppAuthorizeExtraParams,+              toQueryParam req,+              toQueryParam codeChallenge,+              toQueryParam codeChallengeMethod+            ]++    let url =+          TL.fromStrict $+            T.decodeUtf8 $+              serializeURIRef' $+                appendQueryParams allParams $+                  idpAuthorizeEndpoint idp+    pure (url, codeVerifier)++instance HasPkceTokenRequest 'AuthorizationCode where+  conduitPkceTokenRequest ::+    MonadIO m =>+    IdpApplication 'AuthorizationCode i ->+    Manager ->+    (ExchangeToken, CodeVerifier) ->+    ExceptT (OAuth2Error TR.Errors) m OAuth2Token+  conduitPkceTokenRequest idpAppConfig@AuthorizationCodeIdpApplication {..} mgr (exchangeToken, codeVerifier) =+    let req = mkTokenRequest idpAppConfig exchangeToken+        key = toOAuth2Key idpAppClientId idpAppClientSecret+        body =+          mapsToParams+            [ toQueryParam req,+              toQueryParam codeVerifier,+              toQueryParam (if idpAppTokenRequestAuthenticationMethod == ClientSecretPost then Just idpAppClientSecret else Nothing)+            ]+     in doJSONPostRequest mgr key (idpTokenEndpoint idp) body++instance HasRefreshTokenRequest 'AuthorizationCode where+  data RefreshTokenRequest 'AuthorizationCode = AuthorizationCodeTokenRefreshRequest+    { refreshToken :: OAuth2.RefreshToken,+      grantType :: GrantTypeValue,+      scope :: Set Scope+    }++  mkRefreshTokenRequest :: IdpApplication 'AuthorizationCode i -> OAuth2.RefreshToken -> RefreshTokenRequest 'AuthorizationCode+  mkRefreshTokenRequest AuthorizationCodeIdpApplication {..} rt =+    AuthorizationCodeTokenRefreshRequest+      { scope = idpAppScope,+        grantType = GTRefreshToken,+        refreshToken = rt+      }+  conduitRefreshTokenRequest ::+    (MonadIO m) =>+    IdpApplication 'AuthorizationCode i ->+    Manager ->+    OAuth2.RefreshToken ->+    ExceptT (OAuth2Error TR.Errors) m OAuth2Token+  conduitRefreshTokenRequest idpAppConfig@AuthorizationCodeIdpApplication {..} mgr rt =+    let req = mkRefreshTokenRequest idpAppConfig rt+        key = toOAuth2Key idpAppClientId idpAppClientSecret+        body =+          mapsToParams+            [ toQueryParam req,+              toQueryParam (if idpAppTokenRequestAuthenticationMethod == ClientSecretPost then Just idpAppClientSecret else Nothing)+            ]+     in doJSONPostRequest mgr key (idpTokenEndpoint idp) body++instance HasUserInfoRequest 'AuthorizationCode where+  conduitUserInfoRequest ::+    FromJSON (IdpUserInfo i) =>+    IdpApplication 'AuthorizationCode i ->+    Manager ->+    AccessToken ->+    ExceptT BSL.ByteString IO (IdpUserInfo i)+  conduitUserInfoRequest AuthorizationCodeIdpApplication {..} mgr at = do+    idpFetchUserInfo idp mgr at (idpUserInfoEndpoint idp)++instance ToQueryParam (AuthorizationRequest 'AuthorizationCode) where+  toQueryParam :: AuthorizationRequest 'AuthorizationCode -> Map Text Text+  toQueryParam req@AuthorizationCodeAuthorizationRequest {..} =+    Map.unions+      [ toResponseTypeParam req,+        toQueryParam scope,+        toQueryParam clientId,+        toQueryParam state,+        toQueryParam redirectUri+      ]++instance ToQueryParam (TokenRequest 'AuthorizationCode) where+  toQueryParam :: TokenRequest 'AuthorizationCode -> Map Text Text+  toQueryParam AuthorizationCodeTokenRequest {..} =+    Map.unions+      [ toQueryParam grantType,+        toQueryParam code,+        toQueryParam redirectUri+      ]++instance ToQueryParam (RefreshTokenRequest 'AuthorizationCode) where+  toQueryParam :: RefreshTokenRequest 'AuthorizationCode -> Map Text Text+  toQueryParam AuthorizationCodeTokenRefreshRequest {..} =+    Map.unions+      [ toQueryParam grantType,+        toQueryParam scope,+        toQueryParam refreshToken+      ]++-------------------------------------------------------------------------------++-- * Password flow++-------------------------------------------------------------------------------++-- https://www.rfc-editor.org/rfc/rfc6749#section-4.3.1+-- 4.3.1.  Authorization Request and Response (Password grant type)+-- The method through which the client obtains the resource owner+-- credentials is beyond the scope of this specification.  The client+-- MUST discard the credentials once an access token has been obtained.+--+-- Hence no AuhorizationRequest instance++data instance IdpApplication 'ResourceOwnerPassword i = ResourceOwnerPasswordIDPAppConfig+  { idpAppClientId :: ClientId,+    idpAppClientSecret :: ClientSecret,+    idpAppName :: Text,+    idpAppScope :: Set Scope,+    idpAppUserName :: Username,+    idpAppPassword :: Password,+    -- | Any parameter that required by your Idp and not mentioned in the OAuth2 spec+    idpAppTokenRequestExtraParams :: Map Text Text,+    idp :: Idp i+  }++instance HasIdpAppName 'ResourceOwnerPassword where+  getIdpAppName :: IdpApplication 'ResourceOwnerPassword i -> Text+  getIdpAppName ResourceOwnerPasswordIDPAppConfig {..} = idpAppName++instance HasUserInfoRequest 'ResourceOwnerPassword where+  conduitUserInfoRequest ResourceOwnerPasswordIDPAppConfig {..} mgr at = do+    idpFetchUserInfo idp mgr at (idpUserInfoEndpoint idp)++instance HasTokenRequest 'ResourceOwnerPassword where+  -- \| https://www.rfc-editor.org/rfc/rfc6749#section-4.3.2+  data TokenRequest 'ResourceOwnerPassword = PasswordTokenRequest+    { scope :: Set Scope,+      username :: Username,+      password :: Password,+      grantType :: GrantTypeValue+    }+  type WithExchangeToken 'ResourceOwnerPassword a = a++  mkTokenRequest :: IdpApplication 'ResourceOwnerPassword i -> TokenRequest 'ResourceOwnerPassword+  mkTokenRequest ResourceOwnerPasswordIDPAppConfig {..} =+    PasswordTokenRequest+      { username = idpAppUserName,+        password = idpAppPassword,+        grantType = GTPassword,+        scope = idpAppScope+      }++  conduitTokenRequest ::+    (MonadIO m) =>+    IdpApplication 'ResourceOwnerPassword i ->+    Manager ->+    ExceptT (OAuth2Error TR.Errors) m OAuth2Token+  conduitTokenRequest idpAppConfig@ResourceOwnerPasswordIDPAppConfig {..} mgr =+    let req = mkTokenRequest idpAppConfig+        key = toOAuth2Key idpAppClientId idpAppClientSecret+        body = mapsToParams [idpAppTokenRequestExtraParams, toQueryParam req]+     in doJSONPostRequest mgr key (idpTokenEndpoint idp) body++-- | TODO: TBD+instance HasRefreshTokenRequest 'ResourceOwnerPassword where+  data RefreshTokenRequest 'ResourceOwnerPassword = PasswordRefreshTokenRequest++  mkRefreshTokenRequest ::+    IdpApplication 'ResourceOwnerPassword i ->+    OAuth2.RefreshToken ->+    RefreshTokenRequest 'ResourceOwnerPassword+  mkRefreshTokenRequest = undefined++  conduitRefreshTokenRequest ::+    MonadIO m =>+    IdpApplication 'ResourceOwnerPassword i ->+    Manager ->+    OAuth2.RefreshToken ->+    ExceptT (OAuth2Error TR.Errors) m OAuth2Token+  conduitRefreshTokenRequest = undefined++instance ToQueryParam (TokenRequest 'ResourceOwnerPassword) where+  toQueryParam :: TokenRequest 'ResourceOwnerPassword -> Map Text Text+  toQueryParam PasswordTokenRequest {..} =+    Map.unions+      [ toQueryParam grantType,+        toQueryParam scope,+        toQueryParam username,+        toQueryParam password+      ]++-------------------------------------------------------------------------------++-- * Client Credentials flow++-------------------------------------------------------------------------------++-- https://www.rfc-editor.org/rfc/rfc6749#section-4.4.1+-- 4.4.1.  Authorization Request and Response (Client Credentials grant type)+-- Since the client authentication is used as the authorization grant,+-- no additional authorization request is needed.+--+-- Hence no AuhorizationRequest instance++data instance IdpApplication 'ClientCredentials i = ClientCredentialsIDPAppConfig+  { idpAppClientId :: ClientId,+    idpAppClientSecret :: ClientSecret,+    idpAppName :: Text,+    idpAppScope :: Set Scope,+    -- | Any parameter that required by your Idp and not mentioned in the OAuth2 spec+    idpAppTokenRequestExtraParams :: Map Text Text,+    idp :: Idp i+  }++instance HasIdpAppName 'ClientCredentials where+  getIdpAppName :: IdpApplication 'ClientCredentials i -> Text+  getIdpAppName ClientCredentialsIDPAppConfig {..} = idpAppName++instance HasTokenRequest 'ClientCredentials where+  -- \| https://www.rfc-editor.org/rfc/rfc6749#section-4.4.2+  data TokenRequest 'ClientCredentials = ClientCredentialsTokenRequest+    { scope :: Set Scope,+      grantType :: GrantTypeValue+    }++  type WithExchangeToken 'ClientCredentials a = a++  mkTokenRequest :: IdpApplication 'ClientCredentials i -> TokenRequest 'ClientCredentials+  mkTokenRequest ClientCredentialsIDPAppConfig {..} =+    ClientCredentialsTokenRequest+      { scope = idpAppScope,+        grantType = GTClientCredentials+      }++  conduitTokenRequest ::+    (MonadIO m) =>+    IdpApplication 'ClientCredentials i ->+    Manager ->+    ExceptT (OAuth2Error TR.Errors) m OAuth2Token+  conduitTokenRequest idpAppConfig@ClientCredentialsIDPAppConfig {..} mgr =+    let req = mkTokenRequest idpAppConfig+        key =+          toOAuth2Key+            idpAppClientId+            idpAppClientSecret+        body =+          mapsToParams+            [ idpAppTokenRequestExtraParams,+              toQueryParam req+            ]+     in doJSONPostRequest mgr key (idpTokenEndpoint idp) body++instance ToQueryParam (TokenRequest 'ClientCredentials) where+  toQueryParam :: TokenRequest 'ClientCredentials -> Map Text Text+  toQueryParam ClientCredentialsTokenRequest {..} =+    Map.unions+      [ toQueryParam grantType,+        toQueryParam scope+      ]
+ src/Network/OAuth2/Experiment/Utils.hs view
@@ -0,0 +1,21 @@+module Network.OAuth2.Experiment.Utils where++import Data.Bifunctor+import Data.ByteString (ByteString)+import Data.ByteString.Char8 qualified as BS8+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Text.Encoding qualified as TE+import Data.Text.Lazy qualified as TL++tlToBS :: TL.Text -> ByteString+tlToBS = TE.encodeUtf8 . TL.toStrict++bs8ToLazyText :: BS8.ByteString -> TL.Text+bs8ToLazyText = TL.pack . BS8.unpack++mapsToParams :: [Map TL.Text TL.Text] -> [(ByteString, ByteString)]+mapsToParams =+  map (bimap tlToBS tlToBS)+    . Map.toList+    . Map.unions