packages feed

hoauth2 1.16.2 → 2.0.0

raw patch · 11 files changed

+287/−166 lines, 11 filesdep ~mustachePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: mustache

API changes (from Hackage documentation)

- Network.OAuth.OAuth2.Internal: [oauthAccessTokenEndpoint] :: OAuth2 -> URI
- Network.OAuth.OAuth2.Internal: [oauthCallback] :: OAuth2 -> Maybe URI
- Network.OAuth.OAuth2.Internal: [oauthClientId] :: OAuth2 -> Text
- Network.OAuth.OAuth2.Internal: [oauthClientSecret] :: OAuth2 -> Maybe Text
- Network.OAuth.OAuth2.Internal: [oauthOAuthorizeEndpoint] :: OAuth2 -> URI
+ Network.OAuth.OAuth2.Internal: [oauth2AuthorizeEndpoint] :: OAuth2 -> URIRef Absolute
+ Network.OAuth.OAuth2.Internal: [oauth2ClientId] :: OAuth2 -> Text
+ Network.OAuth.OAuth2.Internal: [oauth2ClientSecret] :: OAuth2 -> Maybe Text
+ Network.OAuth.OAuth2.Internal: [oauth2RedirectUri] :: OAuth2 -> Maybe (URIRef Absolute)
+ Network.OAuth.OAuth2.Internal: [oauth2TokenEndpoint] :: OAuth2 -> URIRef Absolute
+ Network.OAuth.OAuth2.Internal: instance Data.Hashable.Class.Hashable Network.OAuth.OAuth2.Internal.OAuth2
- Network.OAuth.OAuth2.Internal: OAuth2 :: Text -> Maybe Text -> URI -> URI -> Maybe URI -> OAuth2
+ Network.OAuth.OAuth2.Internal: OAuth2 :: Text -> Maybe Text -> URIRef Absolute -> URIRef Absolute -> Maybe (URIRef Absolute) -> OAuth2

Files

