packages feed

hoauth2-providers 0.2 → 0.3.0

raw patch · 24 files changed

+704/−563 lines, 24 filesdep +hoauth2-providersdep +hspecdep +uri-bytestring-aesondep −data-defaultdep −directorydep −parsecdep ~basedep ~hoauth2dep ~mtlPVP ok

version bump matches the API change (PVP)

Dependencies added: hoauth2-providers, hspec, uri-bytestring-aeson

Dependencies removed: data-default, directory, parsec, unordered-containers

Dependency ranges changed: base, hoauth2, mtl, text, transformers

API changes (from Hackage documentation)

- Network.OAuth2.Provider.Auth0: Auth0 :: Auth0
- Network.OAuth2.Provider.Auth0: data Auth0
- Network.OAuth2.Provider.Auth0: defaultAuth0App :: Idp Auth0 -> IdpApplication 'AuthorizationCode Auth0
- Network.OAuth2.Provider.Auth0: defaultAuth0Idp :: Idp Auth0
- Network.OAuth2.Provider.Auth0: instance GHC.Classes.Eq Network.OAuth2.Provider.Auth0.Auth0
- Network.OAuth2.Provider.Auth0: instance GHC.Show.Show Network.OAuth2.Provider.Auth0.Auth0
- Network.OAuth2.Provider.AzureAD: AzureAD :: AzureAD
- Network.OAuth2.Provider.AzureAD: data AzureAD
- Network.OAuth2.Provider.AzureAD: defaultAzureADApp :: IdpApplication 'AuthorizationCode AzureAD
- Network.OAuth2.Provider.AzureAD: instance GHC.Classes.Eq Network.OAuth2.Provider.AzureAD.AzureAD
- Network.OAuth2.Provider.AzureAD: instance GHC.Show.Show Network.OAuth2.Provider.AzureAD.AzureAD
- Network.OAuth2.Provider.Dropbox: Dropbox :: Dropbox
- Network.OAuth2.Provider.Dropbox: DropboxUser :: Text -> DropboxUserName -> DropboxUser
- Network.OAuth2.Provider.Dropbox: DropboxUserName :: Text -> DropboxUserName
- Network.OAuth2.Provider.Dropbox: [displayName] :: DropboxUserName -> Text
- Network.OAuth2.Provider.Dropbox: [email] :: DropboxUser -> Text
- Network.OAuth2.Provider.Dropbox: [name] :: DropboxUser -> DropboxUserName
- Network.OAuth2.Provider.Dropbox: data Dropbox
- Network.OAuth2.Provider.Dropbox: data DropboxUser
- Network.OAuth2.Provider.Dropbox: defaultDropboxApp :: IdpApplication 'AuthorizationCode Dropbox
- Network.OAuth2.Provider.Dropbox: defaultDropboxIdp :: Idp Dropbox
- Network.OAuth2.Provider.Dropbox: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.Dropbox.DropboxUser
- Network.OAuth2.Provider.Dropbox: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.Dropbox.DropboxUserName
- Network.OAuth2.Provider.Dropbox: instance GHC.Classes.Eq Network.OAuth2.Provider.Dropbox.Dropbox
- Network.OAuth2.Provider.Dropbox: instance GHC.Generics.Generic Network.OAuth2.Provider.Dropbox.DropboxUser
- Network.OAuth2.Provider.Dropbox: instance GHC.Generics.Generic Network.OAuth2.Provider.Dropbox.DropboxUserName
- Network.OAuth2.Provider.Dropbox: instance GHC.Show.Show Network.OAuth2.Provider.Dropbox.Dropbox
- Network.OAuth2.Provider.Dropbox: instance GHC.Show.Show Network.OAuth2.Provider.Dropbox.DropboxUser
- Network.OAuth2.Provider.Dropbox: instance GHC.Show.Show Network.OAuth2.Provider.Dropbox.DropboxUserName
- Network.OAuth2.Provider.Dropbox: newtype DropboxUserName
- Network.OAuth2.Provider.Facebook: Facebook :: Facebook
- Network.OAuth2.Provider.Facebook: data Facebook
- Network.OAuth2.Provider.Facebook: defaultFacebookApp :: IdpApplication 'AuthorizationCode Facebook
- Network.OAuth2.Provider.Facebook: instance GHC.Classes.Eq Network.OAuth2.Provider.Facebook.Facebook
- Network.OAuth2.Provider.Facebook: instance GHC.Show.Show Network.OAuth2.Provider.Facebook.Facebook
- Network.OAuth2.Provider.Fitbit: Fitbit :: Fitbit
- Network.OAuth2.Provider.Fitbit: data Fitbit
- Network.OAuth2.Provider.Fitbit: defaultFitbitApp :: IdpApplication 'AuthorizationCode Fitbit
- Network.OAuth2.Provider.Fitbit: instance GHC.Classes.Eq Network.OAuth2.Provider.Fitbit.Fitbit
- Network.OAuth2.Provider.Fitbit: instance GHC.Show.Show Network.OAuth2.Provider.Fitbit.Fitbit
- Network.OAuth2.Provider.Github: Github :: Github
- Network.OAuth2.Provider.Github: GithubUser :: Text -> Integer -> GithubUser
- Network.OAuth2.Provider.Github: [id] :: GithubUser -> Integer
- Network.OAuth2.Provider.Github: [name] :: GithubUser -> Text
- Network.OAuth2.Provider.Github: data Github
- Network.OAuth2.Provider.Github: data GithubUser
- Network.OAuth2.Provider.Github: defaultGithubApp :: IdpApplication 'AuthorizationCode Github
- Network.OAuth2.Provider.Github: defaultGithubIdp :: Idp Github
- Network.OAuth2.Provider.Github: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.Github.GithubUser
- Network.OAuth2.Provider.Github: instance GHC.Classes.Eq Network.OAuth2.Provider.Github.Github
- Network.OAuth2.Provider.Github: instance GHC.Generics.Generic Network.OAuth2.Provider.Github.GithubUser
- Network.OAuth2.Provider.Github: instance GHC.Show.Show Network.OAuth2.Provider.Github.Github
- Network.OAuth2.Provider.Github: instance GHC.Show.Show Network.OAuth2.Provider.Github.GithubUser
- Network.OAuth2.Provider.Google: Google :: Google
- Network.OAuth2.Provider.Google: data Google
- Network.OAuth2.Provider.Google: defaultGoogleApp :: IdpApplication 'AuthorizationCode Google
- Network.OAuth2.Provider.Google: defaultServiceAccountApp :: Jwt -> IdpApplication 'JwtBearer Google
- Network.OAuth2.Provider.Google: instance GHC.Classes.Eq Network.OAuth2.Provider.Google.Google
- Network.OAuth2.Provider.Google: instance GHC.Show.Show Network.OAuth2.Provider.Google.Google
- Network.OAuth2.Provider.Linkedin: Linkedin :: Linkedin
- Network.OAuth2.Provider.Linkedin: LinkedinUser :: Text -> Text -> LinkedinUser
- Network.OAuth2.Provider.Linkedin: [localizedFirstName] :: LinkedinUser -> Text
- Network.OAuth2.Provider.Linkedin: [localizedLastName] :: LinkedinUser -> Text
- Network.OAuth2.Provider.Linkedin: data Linkedin
- Network.OAuth2.Provider.Linkedin: data LinkedinUser
- Network.OAuth2.Provider.Linkedin: defaultLinkedinApp :: IdpApplication 'AuthorizationCode Linkedin
- Network.OAuth2.Provider.Linkedin: defaultLinkedinIdp :: Idp Linkedin
- Network.OAuth2.Provider.Linkedin: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.Linkedin.LinkedinUser
- Network.OAuth2.Provider.Linkedin: instance GHC.Classes.Eq Network.OAuth2.Provider.Linkedin.Linkedin
- Network.OAuth2.Provider.Linkedin: instance GHC.Classes.Eq Network.OAuth2.Provider.Linkedin.LinkedinUser
- Network.OAuth2.Provider.Linkedin: instance GHC.Generics.Generic Network.OAuth2.Provider.Linkedin.LinkedinUser
- Network.OAuth2.Provider.Linkedin: instance GHC.Show.Show Network.OAuth2.Provider.Linkedin.Linkedin
- Network.OAuth2.Provider.Linkedin: instance GHC.Show.Show Network.OAuth2.Provider.Linkedin.LinkedinUser
- Network.OAuth2.Provider.Okta: Okta :: Okta
- Network.OAuth2.Provider.Okta: data Okta
- Network.OAuth2.Provider.Okta: defaultOktaApp :: Idp Okta -> IdpApplication 'AuthorizationCode Okta
- Network.OAuth2.Provider.Okta: defaultOktaIdp :: Idp Okta
- Network.OAuth2.Provider.Okta: instance GHC.Classes.Eq Network.OAuth2.Provider.Okta.Okta
- Network.OAuth2.Provider.Okta: instance GHC.Show.Show Network.OAuth2.Provider.Okta.Okta
- Network.OAuth2.Provider.Slack: Slack :: Slack
- Network.OAuth2.Provider.Slack: data Slack
- Network.OAuth2.Provider.Slack: defaultSlackApp :: IdpApplication 'AuthorizationCode Slack
- Network.OAuth2.Provider.Slack: instance GHC.Classes.Eq Network.OAuth2.Provider.Slack.Slack
- Network.OAuth2.Provider.Slack: instance GHC.Show.Show Network.OAuth2.Provider.Slack.Slack
- Network.OAuth2.Provider.StackExchange: StackExchange :: StackExchange
- Network.OAuth2.Provider.StackExchange: data StackExchange
- Network.OAuth2.Provider.StackExchange: defaultStackExchangeApp :: IdpApplication 'AuthorizationCode StackExchange
- Network.OAuth2.Provider.StackExchange: defaultStackexchangeIdp :: Idp StackExchange
- Network.OAuth2.Provider.StackExchange: instance GHC.Classes.Eq Network.OAuth2.Provider.StackExchange.StackExchange
- Network.OAuth2.Provider.StackExchange: instance GHC.Show.Show Network.OAuth2.Provider.StackExchange.StackExchange
- Network.OAuth2.Provider.Twitter: Twitter :: Twitter
- Network.OAuth2.Provider.Twitter: data Twitter
- Network.OAuth2.Provider.Twitter: defaultTwitterApp :: IdpApplication 'AuthorizationCode Twitter
- Network.OAuth2.Provider.Twitter: instance GHC.Classes.Eq Network.OAuth2.Provider.Twitter.Twitter
- Network.OAuth2.Provider.Twitter: instance GHC.Show.Show Network.OAuth2.Provider.Twitter.Twitter
- Network.OAuth2.Provider.Utils: bsFromStrict :: ByteString -> ByteString
- Network.OAuth2.Provider.Utils: bsToStrict :: ByteString -> ByteString
- Network.OAuth2.Provider.Weibo: Weibo :: Weibo
- Network.OAuth2.Provider.Weibo: data Weibo
- Network.OAuth2.Provider.Weibo: defaultWeiboApp :: IdpApplication 'AuthorizationCode Weibo
- Network.OAuth2.Provider.Weibo: instance GHC.Classes.Eq Network.OAuth2.Provider.Weibo.Weibo
- Network.OAuth2.Provider.Weibo: instance GHC.Show.Show Network.OAuth2.Provider.Weibo.Weibo
- Network.OAuth2.Provider.ZOHO: ZOHO :: ZOHO
- Network.OAuth2.Provider.ZOHO: data ZOHO
- Network.OAuth2.Provider.ZOHO: defaultZohoApp :: IdpApplication 'AuthorizationCode ZOHO
- Network.OAuth2.Provider.ZOHO: instance GHC.Classes.Eq Network.OAuth2.Provider.ZOHO.ZOHO
- Network.OAuth2.Provider.ZOHO: instance GHC.Show.Show Network.OAuth2.Provider.ZOHO.ZOHO
- Network.OIDC.WellKnown: OpenIDConfigurationUris :: URI -> URI -> URI -> URI -> OpenIDConfigurationUris
- Network.OIDC.WellKnown: [$sel:authorizationEndpoint:OpenIDConfiguration] :: OpenIDConfiguration -> Text
- Network.OIDC.WellKnown: [$sel:authorizationUri:OpenIDConfigurationUris] :: OpenIDConfigurationUris -> URI
- Network.OIDC.WellKnown: [$sel:issuer:OpenIDConfiguration] :: OpenIDConfiguration -> Text
- Network.OIDC.WellKnown: [$sel:jwksUri:OpenIDConfigurationUris] :: OpenIDConfigurationUris -> URI
- Network.OIDC.WellKnown: [$sel:jwksUri:OpenIDConfiguration] :: OpenIDConfiguration -> Text
- Network.OIDC.WellKnown: [$sel:tokenEndpoint:OpenIDConfiguration] :: OpenIDConfiguration -> Text
- Network.OIDC.WellKnown: [$sel:tokenUri:OpenIDConfigurationUris] :: OpenIDConfigurationUris -> URI
- Network.OIDC.WellKnown: [$sel:userinfoEndpoint:OpenIDConfiguration] :: OpenIDConfiguration -> Text
- Network.OIDC.WellKnown: [$sel:userinfoUri:OpenIDConfigurationUris] :: OpenIDConfigurationUris -> URI
- Network.OIDC.WellKnown: data OpenIDConfigurationUris
- Network.OIDC.WellKnown: fetchWellKnownUris :: MonadIO m => Text -> ExceptT Text m OpenIDConfigurationUris
- Network.OIDC.WellKnown: instance GHC.Generics.Generic Network.OIDC.WellKnown.OpenIDConfiguration
- Network.OIDC.WellKnown: instance GHC.Generics.Generic Network.OIDC.WellKnown.OpenIDConfigurationUris
+ Data.ByteString.Contrib: bsFromStrict :: ByteString -> ByteString
+ Data.ByteString.Contrib: bsToStrict :: ByteString -> ByteString
+ Network.OAuth2.Provider: Auth0 :: IdpName
+ Network.OAuth2.Provider: AzureAD :: IdpName
+ Network.OAuth2.Provider: DropBox :: IdpName
+ Network.OAuth2.Provider: Facebook :: IdpName
+ Network.OAuth2.Provider: Fitbit :: IdpName
+ Network.OAuth2.Provider: GitHub :: IdpName
+ Network.OAuth2.Provider: Google :: IdpName
+ Network.OAuth2.Provider: LinkedIn :: IdpName
+ Network.OAuth2.Provider: Okta :: IdpName
+ Network.OAuth2.Provider: Slack :: IdpName
+ Network.OAuth2.Provider: StackExchange :: IdpName
+ Network.OAuth2.Provider: Twitter :: IdpName
+ Network.OAuth2.Provider: Weibo :: IdpName
+ Network.OAuth2.Provider: ZOHO :: IdpName
+ Network.OAuth2.Provider: data IdpName
+ Network.OAuth2.Provider: instance GHC.Classes.Eq Network.OAuth2.Provider.IdpName
+ Network.OAuth2.Provider: instance GHC.Classes.Ord Network.OAuth2.Provider.IdpName
+ Network.OAuth2.Provider: instance GHC.Enum.Bounded Network.OAuth2.Provider.IdpName
+ Network.OAuth2.Provider: instance GHC.Enum.Enum Network.OAuth2.Provider.IdpName
+ Network.OAuth2.Provider: instance GHC.Generics.Generic Network.OAuth2.Provider.IdpName
+ Network.OAuth2.Provider: instance GHC.Show.Show Network.OAuth2.Provider.IdpName
+ Network.OAuth2.Provider.Auth0: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.Auth0: sampleAuth0AuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.AzureAD: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.AzureAD: mkAzureIdp :: MonadIO m => Text -> ExceptT Text m (Idp AzureAD)
+ Network.OAuth2.Provider.AzureAD: sampleAzureADAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.DropBox: DropBoxUser :: Text -> DropBoxUserName -> DropBoxUser
+ Network.OAuth2.Provider.DropBox: DropBoxUserName :: Text -> DropBoxUserName
+ Network.OAuth2.Provider.DropBox: [displayName] :: DropBoxUserName -> Text
+ Network.OAuth2.Provider.DropBox: [email] :: DropBoxUser -> Text
+ Network.OAuth2.Provider.DropBox: [name] :: DropBoxUser -> DropBoxUserName
+ Network.OAuth2.Provider.DropBox: data DropBoxUser
+ Network.OAuth2.Provider.DropBox: defaultDropBoxIdp :: Idp DropBox
+ Network.OAuth2.Provider.DropBox: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.DropBox: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.DropBox.DropBoxUser
+ Network.OAuth2.Provider.DropBox: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.DropBox.DropBoxUserName
+ Network.OAuth2.Provider.DropBox: instance GHC.Generics.Generic Network.OAuth2.Provider.DropBox.DropBoxUser
+ Network.OAuth2.Provider.DropBox: instance GHC.Generics.Generic Network.OAuth2.Provider.DropBox.DropBoxUserName
+ Network.OAuth2.Provider.DropBox: instance GHC.Show.Show Network.OAuth2.Provider.DropBox.DropBoxUser
+ Network.OAuth2.Provider.DropBox: instance GHC.Show.Show Network.OAuth2.Provider.DropBox.DropBoxUserName
+ Network.OAuth2.Provider.DropBox: newtype DropBoxUserName
+ Network.OAuth2.Provider.DropBox: sampleDropBoxAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.Facebook: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.Facebook: sampleFacebookAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.Fitbit: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.Fitbit: sampleFitbitAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.GitHub: GitHubUser :: Text -> Integer -> GitHubUser
+ Network.OAuth2.Provider.GitHub: [id] :: GitHubUser -> Integer
+ Network.OAuth2.Provider.GitHub: [name] :: GitHubUser -> Text
+ Network.OAuth2.Provider.GitHub: data GitHubUser
+ Network.OAuth2.Provider.GitHub: defaultGithubIdp :: Idp GitHub
+ Network.OAuth2.Provider.GitHub: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.GitHub: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.GitHub.GitHubUser
+ Network.OAuth2.Provider.GitHub: instance GHC.Generics.Generic Network.OAuth2.Provider.GitHub.GitHubUser
+ Network.OAuth2.Provider.GitHub: instance GHC.Show.Show Network.OAuth2.Provider.GitHub.GitHubUser
+ Network.OAuth2.Provider.GitHub: sampleGithubAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.Google: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.Google: sampleGoogleAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.Google: sampleServiceAccountApp :: Jwt -> JwtBearerApplication
+ Network.OAuth2.Provider.LinkedIn: LinkedInUser :: Text -> Text -> LinkedInUser
+ Network.OAuth2.Provider.LinkedIn: [localizedFirstName] :: LinkedInUser -> Text
+ Network.OAuth2.Provider.LinkedIn: [localizedLastName] :: LinkedInUser -> Text
+ Network.OAuth2.Provider.LinkedIn: data LinkedInUser
+ Network.OAuth2.Provider.LinkedIn: defaultLinkedInIdp :: Idp LinkedIn
+ Network.OAuth2.Provider.LinkedIn: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.LinkedIn: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.LinkedIn.LinkedInUser
+ Network.OAuth2.Provider.LinkedIn: instance GHC.Classes.Eq Network.OAuth2.Provider.LinkedIn.LinkedInUser
+ Network.OAuth2.Provider.LinkedIn: instance GHC.Generics.Generic Network.OAuth2.Provider.LinkedIn.LinkedInUser
+ Network.OAuth2.Provider.LinkedIn: instance GHC.Show.Show Network.OAuth2.Provider.LinkedIn.LinkedInUser
+ Network.OAuth2.Provider.LinkedIn: sampleLinkedInAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.Okta: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.Okta: sampleOktaAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.Slack: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.Slack: sampleSlackAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.StackExchange: defaultStackExchangeIdp :: Idp StackExchange
+ Network.OAuth2.Provider.StackExchange: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.StackExchange: sampleStackExchangeAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.StackExchange: userInfoEndpoint :: URI
+ Network.OAuth2.Provider.Twitter: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.Twitter: sampleTwitterAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.Weibo: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.Weibo: sampleWeiboAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OAuth2.Provider.ZOHO: fetchUserInfo :: (MonadIO m, HasUserInfoRequest a, FromJSON b) => IdpApplication i a -> Manager -> AccessToken -> ExceptT ByteString m b
+ Network.OAuth2.Provider.ZOHO: sampleZohoAuthorizationCodeApp :: AuthorizationCodeApplication
+ Network.OIDC.WellKnown: [authorizationEndpoint] :: OpenIDConfiguration -> URI
+ Network.OIDC.WellKnown: [deviceAuthorizationEndpoint] :: OpenIDConfiguration -> URI
+ Network.OIDC.WellKnown: [issuer] :: OpenIDConfiguration -> URI
+ Network.OIDC.WellKnown: [jwksUri] :: OpenIDConfiguration -> URI
+ Network.OIDC.WellKnown: [tokenEndpoint] :: OpenIDConfiguration -> URI
+ Network.OIDC.WellKnown: [userinfoEndpoint] :: OpenIDConfiguration -> URI
+ Network.OIDC.WellKnown: instance GHC.Classes.Eq Network.OIDC.WellKnown.OpenIDConfiguration
+ Network.OIDC.WellKnown: instance GHC.Show.Show Network.OIDC.WellKnown.OpenIDConfiguration
- Network.OAuth2.Provider.Okta: mkOktaClientCredentialAppJwt :: Jwk -> ClientId -> Idp Okta -> IO (Either String Jwt)
+ Network.OAuth2.Provider.Okta: mkOktaClientCredentialAppJwt :: Jwk -> ClientId -> Idp i -> IO (Either Text Jwt)
- Network.OIDC.WellKnown: OpenIDConfiguration :: Text -> Text -> Text -> Text -> Text -> OpenIDConfiguration
+ Network.OIDC.WellKnown: OpenIDConfiguration :: URI -> URI -> URI -> URI -> URI -> URI -> OpenIDConfiguration

