packages feed

hoauth2-providers 0.1 → 0.2

raw patch · 17 files changed

+487/−259 lines, 17 filesdep +HsOpenSSLdep +cryptonitedep +jose-jwtdep ~hoauth2PVP ok

version bump matches the API change (PVP)

Dependencies added: HsOpenSSL, cryptonite, jose-jwt, time

Dependency ranges changed: hoauth2

API changes (from Hackage documentation)

- Network.OAuth2.Provider.AzureAD: [mail] :: AzureADUser -> Text
- Network.OAuth2.Provider.AzureAD: newtype AzureADUser
- Network.OAuth2.Provider.Dropbox: DropboxName :: Text -> DropboxName
- Network.OAuth2.Provider.Dropbox: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.Dropbox.DropboxName
- Network.OAuth2.Provider.Dropbox: instance GHC.Generics.Generic Network.OAuth2.Provider.Dropbox.DropboxName
- Network.OAuth2.Provider.Dropbox: instance GHC.Show.Show Network.OAuth2.Provider.Dropbox.DropboxName
- Network.OAuth2.Provider.Dropbox: newtype DropboxName
+ Network.OAuth2.Provider.AzureAD: [email] :: AzureADUser -> Text
+ Network.OAuth2.Provider.AzureAD: [familyName] :: AzureADUser -> Text
+ Network.OAuth2.Provider.AzureAD: [givenName] :: AzureADUser -> Text
+ Network.OAuth2.Provider.AzureAD: [name] :: AzureADUser -> Text
+ Network.OAuth2.Provider.AzureAD: [sub] :: AzureADUser -> Text
+ Network.OAuth2.Provider.AzureAD: data AzureADUser
+ Network.OAuth2.Provider.Dropbox: DropboxUserName :: Text -> DropboxUserName
+ 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.DropboxUserName
+ Network.OAuth2.Provider.Dropbox: instance GHC.Show.Show Network.OAuth2.Provider.Dropbox.DropboxUserName
+ Network.OAuth2.Provider.Dropbox: newtype DropboxUserName
+ Network.OAuth2.Provider.Google: GoogleServiceAccountKey :: String -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> GoogleServiceAccountKey
+ Network.OAuth2.Provider.Google: [authProviderX509CertUrl] :: GoogleServiceAccountKey -> Text
+ Network.OAuth2.Provider.Google: [authUri] :: GoogleServiceAccountKey -> Text
+ Network.OAuth2.Provider.Google: [clientEmail] :: GoogleServiceAccountKey -> Text
+ Network.OAuth2.Provider.Google: [clientId] :: GoogleServiceAccountKey -> Text
+ Network.OAuth2.Provider.Google: [clientX509CertUrl] :: GoogleServiceAccountKey -> Text
+ Network.OAuth2.Provider.Google: [privateKeyId] :: GoogleServiceAccountKey -> Text
+ Network.OAuth2.Provider.Google: [privateKey] :: GoogleServiceAccountKey -> String
+ Network.OAuth2.Provider.Google: [projectId] :: GoogleServiceAccountKey -> Text
+ Network.OAuth2.Provider.Google: [tokenUri] :: GoogleServiceAccountKey -> Text
+ Network.OAuth2.Provider.Google: data GoogleServiceAccountKey
+ Network.OAuth2.Provider.Google: defaultServiceAccountApp :: Jwt -> IdpApplication 'JwtBearer Google
+ Network.OAuth2.Provider.Google: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.Google.GoogleServiceAccountKey
+ Network.OAuth2.Provider.Google: instance GHC.Generics.Generic Network.OAuth2.Provider.Google.GoogleServiceAccountKey
+ Network.OAuth2.Provider.Google: mkJwt :: PrivateKey -> Text -> Maybe Text -> Set Scope -> Idp Google -> IO (Either String Jwt)
+ Network.OAuth2.Provider.Google: readPemRsaKey :: String -> IO (Either String PrivateKey)
+ Network.OAuth2.Provider.Okta: mkOktaClientCredentialAppJwt :: Jwk -> ClientId -> Idp Okta -> IO (Either String Jwt)
+ Network.OAuth2.Provider.Twitter: Twitter :: Twitter
+ Network.OAuth2.Provider.Twitter: TwitterUser :: Text -> Text -> Text -> TwitterUser
+ Network.OAuth2.Provider.Twitter: TwitterUserResp :: TwitterUser -> TwitterUserResp
+ Network.OAuth2.Provider.Twitter: [id] :: TwitterUser -> Text
+ Network.OAuth2.Provider.Twitter: [name] :: TwitterUser -> Text
+ Network.OAuth2.Provider.Twitter: [twitterUserRespData] :: TwitterUserResp -> TwitterUser
+ Network.OAuth2.Provider.Twitter: [username] :: TwitterUser -> Text
+ Network.OAuth2.Provider.Twitter: data Twitter
+ Network.OAuth2.Provider.Twitter: data TwitterUser
+ Network.OAuth2.Provider.Twitter: defaultTwitterApp :: IdpApplication 'AuthorizationCode Twitter
+ Network.OAuth2.Provider.Twitter: defaultTwitterIdp :: Idp Twitter
+ Network.OAuth2.Provider.Twitter: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.Twitter.TwitterUser
+ Network.OAuth2.Provider.Twitter: instance Data.Aeson.Types.FromJSON.FromJSON Network.OAuth2.Provider.Twitter.TwitterUserResp
+ Network.OAuth2.Provider.Twitter: instance GHC.Classes.Eq Network.OAuth2.Provider.Twitter.Twitter
+ Network.OAuth2.Provider.Twitter: instance GHC.Generics.Generic Network.OAuth2.Provider.Twitter.TwitterUser
+ Network.OAuth2.Provider.Twitter: instance GHC.Generics.Generic Network.OAuth2.Provider.Twitter.TwitterUserResp
+ Network.OAuth2.Provider.Twitter: instance GHC.Show.Show Network.OAuth2.Provider.Twitter.Twitter
+ Network.OAuth2.Provider.Twitter: instance GHC.Show.Show Network.OAuth2.Provider.Twitter.TwitterUser
+ Network.OAuth2.Provider.Twitter: instance GHC.Show.Show Network.OAuth2.Provider.Twitter.TwitterUserResp
+ Network.OAuth2.Provider.Twitter: newtype TwitterUserResp
+ Network.OAuth2.Provider.Utils: bsFromStrict :: ByteString -> ByteString
+ Network.OAuth2.Provider.Utils: bsToStrict :: ByteString -> ByteString
- Network.OAuth2.Provider.AzureAD: AzureADUser :: Text -> AzureADUser
+ Network.OAuth2.Provider.AzureAD: AzureADUser :: Text -> Text -> Text -> Text -> Text -> AzureADUser
- Network.OAuth2.Provider.Dropbox: DropboxUser :: Text -> DropboxName -> DropboxUser
+ Network.OAuth2.Provider.Dropbox: DropboxUser :: Text -> DropboxUserName -> DropboxUser
- Network.OAuth2.Provider.Dropbox: [displayName] :: DropboxName -> Text
+ Network.OAuth2.Provider.Dropbox: [displayName] :: DropboxUserName -> Text
- Network.OAuth2.Provider.Dropbox: [name] :: DropboxUser -> DropboxName
+ Network.OAuth2.Provider.Dropbox: [name] :: DropboxUser -> DropboxUserName