README.md view
@@ -1,4 +1,6 @@-[![Build Status](https://secure.travis-ci.org/freizl/hoauth2.svg?branch=master)](http://travis-ci.org/freizl/hoauth2)+[![Build](https://github.com/freizl/hoauth2/actions/workflows/build.yml/badge.svg)](https://github.com/freizl/hoauth2/actions/workflows/build.yml)+[![lint](https://github.com/freizl/hoauth2/actions/workflows/lint.yml/badge.svg)](https://github.com/freizl/hoauth2/actions/workflows/lint.yml)+[![Travis Status](https://secure.travis-ci.org/freizl/hoauth2.svg?branch=master)](http://travis-ci.org/freizl/hoauth2) [![Hackage](https://img.shields.io/hackage/v/hoauth2.svg)](https://hackage.haskell.org/package/hoauth2)  # Introduction
example/IDP.hs view
@@ -13,6 +13,7 @@ import qualified IDP.Okta as IOkta import qualified IDP.StackExchange as IStackExchange import qualified IDP.Weibo as IWeibo+import qualified IDP.Slack as ISlack import qualified IDP.ZOHO as IZOHO import Session import Types@@ -32,6 +33,7 @@     IDPApp IStackExchange.StackExchange,     IDPApp IWeibo.Weibo,     IDPApp IAuth0.Auth0,+    IDPApp ISlack.Slack,     IDPApp IZOHO.ZOHO   ] 
example/IDP/Okta.hs view
@@ -1,20 +1,21 @@-{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes       #-}+{-# LANGUAGE QuasiQuotes #-}  module IDP.Okta where-import           Data.Aeson-import           Data.Bifunctor-import           Data.Hashable-import           Data.Text.Lazy                 ( Text )-import           GHC.Generics-import           Keys-import           Network.OAuth.OAuth2-import           Types-import           URI.ByteString-import           URI.ByteString.QQ-import           Utils +import Data.Aeson+import Data.Bifunctor+import Data.Hashable+import Data.Text.Lazy (Text)+import GHC.Generics+import Keys+import Network.OAuth.OAuth2+import Types+import URI.ByteString+import URI.ByteString.QQ+import Utils+ data Okta = Okta   deriving (Show, Generic) @@ -35,28 +36,36 @@     re <- authGetJSON mgr at userInfoUri     return (second toLoginUser re) +-- | https://developer.okta.com/docs/reference/api/oidc/#request-parameters+-- Okta Org AS doesn't support consent+-- Okta Custom AS does support consent via config (what scope shall prompt consent)+-- instance HasAuthUri Okta where-  authUri _ = createCodeUri-    oktaKey-    [ ("state", "Okta.test-state-123")-    , ( "scope"-      , "openid profile offline_access okta.users.read.self okta.users.read"-      )-    ]+  authUri _ =+    createCodeUri+      oktaKey+      [ ("state", "Okta.test-state-123"),+        ( "scope",+          "openid profile"+          -- , "openid profile offline_access okta.users.read.self okta.users.read"+        ),+        ("prompt", "login consent")+      ]  data OktaUser = OktaUser-  { name              :: Text-  , preferredUsername :: Text+  { name :: Text,+    preferredUsername :: Text   }   deriving (Show, Generic)  instance FromJSON OktaUser where   parseJSON =-    genericParseJSON defaultOptions { fieldLabelModifier = camelTo2 '_' }+    genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}  userInfoUri :: URI-userInfoUri = [uri|http://rain.okta1.com:1802/oauth2/v1/userinfo|]+userInfoUri = [uri|https://hw2.trexcloud.com/oauth2/v1/userinfo|] -toLoginUser :: OktaUser -> LoginUser-toLoginUser ouser = LoginUser { loginUserName = name ouser }+-- userInfoUri = [uri|https://dev-148986.oktapreview.com/oauth2/v1/userinfo|] +toLoginUser :: OktaUser -> LoginUser+toLoginUser ouser = LoginUser {loginUserName = name ouser}
+ example/IDP/Slack.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++module IDP.Slack where++import Data.Aeson+import Data.Bifunctor+import Data.Hashable+import Data.Text.Lazy (Text)+import GHC.Generics+import Keys+import Network.OAuth.OAuth2+import Types+import URI.ByteString+import URI.ByteString.QQ+import Utils++data Slack = Slack+  deriving (Show, Generic)++instance Hashable Slack++instance IDP Slack++instance HasLabel Slack++instance HasTokenReq Slack where+  tokenReq _ mgr = fetchAccessToken mgr slackKey++instance HasTokenRefreshReq Slack where+  tokenRefreshReq _ mgr = refreshAccessToken mgr slackKey++instance HasUserReq Slack where+  userReq _ mgr at = do+    re <- authGetJSON mgr at userInfoUri+    return (second toLoginUser re)++instance HasAuthUri Slack where+  authUri _ =+    createCodeUri+      slackKey+      [ ("state", "Slack.test-state-123"),+        ( "scope",+          "openid profile email"+        )+      ]++data SlackUser = SlackUser+  { name :: Text,+    email :: Text+  }+  deriving (Show, Generic)++instance FromJSON SlackUser where+  parseJSON =+    genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}++userInfoUri :: URI+userInfoUri = [uri|https://slack.com/api/openid.connect.userInfo|]++toLoginUser :: SlackUser -> LoginUser+toLoginUser ouser = LoginUser {loginUserName = name ouser}
example/Keys.hs view
@@ -9,60 +9,60 @@  weiboKey :: OAuth2 weiboKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/oauthCallback|]-  , oauthOAuthorizeEndpoint  = [uri|https://api.weibo.com/oauth2/authorize|]-  , oauthAccessTokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/oauthCallback|]+  , oauth2AuthorizeEndpoint  = [uri|https://api.weibo.com/oauth2/authorize|]+  , oauth2TokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]   }  -- | http://developer.github.com/v3/oauth/ githubKey :: OAuth2 githubKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/githubCallback|]-  , oauthOAuthorizeEndpoint  = [uri|https://github.com/login/oauth/authorize|]-  , oauthAccessTokenEndpoint =+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/githubCallback|]+  , oauth2AuthorizeEndpoint  = [uri|https://github.com/login/oauth/authorize|]+  , oauth2TokenEndpoint =     [uri|https://github.com/login/oauth/access_token|]   }  -- | oauthCallback = Just "https://developers.google.com/oauthplayground" googleKey :: OAuth2 googleKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx.apps.googleusercontent.com"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/googleCallback|]-  , oauthOAuthorizeEndpoint  = [uri|https://accounts.google.com/o/oauth2/auth|]-  , oauthAccessTokenEndpoint = [uri|https://www.googleapis.com/oauth2/v3/token|]+  { oauth2ClientId            = "xxxxxxxxxxxxxxx.apps.googleusercontent.com"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/googleCallback|]+  , oauth2AuthorizeEndpoint  = [uri|https://accounts.google.com/o/oauth2/auth|]+  , oauth2TokenEndpoint = [uri|https://www.googleapis.com/oauth2/v3/token|]   }  facebookKey :: OAuth2 facebookKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://t.haskellcn.org/cb|]-  , oauthOAuthorizeEndpoint  = [uri|https://www.facebook.com/dialog/oauth|]-  , oauthAccessTokenEndpoint =+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://t.haskellcn.org/cb|]+  , oauth2AuthorizeEndpoint  = [uri|https://www.facebook.com/dialog/oauth|]+  , oauth2TokenEndpoint =     [uri|https://graph.facebook.com/v2.3/oauth/access_token|]   }  doubanKey :: OAuth2 doubanKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://localhost:9999/oauthCallback|]-  , oauthOAuthorizeEndpoint  = [uri|https://www.douban.com/service/auth2/auth|]-  , oauthAccessTokenEndpoint = [uri|https://www.douban.com/service/auth2/token|]+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9999/oauthCallback|]+  , oauth2AuthorizeEndpoint  = [uri|https://www.douban.com/service/auth2/auth|]+  , oauth2TokenEndpoint = [uri|https://www.douban.com/service/auth2/token|]   }  fitbitKey :: OAuth2 fitbitKey = OAuth2-  { oauthClientId            = "xxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  = [uri|https://www.fitbit.com/oauth2/authorize|]-  , oauthAccessTokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]+  { oauth2ClientId            = "xxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  = [uri|https://www.fitbit.com/oauth2/authorize|]+  , oauth2TokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]   }  -- fix key from your application edit page@@ -72,58 +72,70 @@  stackexchangeKey :: OAuth2 stackexchangeKey = OAuth2-  { oauthClientId            = "xx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://c.haskellcn.org/cb|]-  , oauthOAuthorizeEndpoint  = [uri|https://stackexchange.com/oauth|]-  , oauthAccessTokenEndpoint =+  { oauth2ClientId            = "xx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://c.haskellcn.org/cb|]+  , oauth2AuthorizeEndpoint  = [uri|https://stackexchange.com/oauth|]+  , oauth2TokenEndpoint =     [uri|https://stackexchange.com/oauth/access_token|]   } dropboxKey :: OAuth2 dropboxKey = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  = [uri|https://www.dropbox.com/1/oauth2/authorize|]-  , oauthAccessTokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  = [uri|https://www.dropbox.com/1/oauth2/authorize|]+  , oauth2TokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]   }  oktaKey :: OAuth2 oktaKey = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  =+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  =     [uri|https://dev-148986.oktapreview.com/oauth2/v1/authorize|]-  , oauthAccessTokenEndpoint =+  , oauth2TokenEndpoint =     [uri|https://dev-148986.oktapreview.com/oauth2/v1/token|]   }  azureADKey :: OAuth2 azureADKey = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  =+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  =     [uri|https://login.windows.net/common/oauth2/authorize|]-  , oauthAccessTokenEndpoint =+  , oauth2TokenEndpoint =     [uri|https://login.windows.net/common/oauth2/token|]   }  zohoKey :: OAuth2 zohoKey = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  = [uri|https://accounts.zoho.com/oauth/v2/auth|]-  , oauthAccessTokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  = [uri|https://accounts.zoho.com/oauth/v2/auth|]+  , oauth2TokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]   }  auth0Key :: OAuth2 auth0Key = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  = [uri|https://foo.auth0.com/authorize|]-  , oauthAccessTokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  = [uri|https://foo.auth0.com/authorize|]+  , oauth2TokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]   }++-- https://api.slack.com/authentication/sign-in-with-slack+-- https://slack.com/.well-known/openid-configuration+slackKey :: OAuth2+slackKey =+  OAuth2+    { oauth2ClientId = ""+    , oauth2ClientSecret = Just ""+    , oauth2RedirectUri = Just [uri|http://localhost:9988/oauth2/callback|]+    , oauth2AuthorizeEndpoint = [uri|https://slack.com/openid/connect/authorize|]+    , oauth2TokenEndpoint = [uri|https://slack.com/api/openid.connect.token|]+    }
example/Keys.hs.sample view
@@ -9,60 +9,60 @@  weiboKey :: OAuth2 weiboKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/oauthCallback|]-  , oauthOAuthorizeEndpoint  = [uri|https://api.weibo.com/oauth2/authorize|]-  , oauthAccessTokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/oauthCallback|]+  , oauth2AuthorizeEndpoint  = [uri|https://api.weibo.com/oauth2/authorize|]+  , oauth2TokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]   }  -- | http://developer.github.com/v3/oauth/ githubKey :: OAuth2 githubKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/githubCallback|]-  , oauthOAuthorizeEndpoint  = [uri|https://github.com/login/oauth/authorize|]-  , oauthAccessTokenEndpoint =+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/githubCallback|]+  , oauth2AuthorizeEndpoint  = [uri|https://github.com/login/oauth/authorize|]+  , oauth2TokenEndpoint =     [uri|https://github.com/login/oauth/access_token|]   }  -- | oauthCallback = Just "https://developers.google.com/oauthplayground" googleKey :: OAuth2 googleKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx.apps.googleusercontent.com"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/googleCallback|]-  , oauthOAuthorizeEndpoint  = [uri|https://accounts.google.com/o/oauth2/auth|]-  , oauthAccessTokenEndpoint = [uri|https://www.googleapis.com/oauth2/v3/token|]+  { oauth2ClientId            = "xxxxxxxxxxxxxxx.apps.googleusercontent.com"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/googleCallback|]+  , oauth2AuthorizeEndpoint  = [uri|https://accounts.google.com/o/oauth2/auth|]+  , oauth2TokenEndpoint = [uri|https://www.googleapis.com/oauth2/v3/token|]   }  facebookKey :: OAuth2 facebookKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://t.haskellcn.org/cb|]-  , oauthOAuthorizeEndpoint  = [uri|https://www.facebook.com/dialog/oauth|]-  , oauthAccessTokenEndpoint =+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://t.haskellcn.org/cb|]+  , oauth2AuthorizeEndpoint  = [uri|https://www.facebook.com/dialog/oauth|]+  , oauth2TokenEndpoint =     [uri|https://graph.facebook.com/v2.3/oauth/access_token|]   }  doubanKey :: OAuth2 doubanKey = OAuth2-  { oauthClientId            = "xxxxxxxxxxxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://localhost:9999/oauthCallback|]-  , oauthOAuthorizeEndpoint  = [uri|https://www.douban.com/service/auth2/auth|]-  , oauthAccessTokenEndpoint = [uri|https://www.douban.com/service/auth2/token|]+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9999/oauthCallback|]+  , oauth2AuthorizeEndpoint  = [uri|https://www.douban.com/service/auth2/auth|]+  , oauth2TokenEndpoint = [uri|https://www.douban.com/service/auth2/token|]   }  fitbitKey :: OAuth2 fitbitKey = OAuth2-  { oauthClientId            = "xxxxxx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  = [uri|https://www.fitbit.com/oauth2/authorize|]-  , oauthAccessTokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]+  { oauth2ClientId            = "xxxxxx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  = [uri|https://www.fitbit.com/oauth2/authorize|]+  , oauth2TokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]   }  -- fix key from your application edit page@@ -72,58 +72,70 @@  stackexchangeKey :: OAuth2 stackexchangeKey = OAuth2-  { oauthClientId            = "xx"-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxx"-  , oauthCallback            = Just [uri|http://c.haskellcn.org/cb|]-  , oauthOAuthorizeEndpoint  = [uri|https://stackexchange.com/oauth|]-  , oauthAccessTokenEndpoint =+  { oauth2ClientId            = "xx"+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxx"+  , oauth2RedirectUri            = Just [uri|http://c.haskellcn.org/cb|]+  , oauth2AuthorizeEndpoint  = [uri|https://stackexchange.com/oauth|]+  , oauth2TokenEndpoint =     [uri|https://stackexchange.com/oauth/access_token|]   } dropboxKey :: OAuth2 dropboxKey = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  = [uri|https://www.dropbox.com/1/oauth2/authorize|]-  , oauthAccessTokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  = [uri|https://www.dropbox.com/1/oauth2/authorize|]+  , oauth2TokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]   }  oktaKey :: OAuth2 oktaKey = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  =+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  =     [uri|https://dev-148986.oktapreview.com/oauth2/v1/authorize|]-  , oauthAccessTokenEndpoint =+  , oauth2TokenEndpoint =     [uri|https://dev-148986.oktapreview.com/oauth2/v1/token|]   }  azureADKey :: OAuth2 azureADKey = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  =+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  =     [uri|https://login.windows.net/common/oauth2/authorize|]-  , oauthAccessTokenEndpoint =+  , oauth2TokenEndpoint =     [uri|https://login.windows.net/common/oauth2/token|]   }  zohoKey :: OAuth2 zohoKey = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  = [uri|https://accounts.zoho.com/oauth/v2/auth|]-  , oauthAccessTokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  = [uri|https://accounts.zoho.com/oauth/v2/auth|]+  , oauth2TokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]   }  auth0Key :: OAuth2 auth0Key = OAuth2-  { oauthClientId            = "xxx"-  , oauthClientSecret        = Just "xxx"-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]-  , oauthOAuthorizeEndpoint  = [uri|https://foo.auth0.com/authorize|]-  , oauthAccessTokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]+  { oauth2ClientId            = "xxx"+  , oauth2ClientSecret        = Just "xxx"+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]+  , oauth2AuthorizeEndpoint  = [uri|https://foo.auth0.com/authorize|]+  , oauth2TokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]   }++-- https://api.slack.com/authentication/sign-in-with-slack+-- https://slack.com/.well-known/openid-configuration+slackKey :: OAuth2+slackKey =+  OAuth2+    { oauth2ClientId = ""+    , oauth2ClientSecret = Just ""+    , oauth2RedirectUri = Just [uri|http://localhost:9988/oauth2/callback|]+    , oauth2AuthorizeEndpoint = [uri|https://slack.com/openid/connect/authorize|]+    , oauth2TokenEndpoint = [uri|https://slack.com/api/openid.connect.token|]+    }
example/README.org view
@@ -18,6 +18,9 @@    - <https://developer.linkedin.com> +* TODO+- [ ] Split ~Key.hs~ to each IDP and read ~.env~ file if exists for override+ * NOTES - classes in Types.hs takes a (`IDP`) as first parameter but it is actually not used. bad pattern. how to fix it?? - refactor: `App.hs` is messy!
example/templates/index.mustache view
@@ -8,7 +8,7 @@         <section class="login-with">              {{^isLogin}}-            <a href="{{codeFlowUri}}">Login {{name}}</a>+            <a href="{{codeFlowUri}}">Sign in with {{name}}</a>             {{/isLogin}}              {{#isLogin}}@@ -25,6 +25,7 @@         <h2>Notes</h2>         <ol>             <li>for StackExchange, the callback domain is localhost, have manually add port 9988.</li>+            <li>for Slack, the callback url has to be https and without any port hence need manual intervention. (TODO: add tls to the server)</li>         </ol>         </p>     </body>
hoauth2.cabal view
@@ -1,7 +1,7 @@ Cabal-version: 2.4 Name:                hoauth2 -- http://wiki.haskell.org/Package_versioning_policy-Version:             1.16.2+Version:             2.0.0  Synopsis:            Haskell OAuth2 authentication client @@ -46,6 +46,7 @@                     example/IDP/Fitbit.hs                     example/IDP/Douban.hs                     example/IDP/Linkedin.hs+                    example/IDP/Slack.hs                     example/IDP/Auth0.hs                     example/IDP.hs                     example/App.hs@@ -86,6 +87,7 @@                  uri-bytestring       >= 0.2.3.1 && < 0.4,                  uri-bytestring-aeson >= 0.1   && < 0.2,                  microlens            >= 0.4.0 && < 0.5,+                 hashable >= 1.2.6 && < 1.5.0,                  exceptions           >= 0.8.3 && < 0.11    ghc-options: -Wall -fwarn-tabs -funbox-strict-fields@@ -107,6 +109,7 @@                        IDP.Fitbit                        IDP.Github                        IDP.Google+                       IDP.Slack                        IDP.Auth0                        IDP.Okta                        IDP.StackExchange@@ -132,7 +135,7 @@                        microlens            >= 0.4.0 && < 0.5,                        unordered-containers >= 0.2.5,                        wai-middleware-static >= 0.8.1 && < 0.10.0,-                       mustache >= 2.2.3 && < 2.5.0,+                       mustache >= 2.2.3 && < 2.4.0,                        scotty >= 0.10.0 && < 0.13,                        binary >= 0.8.3.0 && < 0.8.9,                        parsec >= 3.1.11 && < 3.2.0 ,
src/Network/OAuth/OAuth2/HttpClient.hs view
@@ -65,7 +65,7 @@ fetchAccessToken2 mgr oa code = do   let (url, body1) = accessTokenUrl oa code   let secret x = [("client_secret", T.encodeUtf8 x)]-  let extraBody = ("client_id", T.encodeUtf8 $ oauthClientId oa) : maybe [] secret (oauthClientSecret oa)+  let extraBody = ("client_id", T.encodeUtf8 $ oauth2ClientId oa) : maybe [] secret (oauth2ClientSecret oa)   doJSONPostRequest mgr oa url (extraBody ++ body1)  -- | Fetch a new AccessToken with the Refresh Token with authentication in request header.@@ -92,7 +92,7 @@ refreshAccessToken2 manager oa token = do   let (uri, body) = refreshAccessTokenUrl oa token   let secret x = [("client_secret", T.encodeUtf8 x)]-  let extraBody = ("client_id", T.encodeUtf8 $ oauthClientId oa) : maybe [] secret (oauthClientSecret oa)+  let extraBody = ("client_id", T.encodeUtf8 $ oauth2ClientId oa) : maybe [] secret (oauth2ClientSecret oa)   doJSONPostRequest manager oa uri (extraBody ++ body)  -- | Conduct post request and return response as JSON.@@ -113,8 +113,8 @@ doSimplePostRequest manager oa url body = fmap handleOAuth2TokenResponse go                                   where go = do                                              req <- uriToRequest url-                                             let addBasicAuth = case oauthClientSecret oa of-                                                   (Just secret) -> applyBasicAuth (T.encodeUtf8 $ oauthClientId oa) (T.encodeUtf8 secret)+                                             let addBasicAuth = case oauth2ClientSecret oa of+                                                   (Just secret) -> applyBasicAuth (T.encodeUtf8 $ oauth2ClientId oa) (T.encodeUtf8 secret)                                                    Nothing -> id                                                  req' = (addBasicAuth . updateRequestHeaders Nothing) req                                              httpLbs (urlEncodedBody body req') manager
src/Network/OAuth/OAuth2/Internal.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-}+{-# LANGUAGE RecordWildCards #-} {-# OPTIONS_HADDOCK -ignore-exports #-}  -- | A simple OAuth2 Haskell binding.  (This is supposed to be@@ -19,13 +20,14 @@ import Data.Maybe import Data.Text (Text, pack, unpack) import Data.Text.Encoding-import GHC.Generics import Lens.Micro import Lens.Micro.Extras import Network.HTTP.Conduit as C import qualified Network.HTTP.Types as H import URI.ByteString import URI.ByteString.Aeson ()+import Data.Hashable+import GHC.Generics  -------------------------------------------------- @@ -34,15 +36,27 @@ --------------------------------------------------  -- | Query Parameter Representation+-- TODO: fix typo in OAuthorizeEndpoint+-- rename AccessToken to TokenEndpoint+-- rename callback to redirectUri+ data OAuth2 = OAuth2-  { oauthClientId :: Text,-    oauthClientSecret :: Maybe Text,-    oauthOAuthorizeEndpoint :: URI,-    oauthAccessTokenEndpoint :: URI,-    oauthCallback :: Maybe URI+  { oauth2ClientId :: Text,+    oauth2ClientSecret :: Maybe Text,+    oauth2AuthorizeEndpoint :: URIRef Absolute,+    oauth2TokenEndpoint :: URIRef Absolute,+    oauth2RedirectUri :: Maybe ( URIRef Absolute )   }   deriving (Show, Eq) +instance Hashable OAuth2 where+  hashWithSalt salt OAuth2{..} = salt+    `hashWithSalt` hash oauth2ClientId+    `hashWithSalt` hash oauth2ClientSecret+    `hashWithSalt` hash (show oauth2AuthorizeEndpoint)+    `hashWithSalt` hash (show oauth2TokenEndpoint)+    `hashWithSalt` hash (show oauth2RedirectUri)+ newtype AccessToken = AccessToken {atoken :: Text} deriving (Binary, Eq, Show, FromJSON, ToJSON)  newtype RefreshToken = RefreshToken {rtoken :: Text} deriving (Binary, Eq, Show, FromJSON, ToJSON)@@ -138,13 +152,13 @@ -- | Prepare the authorization URL.  Redirect to this URL -- asking for user interactive authentication. authorizationUrl :: OAuth2 -> URI-authorizationUrl oa = over (queryL . queryPairsL) (++ queryParts) (oauthOAuthorizeEndpoint oa)+authorizationUrl oa = over (queryL . queryPairsL) (++ queryParts) (oauth2AuthorizeEndpoint oa)   where     queryParts =       catMaybes-        [ Just ("client_id", encodeUtf8 $ oauthClientId oa),+        [ Just ("client_id", encodeUtf8 $ oauth2ClientId oa),           Just ("response_type", "code"),-          fmap (("redirect_uri",) . serializeURIRef') (oauthCallback oa)+          fmap (("redirect_uri",) . serializeURIRef') (oauth2RedirectUri oa)         ]  -- | Prepare the URL and the request body query for fetching an access token.@@ -168,11 +182,11 @@   (URI, PostBody) accessTokenUrl' oa code gt = (uri, body)   where-    uri = oauthAccessTokenEndpoint oa+    uri = oauth2TokenEndpoint oa     body =       catMaybes         [ Just ("code", encodeUtf8 $ extoken code),-          ("redirect_uri",) . serializeURIRef' <$> oauthCallback oa,+          ("redirect_uri",) . serializeURIRef' <$> oauth2RedirectUri oa,           fmap (("grant_type",) . encodeUtf8) gt         ] @@ -186,7 +200,7 @@   (URI, PostBody) refreshAccessTokenUrl oa token = (uri, body)   where-    uri = oauthAccessTokenEndpoint oa+    uri = oauth2TokenEndpoint oa     body =       [ ("grant_type", "refresh_token"),         ("refresh_token", encodeUtf8 $ rtoken token)