packages feed

hoauth2 1.2.0 → 1.3.0

raw patch · 13 files changed

+273/−101 lines, 13 filesdep +uri-bytestring-aesonPVP ok

version bump matches the API change (PVP)

Dependencies added: uri-bytestring-aeson

API changes (from Hackage documentation)

+ Network.OAuth.OAuth2.AuthorizationRequest: AccessDenied :: Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: InvalidRequest :: Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: InvalidScope :: Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: ServerError :: Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: TemporarilyUnavailable :: Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: UnauthorizedClient :: Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: UnsupportedResponseType :: Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: data Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth.OAuth2.AuthorizationRequest.Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: instance Data.Aeson.Types.ToJSON.ToJSON Network.OAuth.OAuth2.AuthorizationRequest.Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: instance GHC.Classes.Eq Network.OAuth.OAuth2.AuthorizationRequest.Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: instance GHC.Generics.Generic Network.OAuth.OAuth2.AuthorizationRequest.Errors
+ Network.OAuth.OAuth2.AuthorizationRequest: instance GHC.Show.Show Network.OAuth.OAuth2.AuthorizationRequest.Errors
+ Network.OAuth.OAuth2.Internal: OAuth2Error :: Either Text a -> Maybe Text -> Maybe (URIRef Absolute) -> OAuth2Error a
+ Network.OAuth.OAuth2.Internal: [errorDescription] :: OAuth2Error a -> Maybe Text
+ Network.OAuth.OAuth2.Internal: [errorUri] :: OAuth2Error a -> Maybe (URIRef Absolute)
+ Network.OAuth.OAuth2.Internal: [error] :: OAuth2Error a -> Either Text a
+ Network.OAuth.OAuth2.Internal: data OAuth2Error a
+ Network.OAuth.OAuth2.Internal: instance Data.Aeson.Types.FromJSON.FromJSON err => Data.Aeson.Types.FromJSON.FromJSON (Network.OAuth.OAuth2.Internal.OAuth2Error err)
+ Network.OAuth.OAuth2.Internal: instance Data.Aeson.Types.ToJSON.ToJSON err => Data.Aeson.Types.ToJSON.ToJSON (Network.OAuth.OAuth2.Internal.OAuth2Error err)
+ Network.OAuth.OAuth2.Internal: instance GHC.Classes.Eq a => GHC.Classes.Eq (Network.OAuth.OAuth2.Internal.OAuth2Error a)
+ Network.OAuth.OAuth2.Internal: instance GHC.Generics.Generic (Network.OAuth.OAuth2.Internal.OAuth2Error a)
+ Network.OAuth.OAuth2.Internal: instance GHC.Show.Show a => GHC.Show.Show (Network.OAuth.OAuth2.Internal.OAuth2Error a)
+ Network.OAuth.OAuth2.Internal: parseOAuth2Error :: FromJSON err => ByteString -> OAuth2Error err
+ Network.OAuth.OAuth2.TokenRequest: InvalidClient :: Errors
+ Network.OAuth.OAuth2.TokenRequest: InvalidGrant :: Errors
+ Network.OAuth.OAuth2.TokenRequest: InvalidRequest :: Errors
+ Network.OAuth.OAuth2.TokenRequest: InvalidScope :: Errors
+ Network.OAuth.OAuth2.TokenRequest: UnauthorizedClient :: Errors
+ Network.OAuth.OAuth2.TokenRequest: UnsupportedGrantType :: Errors
+ Network.OAuth.OAuth2.TokenRequest: data Errors
+ Network.OAuth.OAuth2.TokenRequest: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth.OAuth2.TokenRequest.Errors
+ Network.OAuth.OAuth2.TokenRequest: instance Data.Aeson.Types.ToJSON.ToJSON Network.OAuth.OAuth2.TokenRequest.Errors
+ Network.OAuth.OAuth2.TokenRequest: instance GHC.Classes.Eq Network.OAuth.OAuth2.TokenRequest.Errors
+ Network.OAuth.OAuth2.TokenRequest: instance GHC.Generics.Generic Network.OAuth.OAuth2.TokenRequest.Errors
+ Network.OAuth.OAuth2.TokenRequest: instance GHC.Show.Show Network.OAuth.OAuth2.TokenRequest.Errors
- Network.OAuth.OAuth2.HttpClient: authGetBS :: Manager -> AccessToken -> URI -> IO (OAuth2Result ByteString)
+ Network.OAuth.OAuth2.HttpClient: authGetBS :: FromJSON err => Manager -> AccessToken -> URI -> IO (OAuth2Result err ByteString)
- Network.OAuth.OAuth2.HttpClient: authGetBS' :: Manager -> AccessToken -> URI -> IO (OAuth2Result ByteString)
+ Network.OAuth.OAuth2.HttpClient: authGetBS' :: FromJSON err => Manager -> AccessToken -> URI -> IO (OAuth2Result err ByteString)
- Network.OAuth.OAuth2.HttpClient: authGetJSON :: FromJSON a => Manager -> AccessToken -> URI -> IO (OAuth2Result a)
+ Network.OAuth.OAuth2.HttpClient: authGetJSON :: FromJSON err => FromJSON a => Manager -> AccessToken -> URI -> IO (OAuth2Result err a)
- Network.OAuth.OAuth2.HttpClient: authPostBS :: Manager -> AccessToken -> URI -> PostBody -> IO (OAuth2Result ByteString)
+ Network.OAuth.OAuth2.HttpClient: authPostBS :: FromJSON err => Manager -> AccessToken -> URI -> PostBody -> IO (OAuth2Result err ByteString)
- Network.OAuth.OAuth2.HttpClient: authPostBS' :: Manager -> AccessToken -> URI -> PostBody -> IO (OAuth2Result ByteString)
+ Network.OAuth.OAuth2.HttpClient: authPostBS' :: FromJSON err => Manager -> AccessToken -> URI -> PostBody -> IO (OAuth2Result err ByteString)
- Network.OAuth.OAuth2.HttpClient: authPostJSON :: FromJSON a => Manager -> AccessToken -> URI -> PostBody -> IO (OAuth2Result a)
+ Network.OAuth.OAuth2.HttpClient: authPostJSON :: FromJSON err => FromJSON a => Manager -> AccessToken -> URI -> PostBody -> IO (OAuth2Result err a)
- Network.OAuth.OAuth2.HttpClient: authRequest :: Request -> (Request -> Request) -> Manager -> IO (OAuth2Result ByteString)
+ Network.OAuth.OAuth2.HttpClient: authRequest :: FromJSON err => Request -> (Request -> Request) -> Manager -> IO (OAuth2Result err ByteString)
- Network.OAuth.OAuth2.HttpClient: doFlexiblePostRequest :: FromJSON a => Manager -> OAuth2 -> URI -> PostBody -> IO (OAuth2Result a)
+ Network.OAuth.OAuth2.HttpClient: doFlexiblePostRequest :: FromJSON err => FromJSON a => Manager -> OAuth2 -> URI -> PostBody -> IO (OAuth2Result err a)
- Network.OAuth.OAuth2.HttpClient: doJSONPostRequest :: FromJSON a => Manager -> OAuth2 -> URI -> PostBody -> IO (OAuth2Result a)
+ Network.OAuth.OAuth2.HttpClient: doJSONPostRequest :: FromJSON err => FromJSON a => Manager -> OAuth2 -> URI -> PostBody -> IO (OAuth2Result err a)
- Network.OAuth.OAuth2.HttpClient: doSimplePostRequest :: Manager -> OAuth2 -> URI -> PostBody -> IO (OAuth2Result ByteString)
+ Network.OAuth.OAuth2.HttpClient: doSimplePostRequest :: FromJSON err => Manager -> OAuth2 -> URI -> PostBody -> IO (OAuth2Result err ByteString)
- Network.OAuth.OAuth2.HttpClient: fetchAccessToken :: Manager -> OAuth2 -> ExchangeToken -> IO (OAuth2Result OAuth2Token)
+ Network.OAuth.OAuth2.HttpClient: fetchAccessToken :: Manager -> OAuth2 -> ExchangeToken -> IO (OAuth2Result Errors OAuth2Token)
- Network.OAuth.OAuth2.HttpClient: fetchRefreshToken :: Manager -> OAuth2 -> RefreshToken -> IO (OAuth2Result AccessToken)
+ Network.OAuth.OAuth2.HttpClient: fetchRefreshToken :: Manager -> OAuth2 -> RefreshToken -> IO (OAuth2Result Errors OAuth2Token)
- Network.OAuth.OAuth2.HttpClient: handleResponse :: Response ByteString -> OAuth2Result ByteString
+ Network.OAuth.OAuth2.HttpClient: handleResponse :: FromJSON err => Response ByteString -> OAuth2Result err ByteString
- Network.OAuth.OAuth2.HttpClient: parseResponseFlexible :: FromJSON a => OAuth2Result ByteString -> OAuth2Result a
+ Network.OAuth.OAuth2.HttpClient: parseResponseFlexible :: FromJSON err => FromJSON a => OAuth2Result err ByteString -> OAuth2Result err a
- Network.OAuth.OAuth2.HttpClient: parseResponseJSON :: FromJSON a => OAuth2Result ByteString -> OAuth2Result a
+ Network.OAuth.OAuth2.HttpClient: parseResponseJSON :: FromJSON err => FromJSON a => OAuth2Result err ByteString -> OAuth2Result err a
- Network.OAuth.OAuth2.Internal: type OAuth2Result a = Either ByteString a
+ Network.OAuth.OAuth2.Internal: type OAuth2Result err a = Either (OAuth2Error err) a

