packages feed

hoauth2-providers 0.8.0 → 0.9.0

raw patch · 20 files changed

+232/−78 lines, 20 filesdep ~hoauth2dep ~uri-bytestring

Dependency ranges changed: hoauth2, uri-bytestring

Files

+ CHANGELOG.md view
@@ -0,0 +1,45 @@+# hoauth2-provider Changelog++## 0.9.0 (2025-10-05)++- Add Linear provider+- Fix `OktaUser` default values; parameterize StackExchange key+- Update Facebook and LinkedIn configurations+- Allow `uri-bytestring`-0.4 and `microlens`-0.5+- Internal refactors and cleanups++## 0.8.0 (2024-11-19)++* Updated crypton dependency to version 1.0++## 0.7.0 (2024-03-07)++* Replaced cryptonite dependency with crypton++## 0.6.0 (2024-01-19)++* Updated base64 dependency to version 1.0++## 0.5.0 (2023-12-30)++* Updated aeson dependency to version 2.2+* Updated binary dependency to version 0.10+* Updated bytestring dependency to version 0.12+* Updated container dependency to version 0.7++## 0.4.0 (2023-11-17)++* Updated text dependency to version 2.2++## 0.3.0 (2023-10-26)++* Added mkAzureADIdp helper function+* Renamed Experiment.GrantType to Experiment.Grants+* Added capability to fetch deviceAuthorizationEndpoint from OIDC configuration+* Added generator for all IdP names+* Added IdP-initiated login flow demo for Okta++## 0.2.0 (2022-11-17)++* Updated endpoint domain for Azure IdP+* Renamed `DropboxName` to `DropboxUserName`
+ README.md view
@@ -0,0 +1,1 @@+See [file:./src/Network/OAuth2/Provider](./src/Network/OAuth2/Provider) the list of supported Identity Providers.
hoauth2-providers.cabal view
@@ -1,19 +1,24 @@-cabal-version: 2.4-name:          hoauth2-providers-version:       0.8.0-synopsis:      OAuth2 Identity Providers-description:   A few well known Identity Providers-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.6.1+cabal-version:      2.4+name:               hoauth2-providers+version:            0.9.0+synopsis:           OAuth2 Identity Providers+description:        A few well known Identity Providers+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 ==8.10.7 || ==9.0.2 || ==9.2.7 || ==9.4.4 || ==9.6.1 || ==9.8.2 +extra-source-files:+  CHANGELOG.md+  README.md+ source-repository head   type:     git   location: git://github.com/freizl/hoauth2.git@@ -43,6 +48,7 @@     Network.OAuth2.Provider.Fitbit     Network.OAuth2.Provider.GitHub     Network.OAuth2.Provider.Google+    Network.OAuth2.Provider.Linear     Network.OAuth2.Provider.LinkedIn     Network.OAuth2.Provider.Okta     Network.OAuth2.Provider.Slack@@ -53,21 +59,21 @@     Network.OIDC.WellKnown    build-depends:-    , aeson                 >=2.0   && <2.3-    , base                  >=4.11  && <5-    , bytestring            >=0.9   && <0.13+    , aeson                 >=2.0  && <2.3+    , base                  >=4.11 && <5+    , bytestring            >=0.9  && <0.13     , containers            ^>=0.6-    , crypton               >=0.32  && <1.1-    , hoauth2               >=2.9   && <2.15-    , HsOpenSSL             >=0.11  && <0.12-    , http-conduit          >=2.1   && <2.4-    , http-types            >=0.11  && <0.13-    , jose-jwt              >=0.10  && <0.11-    , mtl                   >=2.0   && <2.4-    , text                  >=2.0   && <2.3-    , time                  >=1.12  && <1.13-    , transformers          >=0.4   && <0.7-    , uri-bytestring        >=0.2.3 && <0.4+    , crypton               >=0.32 && <1.1+    , hoauth2               >=2.9  && <2.16+    , HsOpenSSL             >=0.11 && <0.12+    , http-conduit          >=2.1  && <2.4+    , http-types            >=0.11 && <0.13+    , jose-jwt              >=0.10 && <0.11+    , mtl                   >=2.0  && <2.4+    , text                  >=2.0  && <2.3+    , time                  >=1.12 && <1.13+    , transformers          >=0.4  && <0.7+    , uri-bytestring        >=0.4  && <0.5     , uri-bytestring-aeson  ^>=0.1    ghc-options:@@ -84,11 +90,11 @@   hs-source-dirs:     test   ghc-options:        -Wall   build-depends:-    , aeson              >=2.0   && <2.3-    , base               >=4.11  && <5+    , aeson              >=2.0  && <2.3+    , base               >=4.11 && <5     , hoauth2-providers-    , hspec              >=2     && <3-    , uri-bytestring     >=0.2.3 && <0.4+    , hspec              >=2    && <3+    , uri-bytestring     >=0.4  && <0.5    other-modules:      Network.OIDC.WellKnownSpec   default-language:   Haskell2010
src/Network/OAuth2/Provider.hs view
@@ -10,6 +10,7 @@   | Fitbit   | GitHub   | Google+  | Linear   | LinkedIn   | Okta   | Slack
src/Network/OAuth2/Provider/Auth0.hs view
@@ -16,7 +16,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import Network.OIDC.WellKnown@@ -32,7 +32,7 @@     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-auth0-authorization-code-app"     , acAuthorizeRequestExtraParams = Map.empty-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  fetchUserInfo ::
src/Network/OAuth2/Provider/AzureAD.hs view
@@ -12,7 +12,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import Network.OIDC.WellKnown@@ -32,7 +32,7 @@     , acAuthorizeRequestExtraParams = Map.empty     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-azure-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  fetchUserInfo ::
src/Network/OAuth2/Provider/DropBox.hs view
@@ -12,7 +12,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ@@ -27,7 +27,7 @@     , acAuthorizeRequestExtraParams = Map.empty     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-dropbox-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  fetchUserInfo ::
src/Network/OAuth2/Provider/Facebook.hs view
@@ -12,7 +12,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ@@ -22,12 +22,12 @@   AuthorizationCodeApplication     { acClientId = ""     , acClientSecret = ""-    , acScope = Set.fromList ["user_about_me", "email"]+    , acScope = Set.fromList ["public_profile", "email"]     , acAuthorizeRequestExtraParams = Map.empty     , acAuthorizeState = "CHANGE_ME"     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-facebook-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretPost+    , acClientAuthenticationMethod = ClientSecretPost     }  fetchUserInfo ::@@ -43,7 +43,7 @@   Idp     { idpUserInfoEndpoint = [uri|https://graph.facebook.com/me?fields=id,name,email|]     , idpAuthorizeEndpoint = [uri|https://www.facebook.com/dialog/oauth|]-    , idpTokenEndpoint = [uri|https://graph.facebook.com/v2.3/oauth/access_token|]+    , idpTokenEndpoint = [uri|https://graph.facebook.com/v22.0/oauth/access_token|]     , idpDeviceAuthorizationEndpoint = Nothing     } 
src/Network/OAuth2/Provider/Fitbit.hs view
@@ -12,7 +12,7 @@ import Data.Set qualified as Set import Data.Text.Lazy (Text) import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ@@ -27,7 +27,7 @@     , acAuthorizeState = "CHANGE_ME"     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-fitbit-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  fetchUserInfo ::
src/Network/OAuth2/Provider/GitHub.hs view
@@ -12,7 +12,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ@@ -27,7 +27,7 @@     , acAuthorizeRequestExtraParams = Map.empty     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-github-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  fetchUserInfo ::
src/Network/OAuth2/Provider/Google.hs view
@@ -23,7 +23,7 @@ import Jose.Jws import Jose.Jwt import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import OpenSSL.EVP.PKey (toKeyPair)@@ -50,7 +50,7 @@     , acAuthorizeState = "CHANGE_ME"     , acRedirectUri = [uri|http://localhost|]     , acAuthorizeRequestExtraParams = Map.empty-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  -- * Service Account
+ src/Network/OAuth2/Provider/Linear.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++-- | [OAuth 2.0 Authentication](https://developers.linear.app/docs/oauth/authentication)+module Network.OAuth2.Provider.Linear where++import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..))+import Data.Aeson+import Data.ByteString.Lazy.Char8 qualified as BSL+import Data.Map.Strict qualified as Map+import Data.Set qualified as Set+import Data.Text.Lazy (Text)+import GHC.Generics+import Network.HTTP.Conduit (Manager)+import Network.OAuth2+import Network.OAuth2.Experiment+import Network.OAuth2.Provider+import URI.ByteString.QQ++sampleLinearAuthorizationCodeApp :: AuthorizationCodeApplication+sampleLinearAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.empty+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-linear-authorization-code-app"+    , acClientAuthenticationMethod = ClientSecretPost+    }++fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo =+  conduitUserInfoRequestWithCustomMethod+    (\mgr at url -> authPostJSON mgr at url [("query", "{ viewer { id name email } }")])++defaultLinearIdp :: Idp Linear+defaultLinearIdp =+  Idp+    { idpAuthorizeEndpoint = [uri|https://linear.app/oauth/authorize|]+    , idpTokenEndpoint = [uri|https://api.linear.app/oauth/token|]+    , idpUserInfoEndpoint = [uri|https://api.linear.app/graphql|]+    , idpDeviceAuthorizationEndpoint = Nothing+    }++-- sample response+-- {+--   "data": {+--     "viewer": {+--       "id": "***",+--       "name": "Hai W.",+--       "email": "freizl.em@gmail.com"+--     }+--   }+-- }++newtype LinearResponse a = LinearResponse+  { _data :: LinearData a+  }+  deriving (Show, Generic)++-- Define a data type for the "data" part of the JSON, which contains the viewer+newtype LinearData a = LinearData+  { _viewer :: a+  }+  deriving (Show, Generic)++data LinearUser = LinearUser+  { id :: Text+  , name :: Text+  , email :: Text+  }+  deriving (Show, Generic)++instance FromJSON a => FromJSON (LinearData a) where+  parseJSON = withObject "Data" $ \v ->+    LinearData+      <$> v .: "viewer"++-- Define a custom FromJSON instance for Response to parse the outer structure+instance FromJSON a => FromJSON (LinearResponse a) where+  parseJSON = withObject "Response" $ \v ->+    LinearResponse+      <$> v .: "data"++instance FromJSON LinearUser where+  parseJSON = withObject "LinearUser" $ \v ->+    LinearUser+      <$> v .: "id"+      <*> v .: "name"+      <*> v .: "email"++getUser :: LinearResponse LinearUser -> LinearUser+getUser (LinearResponse (LinearData a)) = a
src/Network/OAuth2/Provider/LinkedIn.hs view
@@ -12,7 +12,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ (uri)@@ -22,12 +22,12 @@   AuthorizationCodeApplication     { acClientId = ""     , acClientSecret = ""-    , acScope = Set.fromList ["r_liteprofile"]+    , acScope = Set.fromList ["openid", "profile"]     , acAuthorizeState = "CHANGE_ME"     , acAuthorizeRequestExtraParams = Map.empty     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-linkedin-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretPost+    , acClientAuthenticationMethod = ClientSecretPost     }  fetchUserInfo ::@@ -41,15 +41,15 @@ defaultLinkedInIdp :: Idp LinkedIn defaultLinkedInIdp =   Idp-    { idpUserInfoEndpoint = [uri|https://api.linkedin.com/v2/me|]+    { idpUserInfoEndpoint = [uri|https://api.linkedin.com/v2/userinfo|]     , idpAuthorizeEndpoint = [uri|https://www.linkedin.com/oauth/v2/authorization|]     , idpTokenEndpoint = [uri|https://www.linkedin.com/oauth/v2/accessToken|]     , idpDeviceAuthorizationEndpoint = Nothing     }  data LinkedInUser = LinkedInUser-  { localizedFirstName :: Text-  , localizedLastName :: Text+  { name :: Text+  , email :: Text   }   deriving (Show, Generic, Eq) 
src/Network/OAuth2/Provider/Okta.hs view
@@ -24,7 +24,7 @@ import Jose.Jws import Jose.Jwt import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import Network.OIDC.WellKnown@@ -40,7 +40,7 @@     , acAuthorizeRequestExtraParams = Map.empty     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-okta-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  fetchUserInfo ::@@ -89,8 +89,12 @@     tToSeconds = formatTime defaultTimeLocale "%s"  data OktaUser = OktaUser-  { name :: Text+  { sub :: Text+  , email :: Text   , preferredUsername :: Text+  , givenName :: Text+  , familyName :: Text+  , emailVerified :: Bool   }   deriving (Show, Generic) 
src/Network/OAuth2/Provider/Slack.hs view
@@ -14,7 +14,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ (uri)@@ -28,7 +28,7 @@     , acAuthorizeState = "CHANGE_ME"     , acAuthorizeRequestExtraParams = Map.empty     , acRedirectUri = [uri|http://localhost|]-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     , acName = "sample-slack-authorization-code-app"     } 
src/Network/OAuth2/Provider/StackExchange.hs view
@@ -15,19 +15,16 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString (URI) import URI.ByteString.QQ (uri) --- fix key from your application edit page+-- Found the key from your application edit page -- https://stackapps.com/apps/oauth-stackexchangeAppKey :: ByteString-stackexchangeAppKey = ""--userInfoEndpoint :: URI-userInfoEndpoint =+userInfoEndpoint :: ByteString -> URI+userInfoEndpoint stackexchangeAppKey =   appendQueryParams     [ ("key", stackexchangeAppKey)     , ("site", "stackoverflow")@@ -44,7 +41,7 @@     , acAuthorizeRequestExtraParams = Map.empty     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-stackexchange-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretPost+    , acClientAuthenticationMethod = ClientSecretPost     }  fetchUserInfo ::@@ -55,14 +52,14 @@   ExceptT BSL.ByteString m b fetchUserInfo = conduitUserInfoRequestWithCustomMethod (authGetJSONWithAuthMethod AuthInRequestQuery) -defaultStackExchangeIdp :: Idp StackExchange-defaultStackExchangeIdp =+defaultStackExchangeIdp :: ByteString -> Idp StackExchange+defaultStackExchangeIdp stackexchangeAppKey =   Idp     { -- Only StackExchange has such specical app key which has to be append in userinfo uri.       -- I feel it's not worth to invent a way to read from config       -- file which would break the generic of Idp data type.       -- Until discover a easier way, hard code for now.-      idpUserInfoEndpoint = userInfoEndpoint+      idpUserInfoEndpoint = userInfoEndpoint stackexchangeAppKey     , idpAuthorizeEndpoint = [uri|https://stackexchange.com/oauth|]     , idpTokenEndpoint = [uri|https://stackexchange.com/oauth/access_token|]     , idpDeviceAuthorizationEndpoint = Nothing
src/Network/OAuth2/Provider/Twitter.hs view
@@ -13,7 +13,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ@@ -27,7 +27,7 @@     , acAuthorizeState = "CHANGE_ME"     , acRedirectUri = [uri|http://localhost|]     , acName = "sample-twitter-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     , acAuthorizeRequestExtraParams = Map.empty     } 
src/Network/OAuth2/Provider/Weibo.hs view
@@ -12,7 +12,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ@@ -27,7 +27,7 @@     , acAuthorizeState = "CHANGE_ME"     , acAuthorizeRequestExtraParams = Map.empty     , acRedirectUri = [uri|http://localhost|]-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  fetchUserInfo ::
src/Network/OAuth2/Provider/ZOHO.hs view
@@ -12,7 +12,7 @@ import Data.Text.Lazy (Text) import GHC.Generics import Network.HTTP.Conduit (Manager)-import Network.OAuth.OAuth2+import Network.OAuth2 import Network.OAuth2.Experiment import Network.OAuth2.Provider import URI.ByteString.QQ@@ -27,7 +27,7 @@     , acAuthorizeState = "CHANGE_ME"     , acRedirectUri = [uri|http://localhost/oauth2/callback|]     , acName = "sample-zoho-authorization-code-app"-    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acClientAuthenticationMethod = ClientSecretBasic     }  fetchUserInfo ::
src/Network/OIDC/WellKnown.hs view
@@ -19,7 +19,6 @@ import URI.ByteString.Aeson ()  -- | Slim OpenID Configuration--- TODO: could add more fields to be complete. -- -- See spec <https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata> data OpenIDConfiguration = OpenIDConfiguration