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 +53/−32
- src/Data/ByteString/Contrib.hs +20/−0
- src/Network/OAuth2/Provider.hs +20/−0
- src/Network/OAuth2/Provider/Auth0.hs +38/−45
- src/Network/OAuth2/Provider/AzureAD.hs +53/−20
- src/Network/OAuth2/Provider/DropBox.hs +63/−0
- src/Network/OAuth2/Provider/Dropbox.hs +0/−57
- src/Network/OAuth2/Provider/Facebook.hs +28/−23
- src/Network/OAuth2/Provider/Fitbit.hs +27/−22
- src/Network/OAuth2/Provider/GitHub.hs +56/−0
- src/Network/OAuth2/Provider/Github.hs +0/−52
- src/Network/OAuth2/Provider/Google.hs +35/−38
- src/Network/OAuth2/Provider/LinkedIn.hs +56/−0
- src/Network/OAuth2/Provider/Linkedin.hs +0/−51
- src/Network/OAuth2/Provider/Okta.hs +44/−52
- src/Network/OAuth2/Provider/Slack.hs +31/−25
- src/Network/OAuth2/Provider/StackExchange.hs +41/−29
- src/Network/OAuth2/Provider/Twitter.hs +27/−23
- src/Network/OAuth2/Provider/Utils.hs +0/−20
- src/Network/OAuth2/Provider/Weibo.hs +27/−18
- src/Network/OAuth2/Provider/ZOHO.hs +27/−22
- src/Network/OIDC/WellKnown.hs +24/−34
- test/Network/OIDC/WellKnownSpec.hs +32/−0
- test/Spec.hs +2/−0
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 #-}