Files

hoauth2-providers.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name:          hoauth2-providers-version:       0.1+version:       0.2 synopsis:      OAuth2 Identity Providers description:   A list of Identity Providers base on hoauth2 Haskell OAuth2 binding@@ -35,6 +35,8 @@     TypeApplications     TypeFamilies +  -- other-modules:+   exposed-modules:     Network.OAuth2.Provider.Auth0     Network.OAuth2.Provider.AzureAD@@ -47,6 +49,8 @@     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@@ -56,14 +60,18 @@     , 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.6+    , 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
src/Network/OAuth2/Provider/Auth0.hs view
@@ -8,6 +8,7 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +-- | https://auth0.com/docs/api/authentication#authorize-application module Network.OAuth2.Provider.Auth0 where  import Control.Monad.IO.Class@@ -30,26 +31,26 @@ 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+    { 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     }  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+    { 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|]     } @@ -62,17 +63,17 @@   OpenIDConfigurationUris {..} <- fetchWellKnownUris domain   pure     ( defaultAuth0Idp-        { idpUserInfoEndpoint = userinfoUri,-          idpAuthorizeEndpoint = authorizationUri,-          idpTokenEndpoint = tokenUri+        { idpUserInfoEndpoint = userinfoUri+        , idpAuthorizeEndpoint = authorizationUri+        , idpTokenEndpoint = tokenUri         }     ) --- | scopes: ["openid", "profile", "email"]+-- | https://auth0.com/docs/api/authentication#user-profile data Auth0User = Auth0User-  { name :: Text,-    email :: Text,-    sub :: Text+  { name :: Text+  , email :: Text+  , sub :: Text   }   deriving (Show, Generic) 
src/Network/OAuth2/Provider/AzureAD.hs view
@@ -15,29 +15,43 @@  type instance IdpUserInfo AzureAD = AzureADUser +-- 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.empty,-      idpAppAuthorizeState = "CHANGE_ME",-      idpAppAuthorizeExtraParams = Map.fromList [("resource", "https://graph.microsoft.com")],-      idpAppRedirectUri = [uri|http://localhost|],-      idpAppName = "default-azure-App",-      idpAppTokenRequestAuthenticationMethod = ClientSecretBasic,-      idp = defaultAzureADIdp+    { 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     } +-- | https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration defaultAzureADIdp :: Idp AzureAD defaultAzureADIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo AzureAD),-      idpUserInfoEndpoint = [uri|https://graph.microsoft.com/v1.0/me|],-      idpAuthorizeEndpoint = [uri|https://login.windows.net/common/oauth2/authorize|],-      idpTokenEndpoint = [uri|https://login.windows.net/common/oauth2/token|]+    { idpFetchUserInfo = authGetJSON @(IdpUserInfo AzureAD)+    , idpUserInfoEndpoint = [uri|https://graph.microsoft.com/oidc/userinfo|]+    , idpAuthorizeEndpoint = [uri|https://login.microsoftonline.com/common/oauth2/v2.0/authorize|]+    , idpTokenEndpoint = [uri|https://login.microsoftonline.com/common/oauth2/v2.0/token|]     } -newtype AzureADUser = AzureADUser {mail :: Text} deriving (Show, Generic)+-- | https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo+data AzureADUser = AzureADUser+  { sub :: Text+  , email :: Text+  , familyName :: Text+  , givenName :: Text+  , name :: Text+  }+  deriving (Show, Generic) -instance FromJSON AzureADUser+instance FromJSON AzureADUser where+  parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}
src/Network/OAuth2/Provider/Dropbox.hs view
@@ -22,36 +22,36 @@ 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+    { 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|]+    { 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 DropboxName = DropboxName {displayName :: Text}+newtype DropboxUserName = DropboxUserName {displayName :: Text}   deriving (Show, Generic)  data DropboxUser = DropboxUser-  { email :: Text,-    name :: DropboxName+  { email :: Text+  , name :: DropboxUserName   }   deriving (Show, Generic) -instance FromJSON DropboxName where+instance FromJSON DropboxUserName where   parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'} -instance FromJSON DropboxUser +instance FromJSON DropboxUser
src/Network/OAuth2/Provider/Facebook.hs view
@@ -15,7 +15,6 @@ import Network.OAuth2.Experiment import URI.ByteString.QQ --- | TODO: rename to Meta data Facebook = Facebook deriving (Eq, Show)  type instance IdpUserInfo Facebook = FacebookUser@@ -23,31 +22,31 @@ 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+    { 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     }  defaultFacebookIdp :: Idp Facebook defaultFacebookIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Facebook),-      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|]+    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Facebook)+    , 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|]     }  data FacebookUser = FacebookUser-  { id :: Text,-    name :: Text,-    email :: Text+  { id :: Text+  , name :: Text+  , email :: Text   }   deriving (Show, Generic) -instance FromJSON FacebookUser +instance FromJSON FacebookUser
src/Network/OAuth2/Provider/Fitbit.hs view
@@ -22,30 +22,30 @@ 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+    { idpAppClientId = ""+    , idpAppClientSecret = ""+    , idpAppScope = Set.empty+    , idpAppAuthorizeExtraParams = Map.empty+    , idpAppAuthorizeState = "CHANGE_ME"+    , idpAppRedirectUri = [uri|http://localhost|]+    , idpAppName = "default-fitbit-App"+    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic+    , idp = defaultFitbitIdp     }  defaultFitbitIdp :: Idp Fitbit defaultFitbitIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Fitbit),-      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|]+    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Fitbit)+    , 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|]     }  data FitbitUser = FitbitUser-  { userId :: Text,-    userName :: Text,-    userAge :: Int+  { userId :: Text+  , userName :: Text+  , userAge :: Int   }   deriving (Show, Eq) 
src/Network/OAuth2/Provider/Github.hs view
@@ -4,6 +4,7 @@ {-# 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@@ -15,7 +16,6 @@ import Network.OAuth2.Experiment import URI.ByteString.QQ --- | http://developer.github.com/v3/oauth/ data Github = Github deriving (Eq, Show)  type instance IdpUserInfo Github = GithubUser@@ -23,30 +23,30 @@ 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+    { 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|]+    { 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+  { name :: Text+  , id :: Integer   }   deriving (Show, Generic) -instance FromJSON GithubUser +instance FromJSON GithubUser
src/Network/OAuth2/Provider/Google.hs view
@@ -9,13 +9,30 @@  module Network.OAuth2.Provider.Google where +import Crypto.PubKey.RSA.Types import Data.Aeson+import Data.Aeson qualified as Aeson+import Data.Bifunctor import Data.Map.Strict qualified as Map+import Data.Maybe import Data.Set qualified as Set+import Data.Text qualified as T import Data.Text.Lazy (Text)+import Data.Text.Lazy qualified as TL+import Data.Time import GHC.Generics+import Jose.Jwa+import Jose.Jws+import Jose.Jwt import Network.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider.Utils+import OpenSSL.EVP.PKey (toKeyPair)+import OpenSSL.PEM (+  PemPasswordSupply (PwNone),+  readPrivateKey,+ )+import OpenSSL.RSA import URI.ByteString.QQ  {-@@ -26,39 +43,126 @@  type instance IdpUserInfo Google = GoogleUser +-- * Authorization Code flow+ defaultGoogleApp :: IdpApplication 'AuthorizationCode Google defaultGoogleApp =   AuthorizationCodeIdpApplication-    { idpAppClientId = "",-      idpAppClientSecret = "",-      idpAppScope =+    { 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+          [ "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     } +-- * Service Account++-- | Service account key (in JSON format) that download from google+data GoogleServiceAccountKey = GoogleServiceAccountKey+  { privateKey :: String+  , clientEmail :: Text+  , projectId :: Text+  , privateKeyId :: Text+  , clientId :: Text+  , authUri :: Text+  , tokenUri :: Text+  , authProviderX509CertUrl :: Text+  , clientX509CertUrl :: Text+  }+  deriving (Generic)++instance FromJSON GoogleServiceAccountKey where+  parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}++-- * Service Account++mkJwt ::+  PrivateKey ->+  -- | Private key+  Text ->+  -- | Issuer+  Maybe Text ->+  -- | impersonate user+  Set.Set Scope ->+  -- | Scope+  Idp Google ->+  IO (Either String Jwt)+mkJwt privateKey iss muser scopes idp = do+  now <- getCurrentTime+  let payload =+        bsToStrict $+          Aeson.encode $+            Aeson.object $+              [ "iss" .= iss+              , "scope" .= T.intercalate " " (map (TL.toStrict . unScope) $ Set.toList scopes)+              , "aud" .= idpTokenEndpoint idp+              , "exp" .= tToSeconds (addUTCTime (secondsToNominalDiffTime 300) now) -- 5 minutes expiration time+              , "iat" .= tToSeconds now+              ]+                ++ maybe [] (\a -> ["sub" .= a]) muser+  first show <$> rsaEncode RS256 privateKey payload+  where+    tToSeconds = formatTime defaultTimeLocale "%s"++-- | Read private RSA Key in PEM format+readPemRsaKey ::+  -- | PEM content+  String ->+  IO (Either String PrivateKey)+readPemRsaKey pemStr = do+  somePair <- readPrivateKey pemStr PwNone+  pure $ case (toKeyPair somePair :: Maybe RSAKeyPair) of+    Just k ->+      Right $+        PrivateKey+          { private_pub =+              PublicKey+                { public_size = rsaSize k+                , public_n = rsaN k+                , public_e = rsaE k+                }+          , private_d = rsaD k+          , private_p = rsaP k+          , private_q = rsaQ k+          , private_dP = fromMaybe 0 (rsaDMP1 k)+          , private_dQ = fromMaybe 0 (rsaDMQ1 k)+          , private_qinv = fromMaybe 0 (rsaIQMP k)+          }+    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+ defaultGoogleIdp :: Idp Google defaultGoogleIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Google),-      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|]+    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Google)+    , 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|]     } +-- requires scope "https://www.googleapis.com/auth/userinfo.profile" to obtain "name".+-- requires scopes "https://www.googleapis.com/auth/userinfo.email" to obtain "email". data GoogleUser = GoogleUser-  { -- | "scope": "https://www.googleapis.com/auth/userinfo.profile"]-    name :: Text,-    id :: Text,-    -- | "scopes": "https://www.googleapis.com/auth/userinfo.email",-    email :: Text+  { name :: Text+  , id :: Text+  , email :: Text   }   deriving (Show, Generic) 
src/Network/OAuth2/Provider/Linkedin.hs view
@@ -6,11 +6,6 @@  module Network.OAuth2.Provider.Linkedin where -{--https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?context=linkedin%2Fcontext&tabs=HTTPS-module Network.OAuth2.Provider.Linkedin where--}- import Data.Aeson import Data.Map.Strict qualified as Map import Data.Set qualified as Set@@ -27,30 +22,30 @@ 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+    { 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|]+    { 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+  { localizedFirstName :: Text+  , localizedLastName :: Text   }   deriving (Show, Generic, Eq) -instance FromJSON LinkedinUser +instance FromJSON LinkedinUser
src/Network/OAuth2/Provider/Okta.hs view
@@ -10,12 +10,20 @@ import Control.Monad.IO.Class import Control.Monad.Trans.Except import Data.Aeson+import Data.Aeson qualified as Aeson+import Data.Bifunctor import Data.Map.Strict qualified as Map import Data.Set qualified as Set import Data.Text.Lazy (Text)+import Data.Time import GHC.Generics+import Jose.Jwa+import Jose.Jwk+import Jose.Jws+import Jose.Jwt import Network.OAuth.OAuth2 import Network.OAuth2.Experiment+import Network.OAuth2.Provider.Utils import Network.OIDC.WellKnown import URI.ByteString.QQ @@ -26,25 +34,25 @@ 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+    { 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     }  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 =+    { 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|]     } @@ -57,18 +65,40 @@   OpenIDConfigurationUris {..} <- fetchWellKnownUris domain   pure     ( defaultOktaIdp-        { idpUserInfoEndpoint = userinfoUri,-          idpAuthorizeEndpoint = authorizationUri,-          idpTokenEndpoint = tokenUri+        { idpUserInfoEndpoint = userinfoUri+        , idpAuthorizeEndpoint = authorizationUri+        , idpTokenEndpoint = tokenUri         }     ) --- | https://developer.okta.com/docs/reference/api/oidc/#request-parameters+mkOktaClientCredentialAppJwt ::+  Jwk ->+  ClientId ->+  Idp Okta ->+  IO (Either String Jwt)+mkOktaClientCredentialAppJwt jwk cid idp = do+  now <- getCurrentTime+  let cidStr = unClientId cid+  let payload =+        bsToStrict $+          Aeson.encode $+            Aeson.object+              [ "iss" .= cidStr+              , "sub" .= cidStr+              , "aud" .= idpTokenEndpoint idp+              , "exp" .= tToSeconds (addUTCTime (secondsToNominalDiffTime 300) now) -- 5 minutes expiration time+              , "iat" .= tToSeconds now+              ]+  first 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+  { name :: Text+  , preferredUsername :: Text   }   deriving (Show, Generic) 
src/Network/OAuth2/Provider/Slack.hs view
@@ -22,15 +22,15 @@ 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+    { 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     }  -- https://api.slack.com/authentication/sign-in-with-slack@@ -38,15 +38,15 @@ defaultSlackIdp :: Idp Slack defaultSlackIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Slack),-      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|]+    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Slack)+    , 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|]     }  data SlackUser = SlackUser-  { name :: Text,-    email :: Text+  { name :: Text+  , email :: Text   }   deriving (Show, Generic) 
src/Network/OAuth2/Provider/StackExchange.hs view
@@ -4,9 +4,6 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} -{--  NOTES: stackexchange API spec and its document just sucks!--} module Network.OAuth2.Provider.StackExchange where  import Data.Aeson@@ -32,45 +29,45 @@ 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+    { idpAppClientId = ""+    , idpAppClientSecret = ""+    , idpAppScope = Set.empty+    , idpAppAuthorizeState = "CHANGE_ME"+    , idpAppAuthorizeExtraParams = Map.empty+    , idpAppRedirectUri = [uri|http://localhost|]+    , idpAppName = "default-stackexchange-App"+    , idpAppTokenRequestAuthenticationMethod = ClientSecretPost+    , idp = defaultStackexchangeIdp     }  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.+    { idpFetchUserInfo = authGetJSONWithAuthMethod @_ @(IdpUserInfo StackExchange) AuthInRequestQuery+    , -- 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,-      idpAuthorizeEndpoint = [uri|https://stackexchange.com/oauth|],-      idpTokenEndpoint = [uri|https://stackexchange.com/oauth/access_token|]+          stackexchangeAppKey+    , idpAuthorizeEndpoint = [uri|https://stackexchange.com/oauth|]+    , idpTokenEndpoint = [uri|https://stackexchange.com/oauth/access_token|]     }  data StackExchangeResp = StackExchangeResp-  { hasMore :: Bool,-    quotaMax :: Integer,-    quotaRemaining :: Integer,-    items :: [StackExchangeUser]+  { hasMore :: Bool+  , quotaMax :: Integer+  , quotaRemaining :: Integer+  , items :: [StackExchangeUser]   }   deriving (Show, Generic)  data StackExchangeUser = StackExchangeUser-  { userId :: Integer,-    displayName :: Text,-    profileImage :: Text+  { userId :: Integer+  , displayName :: Text+  , profileImage :: Text   }   deriving (Show, Generic) 
+ src/Network/OAuth2/Provider/Twitter.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}++-- | https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code+module Network.OAuth2.Provider.Twitter where++import Data.Aeson+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.OAuth.OAuth2+import Network.OAuth2.Experiment+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+    }++defaultTwitterIdp :: Idp Twitter+defaultTwitterIdp =+  Idp+    { idpFetchUserInfo = authGetJSON @(IdpUserInfo Twitter)+    , 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|]+    }++data TwitterUser = TwitterUser+  { name :: Text+  , id :: Text+  , username :: Text+  }+  deriving (Show, Generic)++newtype TwitterUserResp = TwitterUserResp {twitterUserRespData :: TwitterUser}+  deriving (Show, Generic)++instance FromJSON TwitterUserResp where+  -- 15 = length "twitterUserResp"+  parseJSON = genericParseJSON (defaultOptions {fieldLabelModifier = map toLower . drop 15})++instance FromJSON TwitterUser
+ src/Network/OAuth2/Provider/Utils.hs view
@@ -0,0 +1,20 @@+{-# 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
@@ -18,38 +18,38 @@ 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+    { idpAppName = "default-weibo-App"+    , idpAppClientId = ""+    , idpAppClientSecret = ""+    , idpAppScope = Set.empty+    , idpAppAuthorizeState = "CHANGE_ME"+    , idpAppAuthorizeExtraParams = Map.empty+    , idpAppRedirectUri = [uri|http://localhost|]+    , idpAppTokenRequestAuthenticationMethod = ClientSecretBasic+    , idp = defaultWeiboIdp     }  defaultWeiboIdp :: Idp Weibo defaultWeiboIdp =   Idp-    { idpFetchUserInfo = authGetJSONWithAuthMethod @_ @(IdpUserInfo Weibo) AuthInRequestQuery,-      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|]+    { idpFetchUserInfo = authGetJSONWithAuthMethod @_ @(IdpUserInfo Weibo) AuthInRequestQuery+    , 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|]     } --- | UserInfor API: http://open.weibo.com/wiki/2/users/show+-- | http://open.weibo.com/wiki/2/users/show data WeiboUser = WeiboUser-  { id :: Integer,-    name :: Text,-    screenName :: Text+  { id :: Integer+  , name :: Text+  , screenName :: Text   }   deriving (Show, Generic)  newtype WeiboUID = WeiboUID {uid :: Integer}   deriving (Show, Generic) -instance FromJSON WeiboUID +instance FromJSON WeiboUID  instance FromJSON WeiboUser where   parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}
src/Network/OAuth2/Provider/ZOHO.hs view
@@ -15,9 +15,6 @@ import Network.OAuth2.Experiment import URI.ByteString.QQ --- `oauth/user/info` url does not work and find answer from--- https://help.zoho.com/portal/community/topic/oauth2-api-better-document-oauth-user-info- data ZOHO = ZOHO deriving (Eq, Show)  type instance IdpUserInfo ZOHO = ZOHOUserResp@@ -25,36 +22,38 @@ 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+    { 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     }  defaultZohoIdp :: Idp ZOHO defaultZohoIdp =   Idp-    { idpFetchUserInfo = authGetJSON @(IdpUserInfo ZOHO),-      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|]+    { idpFetchUserInfo = authGetJSON @(IdpUserInfo ZOHO)+    , 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|]     } +-- `oauth/user/info` url does not work and find answer from+-- https://help.zoho.com/portal/community/topic/oauth2-api-better-document-oauth-user-info data ZOHOUser = ZOHOUser-  { email :: Text,-    fullName :: Text+  { email :: Text+  , fullName :: Text   }   deriving (Show, Generic)  newtype ZOHOUserResp = ZOHOUserResp {users :: [ZOHOUser]}   deriving (Show, Generic) -instance FromJSON ZOHOUserResp +instance FromJSON ZOHOUserResp  instance FromJSON ZOHOUser where   parseJSON = genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}
src/Network/OIDC/WellKnown.hs view
@@ -18,19 +18,19 @@ -- | Slim OpenID Configuration -- TODO: could add more fields to be complete. data OpenIDConfiguration = OpenIDConfiguration-  { issuer :: Text,-    authorizationEndpoint :: Text,-    tokenEndpoint :: Text,-    userinfoEndpoint :: Text,-    jwksUri :: Text+  { issuer :: Text+  , authorizationEndpoint :: Text+  , tokenEndpoint :: Text+  , userinfoEndpoint :: Text+  , jwksUri :: Text   }   deriving (Generic)  data OpenIDConfigurationUris = OpenIDConfigurationUris-  { authorizationUri :: URI,-    tokenUri :: URI,-    userinfoUri :: URI,-    jwksUri :: URI+  { authorizationUri :: URI+  , tokenUri :: URI+  , userinfoUri :: URI+  , jwksUri :: URI   }   deriving (Generic) @@ -61,10 +61,10 @@     jwks <- ExceptT $ pure (parseURI strictURIParserOptions $ BSL.toStrict $ TL.encodeUtf8 jwksUri)     pure       OpenIDConfigurationUris-        { authorizationUri = ae,-          tokenUri = te,-          userinfoUri = ue,-          jwksUri = jwks+        { authorizationUri = ae+        , tokenUri = te+        , userinfoUri = ue+        , jwksUri = jwks         }  handleWellKnownResponse :: Response ByteString -> Either Text OpenIDConfiguration