Files

hoauth2-providers.cabal view
@@ -1,10 +1,8 @@ cabal-version: 2.4 name:          hoauth2-providers-version:       0.2+version:       0.3.0 synopsis:      OAuth2 Identity Providers-description:-  A list of Identity Providers base on hoauth2 Haskell OAuth2 binding-+description:   A few well known Identity Providers homepage:      https://github.com/freizl/hoauth2 license:       MIT license-file:  LICENSE@@ -14,9 +12,7 @@ category:      Network build-type:    Simple stability:     Beta-tested-with:   GHC <=9.2.2---- extra-source-files:+tested-with:   GHC <=9.6.1  source-repository head   type:     git@@ -30,52 +26,77 @@     DeriveGeneric     GeneralizedNewtypeDeriving     ImportQualifiedPost+    InstanceSigs     OverloadedStrings+    PolyKinds     RecordWildCards     TypeApplications     TypeFamilies -  -- other-modules:-   exposed-modules:+    Data.ByteString.Contrib+    Network.OAuth2.Provider     Network.OAuth2.Provider.Auth0     Network.OAuth2.Provider.AzureAD-    Network.OAuth2.Provider.Dropbox+    Network.OAuth2.Provider.DropBox     Network.OAuth2.Provider.Facebook     Network.OAuth2.Provider.Fitbit-    Network.OAuth2.Provider.Github+    Network.OAuth2.Provider.GitHub     Network.OAuth2.Provider.Google-    Network.OAuth2.Provider.Linkedin+    Network.OAuth2.Provider.LinkedIn     Network.OAuth2.Provider.Okta     Network.OAuth2.Provider.Slack     Network.OAuth2.Provider.StackExchange     Network.OAuth2.Provider.Twitter-    Network.OAuth2.Provider.Utils     Network.OAuth2.Provider.Weibo     Network.OAuth2.Provider.ZOHO     Network.OIDC.WellKnown    build-depends:-    , aeson                 >=2.0    && <2.2-    , base                  >=4.5    && <5-    , bytestring            >=0.9    && <0.12+    , aeson                 >=2.0   && <2.2+    , base                  >=4.5   && <5+    , bytestring            >=0.9   && <0.12     , containers            ^>=0.6-    , cryptonite            >=0.30   && <0.31-    , data-default          ^>=0.7-    , directory             ^>=1.3-    , hoauth2               >=2.7-    , HsOpenSSL             >=0.11   && <0.12-    , http-conduit          >=2.1    && <2.4-    , http-types            >=0.11   && <0.13-    , jose-jwt              >=0.9.4  && <0.10-    , mtl-    , parsec                >=3.1.11 && <3.2.0-    , text                  >=0.11   && <1.3-    , time                  >=1.12   && <1.13-    , transformers          ^>=0.5-    , unordered-containers  >=0.2.5-    , uri-bytestring        >=0.2.3  && <0.4+    , cryptonite            >=0.30  && <0.31+    , hoauth2               ^>=2.9+    , HsOpenSSL             >=0.11  && <0.12+    , http-conduit          >=2.1   && <2.4+    , http-types            >=0.11  && <0.13+    , jose-jwt              >=0.9.4 && <0.10+    , mtl                   >=2.0   && <2.4+    , text                  >=0.11  && <2.1+    , time                  >=1.12  && <1.13+    , transformers          >=0.4   && <0.7+    , uri-bytestring        >=0.2.3 && <0.4+    , uri-bytestring-aeson  ^>=0.1    ghc-options:     -Wall -Wtabs -Wno-unused-do-bind -Wunused-packages -Wpartial-fields-    -Wwarnings-deprecations+    -Wwarn -Wwarnings-deprecations++  -- -Wno-missing-signatures+  if impl(ghc <9.4)+    ghc-options: -Wno-unticked-promoted-constructors++test-suite hoauth-providers-tests+  type:               exitcode-stdio-1.0+  main-is:            Spec.hs+  hs-source-dirs:     test+  ghc-options:        -Wall+  build-depends:+    , aeson              >=2.0   && <2.2+    , base               >=4     && <5+    , hoauth2-providers+    , hspec              >=2     && <3+    , uri-bytestring     >=0.2.3 && <0.4++  other-modules:      Network.OIDC.WellKnownSpec+  default-language:   Haskell2010+  default-extensions:+    ImportQualifiedPost+    OverloadedStrings++  build-tool-depends: hspec-discover:hspec-discover >=2 && <3+  ghc-options:+    -Wall -Wtabs -Wno-unused-do-bind -Wunused-packages -Wpartial-fields+    -Wwarn -Wwarnings-deprecations
+ src/Data/ByteString/Contrib.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE CPP #-}++module Data.ByteString.Contrib where++import Data.ByteString qualified as BS+import Data.ByteString.Lazy qualified as BSL++bsToStrict :: BSL.ByteString -> BS.ByteString+#if MIN_VERSION_bytestring(0,11,0)+bsToStrict = BS.toStrict+#else+bsToStrict = BSL.toStrict+#endif++bsFromStrict :: BS.ByteString -> BSL.ByteString+#if MIN_VERSION_bytestring(0,11,0)+bsFromStrict = BS.fromStrict+#else+bsFromStrict = BSL.fromStrict+#endif
+ src/Network/OAuth2/Provider.hs view
@@ -0,0 +1,20 @@+module Network.OAuth2.Provider where++import GHC.Generics (Generic)++data IdpName+  = Auth0+  | AzureAD+  | DropBox+  | Facebook+  | Fitbit+  | GitHub+  | Google+  | LinkedIn+  | Okta+  | Slack+  | StackExchange+  | Twitter+  | Weibo+  | ZOHO+  deriving (Eq, Ord, Show, Enum, Bounded, Generic)
src/Network/OAuth2/Provider/Auth0.hs view
@@ -1,58 +1,47 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-} --- | https://auth0.com/docs/api/authentication#authorize-application+-- | [Auth0](https://auth0.com)+--+--   * [Auth0 Authorize Application](https://auth0.com/docs/api/authentication#authorize-application)+--+--   * [OAuth 2.0 Authorization Framework](https://auth0.com/docs/authenticate/protocols/oauth) module Network.OAuth2.Provider.Auth0 where -import Control.Monad.IO.Class-import Control.Monad.Trans.Except-import Data.Aeson+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..))+import Data.Aeson (FromJSON)+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 import Network.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider import Network.OIDC.WellKnown import URI.ByteString.QQ -data Auth0 = Auth0-  deriving (Show, Eq)--type instance IdpUserInfo Auth0 = Auth0User--defaultAuth0App :: Idp Auth0 -> IdpApplication 'AuthorizationCode Auth0-defaultAuth0App i =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.fromList ["openid", "profile", "email", "offline_access"]-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-auth0-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = i+sampleAuth0AuthorizationCodeApp :: AuthorizationCodeApplication+sampleAuth0AuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["openid", "profile", "email", "offline_access"]+    , acAuthorizeState = "CHANGE_ME"+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-auth0-authorization-code-app"+    , acAuthorizeRequestExtraParams = Map.empty+    , acTokenRequestAuthenticationMethod = ClientSecretBasic     } -defaultAuth0Idp :: Idp Auth0-defaultAuth0Idp =-  Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Auth0)-    , --  https://auth0.com/docs/api/authentication#user-profile-      idpUserInfoEndpoint = [uri|https://foo.auth0.com/userinfo|]-    , -- https://auth0.com/docs/api/authentication#authorization-code-flow-      idpAuthorizeEndpoint = [uri|https://foo.auth0.com/authorize|]-    , -- https://auth0.com/docs/api/authentication#authorization-code-flow44-      idpTokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]-    }+fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest  mkAuth0Idp ::   MonadIO m =>@@ -60,12 +49,16 @@   Text ->   ExceptT Text m (Idp Auth0) mkAuth0Idp domain = do-  OpenIDConfigurationUris {..} <- fetchWellKnownUris domain+  OpenIDConfiguration {..} <- fetchWellKnown domain   pure-    ( defaultAuth0Idp-        { idpUserInfoEndpoint = userinfoUri-        , idpAuthorizeEndpoint = authorizationUri-        , idpTokenEndpoint = tokenUri+    ( Idp+        { --  https://auth0.com/docs/api/authentication#user-profile+          idpUserInfoEndpoint = userinfoEndpoint+        , -- https://auth0.com/docs/api/authentication#authorization-code-flow+          idpAuthorizeEndpoint = authorizationEndpoint+        , -- https://auth0.com/docs/api/authentication#authorization-code-flow44+          idpTokenEndpoint = tokenEndpoint+        , idpDeviceAuthorizationEndpoint = Just deviceAuthorizationEndpoint         }     ) 
src/Network/OAuth2/Provider/AzureAD.hs view
@@ -1,47 +1,80 @@ {-# LANGUAGE QuasiQuotes #-} +-- | [AzureAD oauth2 flow](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow) module Network.OAuth2.Provider.AzureAD 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.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider+import Network.OIDC.WellKnown import URI.ByteString.QQ -data AzureAD = AzureAD deriving (Eq, Show)--type instance IdpUserInfo AzureAD = AzureADUser---- create app at https://go.microsoft.com/fwlink/?linkid=2083908+-- Create app at https://go.microsoft.com/fwlink/?linkid=2083908 -- -- also be aware to find the right client id. -- see https://stackoverflow.com/a/70670961-defaultAzureADApp :: IdpApplication 'AuthorizationCode AzureAD-defaultAzureADApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.fromList ["openid", "profile", "email"]-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-azure-app"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = defaultAzureADIdp+sampleAzureADAuthorizationCodeApp :: AuthorizationCodeApplication+sampleAzureADAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["openid", "profile", "email"]+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-azure-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretBasic     } +fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest+ -- | https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration+-- It's supporse to resue 'mkAzureIdp'+--+-- @+-- mkAzureIdp "common"+-- @+--+-- But its issuer is "https://login.microsoftonline.com/{tenantid}/v2.0",+-- which is invalid URI!! defaultAzureADIdp :: Idp AzureAD defaultAzureADIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo AzureAD)-    , idpUserInfoEndpoint = [uri|https://graph.microsoft.com/oidc/userinfo|]-    , idpAuthorizeEndpoint = [uri|https://login.microsoftonline.com/common/oauth2/v2.0/authorize|]+    { idpAuthorizeEndpoint = [uri|https://login.microsoftonline.com/common/oauth2/v2.0/authorize|]     , idpTokenEndpoint = [uri|https://login.microsoftonline.com/common/oauth2/v2.0/token|]+    , idpUserInfoEndpoint = [uri|https://graph.microsoft.com/oidc/userinfo|]+    , idpDeviceAuthorizationEndpoint = Just [uri|https://login.microsoftonline.com/common/oauth2/v2.0/devicecode|]     }++mkAzureIdp ::+  MonadIO m =>+  -- | Full domain with no http protocol. e.g. @contoso.onmicrosoft.com@+  Text ->+  ExceptT Text m (Idp AzureAD)+mkAzureIdp domain = do+  OpenIDConfiguration {..} <- fetchWellKnown ("login.microsoftonline.com/" <> domain <> "/v2.0")+  pure $+    Idp+      { idpUserInfoEndpoint = userinfoEndpoint+      , idpAuthorizeEndpoint = authorizationEndpoint+      , idpTokenEndpoint = tokenEndpoint+      , idpDeviceAuthorizationEndpoint = Just deviceAuthorizationEndpoint+      }  -- | https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo data AzureADUser = AzureADUser
+ src/Network/OAuth2/Provider/DropBox.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE QuasiQuotes #-}++-- | [DropBox oauth guide](https://developers.dropbox.com/oauth-guide)+module Network.OAuth2.Provider.DropBox 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.OAuth.OAuth2+import Network.OAuth2.Experiment+import Network.OAuth2.Provider+import URI.ByteString.QQ++sampleDropBoxAuthorizationCodeApp :: AuthorizationCodeApplication+sampleDropBoxAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.empty+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-dropbox-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    }++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 [])++defaultDropBoxIdp :: Idp DropBox+defaultDropBoxIdp =+  Idp+    { idpAuthorizeEndpoint = [uri|https://www.dropbox.com/1/oauth2/authorize|]+    , idpTokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]+    , -- https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account+      idpUserInfoEndpoint = [uri|https://api.dropboxapi.com/2/users/get_current_account|]+    , idpDeviceAuthorizationEndpoint = Nothing+    }++newtype DropBoxUserName = DropBoxUserName {displayName :: Text}+  deriving (Show, Generic)++data DropBoxUser = DropBoxUser+  { email :: Text+  , name :: DropBoxUserName+  }+  deriving (Show, Generic)++instance FromJSON DropBoxUserName where+  parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}++instance FromJSON DropBoxUser
− src/Network/OAuth2/Provider/Dropbox.hs
@@ -1,57 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}--module Network.OAuth2.Provider.Dropbox where--import Data.Aeson-import Data.Map.Strict qualified as Map-import Data.Set qualified as Set-import Data.Text.Lazy (Text)-import GHC.Generics-import Network.OAuth.OAuth2-import Network.OAuth2.Experiment-import URI.ByteString.QQ--data Dropbox = Dropbox deriving (Eq, Show)--type instance IdpUserInfo Dropbox = DropboxUser--defaultDropboxApp :: IdpApplication 'AuthorizationCode Dropbox-defaultDropboxApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.empty-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-dropbox-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = defaultDropboxIdp-    }--defaultDropboxIdp :: Idp Dropbox-defaultDropboxIdp =-  Idp-    { idpFetchUserInfo = \mgr at url -> authPostJSON @(IdpUserInfo Dropbox) mgr at url []-    , idpAuthorizeEndpoint = [uri|https://www.dropbox.com/1/oauth2/authorize|]-    , idpTokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]-    , idpUserInfoEndpoint = [uri|https://api.dropboxapi.com/2/users/get_current_account|]-    }--newtype DropboxUserName = DropboxUserName {displayName :: Text}-  deriving (Show, Generic)--data DropboxUser = DropboxUser-  { email :: Text-  , name :: DropboxUserName-  }-  deriving (Show, Generic)--instance FromJSON DropboxUserName where-  parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}--instance FromJSON DropboxUser
src/Network/OAuth2/Provider/Facebook.hs view
@@ -1,45 +1,50 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-} +-- | [Facebook Login](http://developers.facebook.com/docs/facebook-login/) module Network.OAuth2.Provider.Facebook where -import Data.Aeson+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..))+import Data.Aeson (FromJSON)+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.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider import URI.ByteString.QQ -data Facebook = Facebook deriving (Eq, Show)--type instance IdpUserInfo Facebook = FacebookUser--defaultFacebookApp :: IdpApplication 'AuthorizationCode Facebook-defaultFacebookApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.fromList ["user_about_me", "email"]-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-facebook-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretPost-    , idp = defaultFacebookIdp+sampleFacebookAuthorizationCodeApp :: AuthorizationCodeApplication+sampleFacebookAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["user_about_me", "email"]+    , acAuthorizeRequestExtraParams = Map.empty+    , acAuthorizeState = "CHANGE_ME"+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-facebook-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretPost     } +fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest+ defaultFacebookIdp :: Idp Facebook defaultFacebookIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Facebook)-    , idpUserInfoEndpoint = [uri|https://graph.facebook.com/me?fields=id,name,email|]+    { 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|]+    , idpDeviceAuthorizationEndpoint = Nothing     }  data FacebookUser = FacebookUser
src/Network/OAuth2/Provider/Fitbit.hs view
@@ -1,45 +1,50 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-} +-- | [Fitbit Authorization developer guide](https://dev.fitbit.com/build/reference/web-api/developer-guide/authorization/) module Network.OAuth2.Provider.Fitbit where  import Control.Monad (mzero)+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 Network.HTTP.Conduit (Manager) import Network.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider import URI.ByteString.QQ -data Fitbit = Fitbit deriving (Eq, Show)--type instance IdpUserInfo Fitbit = FitbitUser--defaultFitbitApp :: IdpApplication 'AuthorizationCode Fitbit-defaultFitbitApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.empty-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-fitbit-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = defaultFitbitIdp+sampleFitbitAuthorizationCodeApp :: AuthorizationCodeApplication+sampleFitbitAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.empty+    , acAuthorizeRequestExtraParams = Map.empty+    , acAuthorizeState = "CHANGE_ME"+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-fitbit-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretBasic     } +fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest+ defaultFitbitIdp :: Idp Fitbit defaultFitbitIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Fitbit)-    , idpUserInfoEndpoint = [uri|https://api.fitbit.com/1/user/-/profile.json|]+    { idpUserInfoEndpoint = [uri|https://api.fitbit.com/1/user/-/profile.json|]     , idpAuthorizeEndpoint = [uri|https://www.fitbit.com/oauth2/authorize|]     , idpTokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]+    , idpDeviceAuthorizationEndpoint = Nothing     }  data FitbitUser = FitbitUser
+ src/Network/OAuth2/Provider/GitHub.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE QuasiQuotes #-}++-- | [Github build oauth applications guide](https://docs.github.com/en/developers/apps/building-oauth-apps)+module Network.OAuth2.Provider.GitHub where++import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..))+import Data.Aeson (FromJSON)+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.OAuth.OAuth2+import Network.OAuth2.Experiment+import Network.OAuth2.Provider+import URI.ByteString.QQ++sampleGithubAuthorizationCodeApp :: AuthorizationCodeApplication+sampleGithubAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.empty+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-github-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    }++fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest++defaultGithubIdp :: Idp GitHub+defaultGithubIdp =+  Idp+    { idpUserInfoEndpoint = [uri|https://api.github.com/user|]+    , idpAuthorizeEndpoint = [uri|https://github.com/login/oauth/authorize|]+    , idpTokenEndpoint = [uri|https://github.com/login/oauth/access_token|]+    , idpDeviceAuthorizationEndpoint = Just [uri|https://github.com/login/device/code|]+    }++data GitHubUser = GitHubUser+  { name :: Text+  , id :: Integer+  }+  deriving (Show, Generic)++instance FromJSON GitHubUser
− src/Network/OAuth2/Provider/Github.hs
@@ -1,52 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}---- | https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps-module Network.OAuth2.Provider.Github where--import Data.Aeson-import Data.Map.Strict qualified as Map-import Data.Set qualified as Set-import Data.Text.Lazy (Text)-import GHC.Generics-import Network.OAuth.OAuth2-import Network.OAuth2.Experiment-import URI.ByteString.QQ--data Github = Github deriving (Eq, Show)--type instance IdpUserInfo Github = GithubUser--defaultGithubApp :: IdpApplication 'AuthorizationCode Github-defaultGithubApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.empty-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-github-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = defaultGithubIdp-    }--defaultGithubIdp :: Idp Github-defaultGithubIdp =-  Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Github)-    , idpUserInfoEndpoint = [uri|https://api.github.com/user|]-    , idpAuthorizeEndpoint = [uri|https://github.com/login/oauth/authorize|]-    , idpTokenEndpoint = [uri|https://github.com/login/oauth/access_token|]-    }--data GithubUser = GithubUser-  { name :: Text-  , id :: Integer-  }-  deriving (Show, Generic)--instance FromJSON GithubUser
src/Network/OAuth2/Provider/Google.hs view
@@ -1,18 +1,16 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-} +-- | [Google build oauth2 web server application](https://developers.google.com/identity/protocols/oauth2/web-server) module Network.OAuth2.Provider.Google where +import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..)) import Crypto.PubKey.RSA.Types import Data.Aeson import Data.Aeson qualified as Aeson import Data.Bifunctor+import Data.ByteString.Contrib+import Data.ByteString.Lazy.Char8 qualified as BSL import Data.Map.Strict qualified as Map import Data.Maybe import Data.Set qualified as Set@@ -24,9 +22,10 @@ import Jose.Jwa import Jose.Jws import Jose.Jwt+import Network.HTTP.Conduit (Manager) import Network.OAuth.OAuth2 import Network.OAuth2.Experiment-import Network.OAuth2.Provider.Utils+import Network.OAuth2.Provider import OpenSSL.EVP.PKey (toKeyPair) import OpenSSL.PEM (   PemPasswordSupply (PwNone),@@ -39,32 +38,30 @@ To test at google playground, set redirect uri to "https://developers.google.com/oauthplayground" -} -data Google = Google deriving (Eq, Show)--type instance IdpUserInfo Google = GoogleUser- -- * Authorization Code flow -defaultGoogleApp :: IdpApplication 'AuthorizationCode Google-defaultGoogleApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope =-        Set.fromList-          [ "https://www.googleapis.com/auth/userinfo.email"-          , "https://www.googleapis.com/auth/userinfo.profile"-          ]-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-google-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = defaultGoogleIdp+sampleGoogleAuthorizationCodeApp :: AuthorizationCodeApplication+sampleGoogleAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acName = "sample-google-authorization-code-app"+    , acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"]+    , acAuthorizeState = "CHANGE_ME"+    , acRedirectUri = [uri|http://localhost|]+    , acAuthorizeRequestExtraParams = Map.empty+    , acTokenRequestAuthenticationMethod = ClientSecretBasic     }  -- * Service Account +sampleServiceAccountApp :: Jwt -> JwtBearerApplication+sampleServiceAccountApp jwt =+  JwtBearerApplication+    { jbName = "sample-google-service-account-app"+    , jbJwtAssertion = unJwt jwt+    }+ -- | Service account key (in JSON format) that download from google data GoogleServiceAccountKey = GoogleServiceAccountKey   { privateKey :: String@@ -138,23 +135,23 @@           }     Nothing -> Left "unable to parse PEM to RSA key" -defaultServiceAccountApp :: Jwt -> IdpApplication 'JwtBearer Google-defaultServiceAccountApp jwt =-  JwtBearerIdpApplication-    { idpAppName = "google-sa-app"-    , idpAppJwt = unJwt jwt-    , idp = defaultGoogleIdp-    }- -- * IDP +fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest+ defaultGoogleIdp :: Idp Google defaultGoogleIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Google)-    , idpAuthorizeEndpoint = [uri|https://accounts.google.com/o/oauth2/v2/auth|]+    { idpAuthorizeEndpoint = [uri|https://accounts.google.com/o/oauth2/v2/auth|]     , idpTokenEndpoint = [uri|https://oauth2.googleapis.com/token|]     , idpUserInfoEndpoint = [uri|https://www.googleapis.com/oauth2/v2/userinfo|]+    , idpDeviceAuthorizationEndpoint = Just [uri|https://oauth2.googleapis.com/device/code|]     }  -- requires scope "https://www.googleapis.com/auth/userinfo.profile" to obtain "name".
+ src/Network/OAuth2/Provider/LinkedIn.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE QuasiQuotes #-}++-- | [LinkedIn Authenticating with OAuth 2.0 Overview](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authentication?context=linkedin%2Fcontext)+module Network.OAuth2.Provider.LinkedIn where++import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..))+import Data.Aeson (FromJSON)+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.OAuth.OAuth2+import Network.OAuth2.Experiment+import Network.OAuth2.Provider+import URI.ByteString.QQ (uri)++sampleLinkedInAuthorizationCodeApp :: AuthorizationCodeApplication+sampleLinkedInAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["r_liteprofile"]+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-linkedin-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretPost+    }++fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest++defaultLinkedInIdp :: Idp LinkedIn+defaultLinkedInIdp =+  Idp+    { idpUserInfoEndpoint = [uri|https://api.linkedin.com/v2/me|]+    , 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+  }+  deriving (Show, Generic, Eq)++instance FromJSON LinkedInUser
− src/Network/OAuth2/Provider/Linkedin.hs
@@ -1,51 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}--module Network.OAuth2.Provider.Linkedin where--import Data.Aeson-import Data.Map.Strict qualified as Map-import Data.Set qualified as Set-import Data.Text.Lazy (Text)-import GHC.Generics-import Network.OAuth.OAuth2-import Network.OAuth2.Experiment-import URI.ByteString.QQ--data Linkedin = Linkedin deriving (Eq, Show)--type instance IdpUserInfo Linkedin = LinkedinUser--defaultLinkedinApp :: IdpApplication 'AuthorizationCode Linkedin-defaultLinkedinApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.fromList ["r_liteprofile"]-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-linkedin-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretPost-    , idp = defaultLinkedinIdp-    }--defaultLinkedinIdp :: Idp Linkedin-defaultLinkedinIdp =-  Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Linkedin)-    , idpUserInfoEndpoint = [uri|https://api.linkedin.com/v2/me|]-    , idpAuthorizeEndpoint = [uri|https://www.linkedin.com/oauth/v2/authorization|]-    , idpTokenEndpoint = [uri|https://www.linkedin.com/oauth/v2/accessToken|]-    }--data LinkedinUser = LinkedinUser-  { localizedFirstName :: Text-  , localizedLastName :: Text-  }-  deriving (Show, Generic, Eq)--instance FromJSON LinkedinUser
src/Network/OAuth2/Provider/Okta.hs view
@@ -1,60 +1,55 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-} +-- 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)++-- | [Okta OIDC & OAuth2 API](https://developer.okta.com/docs/reference/api/oidc/) module Network.OAuth2.Provider.Okta where -import Control.Monad.IO.Class-import Control.Monad.Trans.Except+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..)) import Data.Aeson import Data.Aeson qualified as Aeson import Data.Bifunctor+import Data.ByteString.Contrib+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 Data.Text.Lazy as TL import Data.Time import GHC.Generics import Jose.Jwa import Jose.Jwk import Jose.Jws import Jose.Jwt+import Network.HTTP.Conduit (Manager) import Network.OAuth.OAuth2 import Network.OAuth2.Experiment-import Network.OAuth2.Provider.Utils+import Network.OAuth2.Provider import Network.OIDC.WellKnown import URI.ByteString.QQ -data Okta = Okta deriving (Eq, Show)--type instance IdpUserInfo Okta = OktaUser--defaultOktaApp :: Idp Okta -> IdpApplication 'AuthorizationCode Okta-defaultOktaApp i =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.fromList ["openid", "profile", "email"]-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-okta-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = i+sampleOktaAuthorizationCodeApp :: AuthorizationCodeApplication+sampleOktaAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["openid", "profile", "email"]+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-okta-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretBasic     } -defaultOktaIdp :: Idp Okta-defaultOktaIdp =-  Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Okta)-    , idpUserInfoEndpoint = [uri|https://foo.okta.com/oauth2/v1/userinfo|]-    , idpAuthorizeEndpoint =-        [uri|https://foo.okta.com/oauth2/v1/authorize|]-    , idpTokenEndpoint =-        [uri|https://foo.okta.com/oauth2/v1/token|]-    }+fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest  mkOktaIdp ::   MonadIO m =>@@ -62,20 +57,20 @@   Text ->   ExceptT Text m (Idp Okta) mkOktaIdp domain = do-  OpenIDConfigurationUris {..} <- fetchWellKnownUris domain-  pure-    ( defaultOktaIdp-        { idpUserInfoEndpoint = userinfoUri-        , idpAuthorizeEndpoint = authorizationUri-        , idpTokenEndpoint = tokenUri-        }-    )+  OpenIDConfiguration {..} <- fetchWellKnown domain+  pure $+    Idp+      { idpUserInfoEndpoint = userinfoEndpoint+      , idpAuthorizeEndpoint = authorizationEndpoint+      , idpTokenEndpoint = tokenEndpoint+      , idpDeviceAuthorizationEndpoint = Just deviceAuthorizationEndpoint+      }  mkOktaClientCredentialAppJwt ::   Jwk ->   ClientId ->-  Idp Okta ->-  IO (Either String Jwt)+  Idp i ->+  IO (Either Text Jwt) mkOktaClientCredentialAppJwt jwk cid idp = do   now <- getCurrentTime   let cidStr = unClientId cid@@ -83,19 +78,16 @@         bsToStrict $           Aeson.encode $             Aeson.object-              [ "iss" .= cidStr-              , "sub" .= cidStr-              , "aud" .= idpTokenEndpoint idp+              [ "aud" .= idpTokenEndpoint idp               , "exp" .= tToSeconds (addUTCTime (secondsToNominalDiffTime 300) now) -- 5 minutes expiration time               , "iat" .= tToSeconds now+              , "iss" .= cidStr+              , "sub" .= cidStr               ]-  first show <$> jwkEncode RS256 jwk (Claims payload)+  first (TL.pack . show) <$> jwkEncode RS256 jwk (Claims payload)   where     tToSeconds = formatTime defaultTimeLocale "%s" --- 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) data OktaUser = OktaUser   { name :: Text   , preferredUsername :: Text
src/Network/OAuth2/Provider/Slack.hs view
@@ -1,47 +1,53 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-} +-- | [Sign in with Slack](https://api.slack.com/authentication/sign-in-with-slack)+--+--   * [Using OAuth 2.0](https://api.slack.com/legacy/oauth) module Network.OAuth2.Provider.Slack where -import Data.Aeson+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..))+import Data.Aeson (FromJSON)+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.OAuth.OAuth2 import Network.OAuth2.Experiment-import URI.ByteString.QQ--data Slack = Slack deriving (Show, Eq)--type instance IdpUserInfo Slack = SlackUser+import Network.OAuth2.Provider+import URI.ByteString.QQ (uri) -defaultSlackApp :: IdpApplication 'AuthorizationCode Slack-defaultSlackApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.fromList ["openid", "profile"]-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idpAppName = "default-slack-App"-    , idp = defaultSlackIdp+sampleSlackAuthorizationCodeApp :: AuthorizationCodeApplication+sampleSlackAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["openid", "profile"]+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acName = "sample-slack-authorization-code-app"     } --- https://api.slack.com/authentication/sign-in-with-slack+fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest+ -- https://slack.com/.well-known/openid-configuration defaultSlackIdp :: Idp Slack defaultSlackIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Slack)-    , idpUserInfoEndpoint = [uri|https://slack.com/api/openid.connect.userInfo|]+    { idpUserInfoEndpoint = [uri|https://slack.com/api/openid.connect.userInfo|]     , idpAuthorizeEndpoint = [uri|https://slack.com/openid/connect/authorize|]     , idpTokenEndpoint = [uri|https://slack.com/api/openid.connect.token|]+    , idpDeviceAuthorizationEndpoint = Nothing     }  data SlackUser = SlackUser
src/Network/OAuth2/Provider/StackExchange.hs view
@@ -1,59 +1,71 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-} +-- | [StackExchange authentication guide](https://api.stackexchange.com/docs/authentication)+--+--    * [StackExchange Apps page](https://stackapps.com/apps/oauth) module Network.OAuth2.Provider.StackExchange where +import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Except (ExceptT (..)) import Data.Aeson import Data.ByteString (ByteString)+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.OAuth.OAuth2 import Network.OAuth2.Experiment-import URI.ByteString-import URI.ByteString.QQ+import Network.OAuth2.Provider+import URI.ByteString (URI)+import URI.ByteString.QQ (uri)  -- fix key from your application edit page -- https://stackapps.com/apps/oauth stackexchangeAppKey :: ByteString stackexchangeAppKey = "" -data StackExchange = StackExchange deriving (Eq, Show)--type instance IdpUserInfo StackExchange = StackExchangeResp+userInfoEndpoint :: URI+userInfoEndpoint =+  appendQueryParams+    [ ("key", stackexchangeAppKey)+    , ("site", "stackoverflow")+    ]+    [uri|https://api.stackexchange.com/2.2/me|] -defaultStackExchangeApp :: IdpApplication 'AuthorizationCode StackExchange-defaultStackExchangeApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.empty-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-stackexchange-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretPost-    , idp = defaultStackexchangeIdp+sampleStackExchangeAuthorizationCodeApp :: AuthorizationCodeApplication+sampleStackExchangeAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.empty+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-stackexchange-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretPost     } -defaultStackexchangeIdp :: Idp StackExchange-defaultStackexchangeIdp =+fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequestWithCustomMethod (authGetJSONWithAuthMethod AuthInRequestQuery)++defaultStackExchangeIdp :: Idp StackExchange+defaultStackExchangeIdp =   Idp-    { idpFetchUserInfo = authGetJSONWithAuthMethod @_ @(IdpUserInfo StackExchange) AuthInRequestQuery-    , -- Only StackExchange has such specical app key which has to be append in userinfo uri.+    { -- 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 =-        appendStackExchangeAppKey-          [uri|https://api.stackexchange.com/2.2/me?site=stackoverflow|]-          stackexchangeAppKey+      idpUserInfoEndpoint = userInfoEndpoint     , idpAuthorizeEndpoint = [uri|https://stackexchange.com/oauth|]     , idpTokenEndpoint = [uri|https://stackexchange.com/oauth/access_token|]+    , idpDeviceAuthorizationEndpoint = Nothing     }  data StackExchangeResp = StackExchangeResp
src/Network/OAuth2/Provider/Twitter.hs view
@@ -1,47 +1,51 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-} --- | https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code+-- | [Twitter OAuth2 guide](https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code) module Network.OAuth2.Provider.Twitter 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.Char (toLower) 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.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider import URI.ByteString.QQ -data Twitter = Twitter deriving (Eq, Show)--type instance IdpUserInfo Twitter = TwitterUserResp--defaultTwitterApp :: IdpApplication 'AuthorizationCode Twitter-defaultTwitterApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.fromList ["tweet.read", "users.read"]-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppName = "default-twitter-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = defaultTwitterIdp+sampleTwitterAuthorizationCodeApp :: AuthorizationCodeApplication+sampleTwitterAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["tweet.read", "users.read"]+    , acAuthorizeState = "CHANGE_ME"+    , acRedirectUri = [uri|http://localhost|]+    , acName = "sample-twitter-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretBasic+    , acAuthorizeRequestExtraParams = Map.empty     } +fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest+ defaultTwitterIdp :: Idp Twitter defaultTwitterIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Twitter)-    , idpUserInfoEndpoint = [uri|https://api.twitter.com/2/users/me|]+    { idpUserInfoEndpoint = [uri|https://api.twitter.com/2/users/me|]     , idpAuthorizeEndpoint = [uri|https://twitter.com/i/oauth2/authorize|]     , idpTokenEndpoint = [uri|https://api.twitter.com/2/oauth2/token|]+    , idpDeviceAuthorizationEndpoint = Nothing     }  data TwitterUser = TwitterUser
− src/Network/OAuth2/Provider/Utils.hs
@@ -1,20 +0,0 @@-{-# LANGUAGE CPP #-}--module Network.OAuth2.Provider.Utils where--import Data.ByteString qualified as BS-import Data.ByteString.Lazy qualified as BSL--bsToStrict :: BSL.ByteString -> BS.ByteString-#if MIN_VERSION_bytestring(0,11,0)-bsToStrict = BS.toStrict-#else-bsToStrict = BSL.toStrict-#endif--bsFromStrict :: BS.ByteString -> BSL.ByteString-#if MIN_VERSION_bytestring(0,11,0)-bsFromStrict = BS.fromStrict-#else-bsFromStrict = BSL.fromStrict-#endif
src/Network/OAuth2/Provider/Weibo.hs view
@@ -1,41 +1,50 @@ {-# LANGUAGE QuasiQuotes #-} +-- | [微博授权机制](https://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6) module Network.OAuth2.Provider.Weibo 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.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider import URI.ByteString.QQ -data Weibo = Weibo deriving (Eq, Show)--type instance IdpUserInfo Weibo = WeiboUID--defaultWeiboApp :: IdpApplication 'AuthorizationCode Weibo-defaultWeiboApp =-  AuthorizationCodeIdpApplication-    { idpAppName = "default-weibo-App"-    , idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.empty-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppAuthorizeExtraParams = Map.empty-    , idpAppRedirectUri = [uri|http://localhost|]-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = defaultWeiboIdp+sampleWeiboAuthorizationCodeApp :: AuthorizationCodeApplication+sampleWeiboAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acName = "sample-weibo-authorization-code-app"+    , acClientId = ""+    , acClientSecret = ""+    , acScope = Set.empty+    , acAuthorizeState = "CHANGE_ME"+    , acAuthorizeRequestExtraParams = Map.empty+    , acRedirectUri = [uri|http://localhost|]+    , acTokenRequestAuthenticationMethod = ClientSecretBasic     } +fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequestWithCustomMethod (authGetJSONWithAuthMethod AuthInRequestQuery)+ defaultWeiboIdp :: Idp Weibo defaultWeiboIdp =   Idp-    { idpFetchUserInfo = authGetJSONWithAuthMethod @_ @(IdpUserInfo Weibo) AuthInRequestQuery-    , idpUserInfoEndpoint = [uri|https://api.weibo.com/2/account/get_uid.json|]+    { idpUserInfoEndpoint = [uri|https://api.weibo.com/2/account/get_uid.json|]     , idpAuthorizeEndpoint = [uri|https://api.weibo.com/oauth2/authorize|]     , idpTokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]+    , idpDeviceAuthorizationEndpoint = Nothing     }  -- | http://open.weibo.com/wiki/2/users/show
src/Network/OAuth2/Provider/ZOHO.hs view
@@ -1,45 +1,50 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-} +-- | [ZOHO oauth overview](https://www.zoho.com/crm/developer/docs/api/v2/oauth-overview.html) module Network.OAuth2.Provider.ZOHO 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.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider import URI.ByteString.QQ -data ZOHO = ZOHO deriving (Eq, Show)--type instance IdpUserInfo ZOHO = ZOHOUserResp--defaultZohoApp :: IdpApplication 'AuthorizationCode ZOHO-defaultZohoApp =-  AuthorizationCodeIdpApplication-    { idpAppClientId = ""-    , idpAppClientSecret = ""-    , idpAppScope = Set.fromList ["ZohoCRM.users.READ"]-    , idpAppAuthorizeExtraParams = Map.fromList [("access_type", "offline"), ("prompt", "consent")]-    , idpAppAuthorizeState = "CHANGE_ME"-    , idpAppRedirectUri = [uri|http://localhost/oauth2/callback|]-    , idpAppName = "default-zoho-App"-    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic-    , idp = defaultZohoIdp+sampleZohoAuthorizationCodeApp :: AuthorizationCodeApplication+sampleZohoAuthorizationCodeApp =+  AuthorizationCodeApplication+    { acClientId = ""+    , acClientSecret = ""+    , acScope = Set.fromList ["ZohoCRM.users.READ"]+    , acAuthorizeRequestExtraParams = Map.fromList [("access_type", "offline"), ("prompt", "consent")]+    , acAuthorizeState = "CHANGE_ME"+    , acRedirectUri = [uri|http://localhost/oauth2/callback|]+    , acName = "sample-zoho-authorization-code-app"+    , acTokenRequestAuthenticationMethod = ClientSecretBasic     } +fetchUserInfo ::+  (MonadIO m, HasUserInfoRequest a, FromJSON b) =>+  IdpApplication i a ->+  Manager ->+  AccessToken ->+  ExceptT BSL.ByteString m b+fetchUserInfo = conduitUserInfoRequest+ defaultZohoIdp :: Idp ZOHO defaultZohoIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo ZOHO)-    , idpUserInfoEndpoint = [uri|https://www.zohoapis.com/crm/v2/users|]+    { idpUserInfoEndpoint = [uri|https://www.zohoapis.com/crm/v2/users|]     , idpAuthorizeEndpoint = [uri|https://accounts.zoho.com/oauth/v2/auth|]     , idpTokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]+    , idpDeviceAuthorizationEndpoint = Nothing     }  -- `oauth/user/info` url does not work and find answer from
src/Network/OIDC/WellKnown.hs view
@@ -1,41 +1,47 @@-{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE CPP #-}  module Network.OIDC.WellKnown where  import Control.Monad.Except+#if MIN_VERSION_base(4,18,0)+import Control.Monad.IO.Class+#endif import Data.Aeson+import Data.Aeson.Types import Data.Bifunctor import Data.ByteString.Lazy (ByteString)-import Data.ByteString.Lazy qualified as BSL import Data.Text.Lazy (Text) import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Encoding qualified as TL-import GHC.Generics import Network.HTTP.Simple import Network.HTTP.Types.Status import URI.ByteString+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-  { issuer :: Text-  , authorizationEndpoint :: Text-  , tokenEndpoint :: Text-  , userinfoEndpoint :: Text-  , jwksUri :: Text-  }-  deriving (Generic)--data OpenIDConfigurationUris = OpenIDConfigurationUris-  { authorizationUri :: URI-  , tokenUri :: URI-  , userinfoUri :: URI+  { issuer :: URI+  , authorizationEndpoint :: URI+  , tokenEndpoint :: URI+  , userinfoEndpoint :: URI   , jwksUri :: URI+  , deviceAuthorizationEndpoint :: URI   }-  deriving (Generic)+  deriving (Show, Eq)  instance FromJSON OpenIDConfiguration where-  parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}+  parseJSON :: Value -> Parser OpenIDConfiguration+  parseJSON = withObject "parseJSON OpenIDConfiguration" $ \t -> do+    issuer <- t .: "issuer"+    authorizationEndpoint <- t .: "authorization_endpoint"+    tokenEndpoint <- t .: "token_endpoint"+    userinfoEndpoint <- t .: "userinfo_endpoint"+    jwksUri <- t .: "jwks_uri"+    deviceAuthorizationEndpoint <- t .: "device_authorization_endpoint"+    pure OpenIDConfiguration {..}  wellknownUrl :: TL.Text wellknownUrl = "/.well-known/openid-configuration"@@ -49,23 +55,7 @@   let uri = "https://" <> domain <> wellknownUrl   req <- liftIO $ parseRequest (TL.unpack uri)   resp <- httpLbs req-  return (handleWellKnownResponse resp)--fetchWellKnownUris :: MonadIO m => TL.Text -> ExceptT Text m OpenIDConfigurationUris-fetchWellKnownUris domain = do-  OpenIDConfiguration {..} <- fetchWellKnown domain-  withExceptT (TL.pack . show) $ do-    ae <- ExceptT $ pure (parseURI strictURIParserOptions $ BSL.toStrict $ TL.encodeUtf8 authorizationEndpoint)-    te <- ExceptT $ pure (parseURI strictURIParserOptions $ BSL.toStrict $ TL.encodeUtf8 tokenEndpoint)-    ue <- ExceptT $ pure (parseURI strictURIParserOptions $ BSL.toStrict $ TL.encodeUtf8 userinfoEndpoint)-    jwks <- ExceptT $ pure (parseURI strictURIParserOptions $ BSL.toStrict $ TL.encodeUtf8 jwksUri)-    pure-      OpenIDConfigurationUris-        { authorizationUri = ae-        , tokenUri = te-        , userinfoUri = ue-        , jwksUri = jwks-        }+  pure (handleWellKnownResponse resp)  handleWellKnownResponse :: Response ByteString -> Either Text OpenIDConfiguration handleWellKnownResponse resp = do
+ test/Network/OIDC/WellKnownSpec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE QuasiQuotes #-}++module Network.OIDC.WellKnownSpec where++import Data.Aeson qualified as Aeson+import Network.OIDC.WellKnown (OpenIDConfiguration (..))+import Test.Hspec+import URI.ByteString.QQ++spec :: Spec+spec = do+  describe "parseJSON OpenIDConfiguration" $ do+    it "parse openidConfiguration response" $ do+      let response =+            "{"+              <> "\"issuer\": \"https://foo.com\","+              <> "\"authorization_endpoint\": \"https://foo.com/auth\","+              <> "\"token_endpoint\": \"https://foo.com/token\","+              <> "\"userinfo_endpoint\": \"https://foo.com/userinfo\","+              <> "\"jwks_uri\": \"https://foo.com/jwks\","+              <> "\"device_authorization_endpoint\": \"https://foo.com/device-code\""+              <> "}"+      let expected =+            OpenIDConfiguration+              { issuer = [uri|https://foo.com|]+              , authorizationEndpoint = [uri|https://foo.com/auth|]+              , tokenEndpoint = [uri|https://foo.com/token|]+              , userinfoEndpoint = [uri|https://foo.com/userinfo|]+              , jwksUri = [uri|https://foo.com/jwks|]+              , deviceAuthorizationEndpoint = [uri|https://foo.com/device-code|]+              }+      Aeson.eitherDecode response `shouldBe` Right expected
+ test/Spec.hs view
@@ -0,0 +1,2 @@+-- file test/Spec.hs+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}