Files

example/Douban/test.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE ScopedTypeVariables #-}  {- @@ -13,11 +15,9 @@ module Main where  import qualified Data.ByteString.Char8      as BS-import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.Text                  as T import qualified Data.Text.Encoding         as T import qualified Data.Text.Lazy.Encoding    as TL-import qualified Data.Text.Lazy.IO          as TL import           Network.HTTP.Conduit import           URI.ByteString import           URI.ByteString.QQ@@ -26,6 +26,17 @@  import           Keys                       (doubanKey) +import           GHC.Generics+import           Data.Aeson+import           Data.Aeson.Types++data Errors =+  SomeRandomError+  deriving (Show, Eq, Generic)++instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+ main :: IO () main = do   BS.putStrLn $ serializeURIRef' $ authorizationUrl doubanKey@@ -37,13 +48,14 @@                   , ("client_secret", T.encodeUtf8 $ oauthClientSecret doubanKey)                   ] -  token <- doJSONPostRequest mgr doubanKey url (extraBody ++ body)+  token :: OAuth2Result Errors OAuth2Token <- doJSONPostRequest mgr doubanKey url (extraBody ++ body)   print token   case token of     Right r -> do+      -- TODO: display Chinese character. (Text UTF-8 encodeing does not work, why?)       uid <- authGetBS mgr (accessToken r) [uri|https://api.douban.com/v2/user/~me|]-      TL.putStrLn $ either TL.decodeUtf8 TL.decodeUtf8 uid-    Left l -> BSL.putStrLn l+      putStrLn $ either (show :: OAuth2Error Errors -> String) (show . TL.decodeUtf8) uid+    Left l -> print l  sToBS :: String -> BS.ByteString sToBS = T.encodeUtf8 . T.pack
example/Dropbox/test.hs view
@@ -1,20 +1,30 @@ {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric     #-}  module Main where  import qualified Data.ByteString.Char8      as BS import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.Text                  as T+import           Data.Aeson+import           Data.Aeson.Types import           Network.HTTP.Conduit import qualified Network.HTTP.Types         as HT import           URI.ByteString+import           GHC.Generics  import           Network.OAuth.OAuth2  import           Keys +data Errors =+  SomeRandomError+  deriving (Show, Eq, Generic) +instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+ main :: IO () main = do     BS.putStrLn $ serializeURIRef' $ authorizationUrl dropboxKey@@ -27,7 +37,7 @@       Right at -> getSpaceUsage mgr (accessToken at) >>= print       Left _   -> putStrLn "no access token found yet" -getSpaceUsage :: Manager -> AccessToken -> IO (OAuth2Result BSL.ByteString)+getSpaceUsage :: Manager -> AccessToken -> IO (OAuth2Result Errors BSL.ByteString) getSpaceUsage mgr token = do   req <- parseRequest $ BS.unpack "https://api.dropboxapi.com/2/users/get_space_usage"   authRequest req upReq mgr
example/Facebook/test.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-} {-# LANGUAGE TemplateHaskell   #-}+{-# LANGUAGE DeriveGeneric     #-}  {- Facebook example -} @@ -10,7 +11,6 @@ import           Keys                       (facebookKey) import           Network.OAuth.OAuth2 -import           Data.Aeson                 (FromJSON) import           Data.Aeson.TH              (defaultOptions, deriveJSON) import qualified Data.ByteString.Lazy.Char8 as BL import           Data.Text                  (Text)@@ -21,6 +21,18 @@ import           URI.ByteString import           URI.ByteString.QQ +import           GHC.Generics+import           Data.Aeson+import           Data.Aeson.Types++data Errors =+  SomeRandomError+  deriving (Show, Eq, Generic)++instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }++ --------------------------------------------------  data User = User { id    :: Text@@ -44,7 +56,7 @@                     , ("client_secret", T.encodeUtf8 $ oauthClientSecret facebookKey)                     ]     resp <- doJSONPostRequest mgr facebookKey url (body ++ extraBody)-    case (resp :: OAuth2Result OAuth2Token) of+    case (resp :: OAuth2Result Errors OAuth2Token) of       Right token -> do                      print token                      userinfo mgr (accessToken token) >>= print@@ -59,8 +71,8 @@ facebookScope = [("scope", "user_about_me,email")]  -- | Fetch user id and email.-userinfo :: Manager -> AccessToken -> IO (OAuth2Result BL.ByteString)+userinfo :: Manager -> AccessToken -> IO (OAuth2Result Errors BL.ByteString) userinfo mgr token = authGetBS mgr token    [uri|https://graph.facebook.com/me?fields=id,name,email|] -userinfo' :: FromJSON User => Manager -> AccessToken -> IO (OAuth2Result User)+userinfo' :: FromJSON User => Manager -> AccessToken -> IO (OAuth2Result Errors User) userinfo' mgr token = authGetJSON mgr token [uri|https://graph.facebook.com/me?fields=id,name,email|]
example/Fitbit/test.hs view
@@ -1,11 +1,12 @@ {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE ScopedTypeVariables #-}  module Main where  import           Control.Monad            (mzero)-import           Data.Aeson import qualified Data.ByteString          as B import qualified Data.ByteString.Lazy     as BL import           Data.Char                (chr)@@ -23,6 +24,18 @@ import           Keys                     (fitbitKey) import           Network.OAuth.OAuth2 +import           GHC.Generics+import           Data.Aeson+import           Data.Aeson.Types++data Errors =+  SomeRandomError+  deriving (Show, Eq, Generic)++instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }++ ------------------------------------------------------------------------------  data FitbitUser = FitbitUser@@ -78,7 +91,7 @@ getApiCode request =     case M.lookup "code" queryMap of         Just code -> ExchangeToken $ T.decodeUtf8 code-        Nothing   -> error "request doesn't include code"+        Nothing   -> Prelude.error "request doesn't include code"   where     queryMap = convertQueryToMap $ queryString request @@ -87,7 +100,7 @@     result <- doJSONPostRequest mgr fitbitKey url $ body ++ [("state", state)]     case result of         Right token -> return token-        Left e      -> error $ lazyBSToString e+        Left (e :: OAuth2Error Errors) -> Prelude.error $ show e   where     (url, body) = accessTokenUrl fitbitKey code @@ -96,7 +109,7 @@     result <- authGetJSON mgr token [uri|https://api.fitbit.com/1/user/-/profile.json|]     case result of         Right user -> return user-        Left e     -> error $ lazyBSToString e+        Left (e :: OAuth2Error Errors) -> Prelude.error $ show e  convertQueryToMap :: Query -> M.Map B.ByteString B.ByteString convertQueryToMap query =
example/Github/test.hs view
@@ -1,13 +1,13 @@ {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE DeriveGeneric     #-}  -- | Github API: http://developer.github.com/v3/oauth/  module Main where  import           Control.Monad        (mzero)-import           Data.Aeson import qualified Data.ByteString      as BS import           Data.Text            (Text) import qualified Data.Text            as T@@ -17,28 +17,47 @@ import           URI.ByteString.QQ  import           Network.OAuth.OAuth2+import qualified Network.OAuth.OAuth2.AuthorizationRequest as AR  import           Keys +import           GHC.Generics+import           Data.Aeson+import           Data.Aeson.Types +data Errors =+  SomeRandomError+  deriving (Show, Eq, Generic)++instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+++ main :: IO () main = do     let state = "testGithubApi"+    mgr <- newManager tlsManagerSettings+    putStrLn "Trying invalid token..."+    failToken <- getToken state "invalidCode" mgr+    print (failToken :: OAuth2Result AR.Errors OAuth2Token)     print $ serializeURIRef' $ appendQueryParams [("state", state)] $ authorizationUrl githubKey     putStrLn "visit the url and paste code here: "     code <- getLine-    mgr <- newManager tlsManagerSettings-    let (url, body) = accessTokenUrl githubKey $ ExchangeToken $ T.pack code-    token <- doJSONPostRequest mgr githubKey url (body ++ [("state", state)])-    print (token :: OAuth2Result OAuth2Token)+    token <- getToken state code mgr+    print (token :: OAuth2Result AR.Errors OAuth2Token)     case token of       Right at -> userInfo mgr (accessToken at) >>= print       Left _   -> putStrLn "no access token found yet" +getToken :: FromJSON a => BS.ByteString -> String -> Manager -> IO (OAuth2Result AR.Errors a)+getToken state code mgr = do+    let (url, body) = accessTokenUrl githubKey $ ExchangeToken $ T.pack code+    doJSONPostRequest mgr githubKey url (body ++ [("state", state)])  -- | Test API: user ---userInfo :: Manager -> AccessToken -> IO (OAuth2Result GithubUser)+userInfo :: Manager -> AccessToken -> IO (OAuth2Result (OAuth2Error Errors) GithubUser) userInfo mgr token = authGetJSON mgr token [uri|https://api.github.com/user|]  data GithubUser = GithubUser { gid   :: Integer
example/Google/test.hs view
@@ -30,6 +30,13 @@ import           Keys                          (googleKey) import           Network.OAuth.OAuth2 +data Errors =+  SomeRandomError+  deriving (Show, Eq, Generic)++instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+ --------------------------------------------------  data Token = Token { issuedTo   :: Text@@ -76,7 +83,7 @@ offlineCase :: Manager -> IO () offlineCase mgr = do     BS.putStrLn $ serializeURIRef' $ appendQueryParams (googleScopeEmail ++ googleAccessOffline) $ authorizationUrl googleKey-    putStrLn "visit the url and paste code here: "+    putStrLn "offline mode: visit the url and paste code here: "     code <- getLine     (Right token) <- fetchAccessToken mgr googleKey $ ExchangeToken $ T.pack code     f (accessToken token)@@ -88,29 +95,34 @@         Nothing -> putStrLn "Failed to fetch refresh token"         Just tk -> do             (Right token') <- fetchRefreshToken mgr googleKey tk-            f token'+            f (accessToken token')             --validateToken accessToken >>= print             --(validateToken' accessToken :: IO (OAuth2Result Token)) >>= print     where f token = do             print token             validateToken mgr token >>= print-            (validateToken' mgr token :: IO (OAuth2Result Token)) >>= print+            (validateToken' mgr token :: IO (OAuth2Result (OAuth2Error Errors) Token)) >>= print  normalCase :: Manager -> IO () normalCase mgr = do+    -- try an invalid token+    putStr "Trying invalid token..."+    validateToken mgr (AccessToken "invalid") >>= print     BS.putStrLn $ serializeURIRef' $ appendQueryParams googleScopeUserInfo (authorizationUrl googleKey)-    putStrLn "visit the url and paste code here: "+    putStrLn "normal mode: visit the url and paste code here: "     code <- fmap (ExchangeToken . T.pack) getLine-    (Right token) <- fetchAccessToken mgr googleKey code+    maybeToken <- fetchAccessToken mgr googleKey code+    print maybeToken+    (Right token) <- return maybeToken     putStr "AccessToken: " >> print token     -- get response in ByteString     validateToken mgr (accessToken token) >>= print     -- get response in JSON-    (validateToken' mgr (accessToken token):: IO (OAuth2Result Token)) >>= print+    (validateToken' mgr (accessToken token):: IO (OAuth2Result (OAuth2Error Errors) Token)) >>= print     -- get response in ByteString     userinfo mgr (accessToken token) >>= print     -- get response in JSON-    (userinfo' mgr (accessToken token) :: IO (OAuth2Result User)) >>= print+    (userinfo' mgr (accessToken token) :: IO (OAuth2Result (OAuth2Error Errors) User)) >>= print  -------------------------------------------------- -- Google API@@ -131,7 +143,7 @@ -- | Token Validation validateToken :: Manager                  -> AccessToken-                 -> IO (OAuth2Result BL.ByteString)+                 -> IO (OAuth2Result (OAuth2Error Errors) BL.ByteString) validateToken mgr token =    authGetBS' mgr token url    where url = [uri|https://www.googleapis.com/oauth2/v1/tokeninfo|]@@ -139,7 +151,7 @@ validateToken' :: FromJSON a                   => Manager                   -> AccessToken-                  -> IO (OAuth2Result a)+                  -> IO (OAuth2Result (OAuth2Error Errors) a) validateToken' mgr token = parseResponseJSON <$> validateToken mgr token  -- | fetch user email.@@ -147,11 +159,11 @@ -- userinfo :: Manager             -> AccessToken-            -> IO (OAuth2Result BL.ByteString)+            -> IO (OAuth2Result (OAuth2Error Errors) BL.ByteString) userinfo mgr token = authGetBS mgr token [uri|https://www.googleapis.com/oauth2/v2/userinfo|]  userinfo' :: FromJSON a              => Manager              -> AccessToken-             -> IO (OAuth2Result a)+             -> IO (OAuth2Result (OAuth2Error Errors) a) userinfo' mgr token = authGetJSON mgr token [uri|https://www.googleapis.com/oauth2/v2/userinfo|]
example/StackExchange/test.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE ScopedTypeVariables #-}  -- | https://api.stackexchange.com/docs/authentication @@ -54,7 +55,15 @@ instance ToJSON SiteItem where     toEncoding = genericToEncoding defaultOptions { fieldLabelModifier = camelTo2 '_' } +data Errors =+  SomeRandomError+  deriving (Show, Eq, Generic) +instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+++ main :: IO () main = do     BS.putStrLn $ serializeURIRef' $ authorizationUrl stackexchangeKey@@ -75,10 +84,10 @@     print token     case token of       Right at -> siteInfo mgr (accessToken at) >>= print-      Left _   -> putStrLn "no access token found yet"+      Left (_ :: OAuth2Error Errors) -> putStrLn "no access token found yet"  -- | Test API: info-siteInfo :: Manager -> AccessToken -> IO (OAuth2Result SiteInfo)+siteInfo :: Manager -> AccessToken -> IO (OAuth2Result (OAuth2Error Errors) SiteInfo) siteInfo mgr token = authGetJSON mgr token [uri|https://api.stackexchange.com/2.2/info?site=stackoverflow|]  sToBS :: String -> BS.ByteString
example/Weibo/test.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE DeriveGeneric     #-}  {- @@ -22,7 +23,7 @@ module Main where  import qualified Data.ByteString            as BS-import qualified Data.ByteString.Lazy.Char8 as BSL+import qualified Data.ByteString.Lazy       as BSL import qualified Data.Text                  as T import qualified Data.Text.Encoding         as T import           Network.HTTP.Conduit@@ -32,6 +33,18 @@  import           Keys +import           GHC.Generics+import           Data.Aeson+import           Data.Aeson.Types++data Errors =+  SomeRandomError+  deriving (Show, Eq, Generic)++instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }++ main :: IO () main = do        print $ serializeURIRef' $ authorizationUrl weiboKey@@ -43,8 +56,8 @@        case token of          Right r -> do                     uid <- authGetBS' mgr (accessToken r) [uri|https://api.weibo.com/2/account/get_uid.json|]-                    print uid-         Left l -> BSL.putStrLn l+                    print (uid :: OAuth2Result (OAuth2Error Errors) BSL.ByteString)+         Left l -> print l  sToBS :: String -> BS.ByteString sToBS = T.encodeUtf8 . T.pack
hoauth2.cabal view
@@ -1,6 +1,6 @@ Name:                hoauth2 -- http://wiki.haskell.org/Package_versioning_policy-Version:             1.2.0+Version:             1.3.0  Synopsis:            Haskell OAuth2 authentication client @@ -58,6 +58,8 @@   Exposed-modules: Network.OAuth.OAuth2.HttpClient                    Network.OAuth.OAuth2.Internal                    Network.OAuth.OAuth2+                   Network.OAuth.OAuth2.TokenRequest+                   Network.OAuth.OAuth2.AuthorizationRequest    Build-Depends: base                 >= 4     && < 5,                  text                 >= 0.11  && < 1.3,@@ -67,6 +69,7 @@                  aeson                >= 0.11  && < 1.3,                  unordered-containers >= 0.2.5,                  uri-bytestring       >= 0.2.3.1 && < 0.3,+                 uri-bytestring-aeson >= 0.1   && < 0.2,                  microlens            >= 0.4.0 && < 0.5,                  exceptions           >= 0.8.3 && < 0.9 
+ src/Network/OAuth/OAuth2/AuthorizationRequest.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE DeriveGeneric              #-}++module Network.OAuth.OAuth2.AuthorizationRequest where++import           Data.Aeson+import           Data.Aeson.Types+import           GHC.Generics++instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+instance ToJSON Errors where+  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+data Errors =+    InvalidRequest+  | UnauthorizedClient+  | AccessDenied+  | UnsupportedResponseType+  | InvalidScope+  | ServerError+  | TemporarilyUnavailable+  deriving (Show, Eq, Generic)
src/Network/OAuth/OAuth2/HttpClient.hs view
@@ -37,53 +37,55 @@ import           Network.HTTP.Types.URI        (parseQuery) import           Network.OAuth.OAuth2.Internal import           URI.ByteString+import qualified Network.OAuth.OAuth2.TokenRequest as TR  -------------------------------------------------- -- * Token management --------------------------------------------------  -- | Request (via POST method) "OAuth2 Token".-fetchAccessToken :: Manager                          -- ^ HTTP connection manager-                   -> OAuth2                         -- ^ OAuth Data-                   -> ExchangeToken                  -- ^ OAuth 2 Tokens-                   -> IO (OAuth2Result OAuth2Token)  -- ^ Access Token+fetchAccessToken :: Manager                                   -- ^ HTTP connection manager+                   -> OAuth2                                  -- ^ OAuth Data+                   -> ExchangeToken                           -- ^ OAuth 2 Tokens+                   -> IO (OAuth2Result TR.Errors OAuth2Token) -- ^ Access Token fetchAccessToken manager oa code = doFlexiblePostRequest manager oa uri body                            where (uri, body) = accessTokenUrl oa code  --- | Request the "Refresh Token".+-- | Request a new AccessToken with the Refresh Token.+-- TODO: seems more approporate to rename to refreshAccessToken fetchRefreshToken :: Manager                         -- ^ HTTP connection manager.                      -> OAuth2                       -- ^ OAuth context                      -> RefreshToken                 -- ^ refresh token gained after authorization-                     -> IO (OAuth2Result AccessToken)+                     -> IO (OAuth2Result TR.Errors OAuth2Token) fetchRefreshToken manager oa token = doFlexiblePostRequest manager oa uri body                               where (uri, body) = refreshAccessTokenUrl oa token   -- | Conduct post request and return response as JSON.-doJSONPostRequest :: FromJSON a+doJSONPostRequest :: FromJSON err => FromJSON a                   => Manager                             -- ^ HTTP connection manager.                   -> OAuth2                              -- ^ OAuth options                   -> URI                                 -- ^ The URL                   -> PostBody                            -- ^ request body-                  -> IO (OAuth2Result a)                 -- ^ Response as ByteString+                  -> IO (OAuth2Result err a)             -- ^ Response as JSON doJSONPostRequest manager oa uri body = fmap parseResponseJSON (doSimplePostRequest manager oa uri body)  -- | Conduct post request and return response as JSON or Query String.-doFlexiblePostRequest :: FromJSON a+doFlexiblePostRequest :: FromJSON err => FromJSON a                          => Manager                             -- ^ HTTP connection manager.                          -> OAuth2                              -- ^ OAuth options                          -> URI                                 -- ^ The URL                          -> PostBody                            -- ^ request body-                         -> IO (OAuth2Result a)                 -- ^ Response as ByteString+                         -> IO (OAuth2Result err a)             -- ^ Response as ByteString doFlexiblePostRequest manager oa uri body = fmap parseResponseFlexible (doSimplePostRequest manager oa uri body)  -- | Conduct post request.-doSimplePostRequest :: Manager                              -- ^ HTTP connection manager.-                       -> OAuth2                            -- ^ OAuth options-                       -> URI                               -- ^ URL-                       -> PostBody                          -- ^ Request body.-                       -> IO (OAuth2Result BSL.ByteString)  -- ^ Response as ByteString+doSimplePostRequest :: FromJSON err => Manager                 -- ^ HTTP connection manager.+                       -> OAuth2                               -- ^ OAuth options+                       -> URI                                  -- ^ URL+                       -> PostBody                             -- ^ Request body.+                       -> IO (OAuth2Result err BSL.ByteString) -- ^ Response as ByteString doSimplePostRequest manager oa url body = fmap handleResponse go                                   where go = do                                              req <- uriToRequest url@@ -95,49 +97,49 @@ -- * AUTH requests -------------------------------------------------- --- | Conduct GET request and return response as JSON.-authGetJSON :: FromJSON a-                 => Manager                      -- ^ HTTP connection manager.+-- | Conduct an authorized GET request and return response as JSON.+authGetJSON :: FromJSON err => FromJSON a+                 => Manager                 -- ^ HTTP connection manager.                  -> AccessToken-                 -> URI                          -- ^ Full URL-                 -> IO (OAuth2Result a)          -- ^ Response as JSON+                 -> URI+                 -> IO (OAuth2Result err a) -- ^ Response as JSON authGetJSON manager t uri = parseResponseJSON <$> authGetBS manager t uri --- | Conduct GET request.-authGetBS :: Manager                              -- ^ HTTP connection manager.+-- | Conduct an authorized GET request.+authGetBS :: FromJSON err => Manager                 -- ^ HTTP connection manager.              -> AccessToken-             -> URI                               -- ^ URL-             -> IO (OAuth2Result BSL.ByteString)  -- ^ Response as ByteString+             -> URI+             -> IO (OAuth2Result err BSL.ByteString) -- ^ Response as ByteString authGetBS manager token url = do   req <- uriToRequest url   authRequest req upReq manager   where upReq = updateRequestHeaders (Just token) . setMethod HT.GET  -- | same to 'authGetBS' but set access token to query parameter rather than header-authGetBS' :: Manager                             -- ^ HTTP connection manager.+authGetBS' :: FromJSON err => Manager                -- ^ HTTP connection manager.              -> AccessToken-             -> URI                               -- ^ URL-             -> IO (OAuth2Result BSL.ByteString)  -- ^ Response as ByteString+             -> URI+             -> IO (OAuth2Result err BSL.ByteString) -- ^ Response as ByteString authGetBS' manager token url = do   req <- uriToRequest (url `appendAccessToken` token)   authRequest req upReq manager   where upReq = updateRequestHeaders Nothing . setMethod HT.GET  -- | Conduct POST request and return response as JSON.-authPostJSON :: FromJSON a-                 => Manager                      -- ^ HTTP connection manager.+authPostJSON :: FromJSON err => FromJSON a+                 => Manager                 -- ^ HTTP connection manager.                  -> AccessToken-                 -> URI                          -- ^ Full URL+                 -> URI                  -> PostBody-                 -> IO (OAuth2Result a)          -- ^ Response as JSON+                 -> IO (OAuth2Result err a) -- ^ Response as JSON authPostJSON manager t uri pb = parseResponseJSON <$> authPostBS manager t uri pb  -- | Conduct POST request.-authPostBS :: Manager                             -- ^ HTTP connection manager.+authPostBS :: FromJSON err => Manager                -- ^ HTTP connection manager.              -> AccessToken-             -> URI                               -- ^ URL+             -> URI              -> PostBody-             -> IO (OAuth2Result BSL.ByteString)  -- ^ Response as ByteString+             -> IO (OAuth2Result err BSL.ByteString) -- ^ Response as ByteString authPostBS manager token url pb = do   req <- uriToRequest url   authRequest req upReq manager@@ -146,11 +148,11 @@         upReq = upHeaders . upBody  -- | Conduct POST request with access token in the request body rather header-authPostBS' :: Manager                            -- ^ HTTP connection manager.+authPostBS' :: FromJSON err => Manager               -- ^ HTTP connection manager.              -> AccessToken-             -> URI                               -- ^ URL+             -> URI              -> PostBody-             -> IO (OAuth2Result BSL.ByteString)  -- ^ Response as ByteString+             -> IO (OAuth2Result err BSL.ByteString) -- ^ Response as ByteString authPostBS' manager token url pb = do   req <- uriToRequest url   authRequest req upReq manager@@ -161,10 +163,10 @@ -- |Send an HTTP request including the Authorization header with the specified --  access token. ---authRequest :: Request                          -- ^ Request to perform+authRequest :: FromJSON err => Request          -- ^ Request to perform                -> (Request -> Request)          -- ^ Modify request before sending                -> Manager                       -- ^ HTTP connection manager.-               -> IO (OAuth2Result BSL.ByteString)+               -> IO (OAuth2Result err BSL.ByteString) authRequest req upReq manager = fmap handleResponse (httpLbs (upReq req) manager)  --------------------------------------------------@@ -172,32 +174,25 @@ --------------------------------------------------  -- | Parses a @Response@ to to @OAuth2Result@-handleResponse :: Response BSL.ByteString -> OAuth2Result BSL.ByteString+handleResponse :: FromJSON err => Response BSL.ByteString -> OAuth2Result err BSL.ByteString handleResponse rsp =     if HT.statusIsSuccessful (responseStatus rsp)         then Right $ responseBody rsp-        else Left $ BSL.append "hoauth2.HttpClient.handleResponse/Gaining token failed: " (responseBody rsp)+        else Left $ parseOAuth2Error (responseBody rsp)  -- | Parses a @OAuth2Result BSL.ByteString@ into @FromJSON a => a@-parseResponseJSON :: FromJSON a-              => OAuth2Result BSL.ByteString-              -> OAuth2Result a+parseResponseJSON :: FromJSON err => FromJSON a+              => OAuth2Result err BSL.ByteString+              -> OAuth2Result err a parseResponseJSON (Left b) = Left b-parseResponseJSON (Right b) = case eitherDecode b of-                            Left msg -> Left ("hoauth2.HttpClient.parseResponseJSON/Could not decode JSON: "-                                              `BSL.append`-                                              b-                                               `BSL.append`-                                             ", with error: "-                                               `BSL.append`-                                               BSL.pack msg-                                             )-                            Right x -> Right x+parseResponseJSON (Right b) = case decode b of+                            Nothing -> Left (parseOAuth2Error b)+                            Just x -> Right x  -- | Parses a @OAuth2Result BSL.ByteString@ that contains not JSON but a Query String-parseResponseString :: FromJSON a-              => OAuth2Result BSL.ByteString-              -> OAuth2Result a+parseResponseString :: FromJSON err => FromJSON a+              => OAuth2Result err BSL.ByteString+              -> OAuth2Result err a parseResponseString (Left b) = Left b parseResponseString (Right b) = case parseQuery $ BSL.toStrict b of                               [] -> Left errorMessage@@ -207,12 +202,12 @@   where     queryToValue = Object . HM.fromList . map paramToPair     paramToPair (k, mv) = (T.decodeUtf8 k, maybe Null (String . T.decodeUtf8) mv)-    errorMessage = "hoauth2.HttpClient.parseResponseString/Could not decode URL: " `BSL.append` b+    errorMessage = parseOAuth2Error b  -- | Try 'parseResponseJSON' and 'parseResponseString'-parseResponseFlexible :: FromJSON a-                         => OAuth2Result BSL.ByteString-                         -> OAuth2Result a+parseResponseFlexible :: FromJSON err => FromJSON a+                         => OAuth2Result err BSL.ByteString+                         -> OAuth2Result err a parseResponseFlexible r = case parseResponseJSON r of                            Left _ -> parseResponseString r                            x      -> x
src/Network/OAuth/OAuth2/Internal.hs view
@@ -10,21 +10,25 @@  module Network.OAuth.OAuth2.Internal where +import           Prelude              hiding (error) import           Control.Arrow        (second)+import           Control.Applicative import           Control.Monad.Catch import           Data.Aeson import           Data.Aeson.Types import qualified Data.ByteString      as BS import qualified Data.ByteString.Lazy as BSL import           Data.Maybe-import           Data.Text            (Text)+import           Data.Monoid+import           Data.Text            (Text, pack) import           Data.Text.Encoding import           GHC.Generics+import           URI.ByteString+import           URI.ByteString.Aeson () import           Lens.Micro import           Lens.Micro.Extras import           Network.HTTP.Conduit as C import qualified Network.HTTP.Types   as H-import           URI.ByteString  -------------------------------------------------- -- * Data Types@@ -63,12 +67,36 @@ instance ToJSON OAuth2Token where     toEncoding = genericToEncoding defaultOptions { fieldLabelModifier = camelTo2 '_' } +data OAuth2Error a =+  OAuth2Error+    { error :: Either Text a+    , errorDescription :: Maybe Text+    , errorUri :: Maybe (URIRef Absolute) }+  deriving (Show, Eq, Generic)++instance FromJSON err => FromJSON (OAuth2Error err) where+  parseJSON (Object a) =+    do+      err <- (a .: "error") >>= (\str -> Right <$> parseJSON str <|> Left <$> parseJSON str)+      desc <- a .:? "error_description"+      uri <- a .:? "error_uri"+      return $ OAuth2Error err desc uri+  parseJSON _ = fail "Expected an object"++instance ToJSON err => ToJSON (OAuth2Error err) where+  toEncoding = genericToEncoding defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }++parseOAuth2Error :: FromJSON err => BSL.ByteString -> OAuth2Error err+parseOAuth2Error string =+  either (\err -> OAuth2Error (Left "Decode error") (Just $ pack $ "Error: " <> err <> "\n Original Response:\n" <> show (decodeUtf8 $ BSL.toStrict string)) Nothing) id (eitherDecode string)++ -------------------------------------------------- -- * Types Synonym --------------------------------------------------  -- | Is either 'Left' containing an error or 'Right' containg a result-type OAuth2Result a = Either BSL.ByteString a+type OAuth2Result err a = Either (OAuth2Error err) a  -- | type synonym of post body content type PostBody = [(BS.ByteString, BS.ByteString)]
+ src/Network/OAuth/OAuth2/TokenRequest.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE DeriveGeneric              #-}++module Network.OAuth.OAuth2.TokenRequest where++import           Data.Aeson+import           Data.Aeson.Types+import           GHC.Generics++instance FromJSON Errors where+  parseJSON = genericParseJSON defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }+instance ToJSON Errors where+  toEncoding = genericToEncoding defaultOptions { constructorTagModifier = camelTo2 '_', allNullaryToStringTag = True }++-- | Token Error Responses https://tools.ietf.org/html/rfc6749#section-5.2+data Errors =+    InvalidRequest+  | InvalidClient+  | InvalidGrant+  | UnauthorizedClient+  | UnsupportedGrantType+  | InvalidScope+  deriving (Show, Eq, Generic)