mirror of
https://github.com/freckle/yesod-auth-oauth2.git
synced 2026-01-12 04:08:30 +01:00
Support hoauth2-2.0
The new major version improves the naming of the fields of the OAuth2
record type. This type is central to this library and we leak it freely.
Users who make their own plugins are expected to construct values of
this type to pass into our functions, this makes the new version
disruptive to our code and our users'.
We have two options:
1. Update and release our own new major version
The major downside is that the current LTS resolver will then not
update beyond our currently-released version. We have no immediate
plans for new features in this library, but if we have bugs reported
to be fixed we would either have to manage a complex backporting or
ask our Stack users to wait for the next major LTS, which has
historically been many months.
Users who wish to use our new version would need to also bring in
hoauth2, and who knows what else.
2. Release a fully-compatible update
As mentioned, we leak OAuth2(..) through this library's interface. In
order to be truly backwards-compatible, we would have to use CCP to
define an "old style" OAuth2 and use that throughout, such that
in-the-wild OAuth2 values continue to work as-is.
This would not be a good long-term solution as it introduces a fair
amount of naming confusion and will lead to import conflicts for any
users who also import hoauth2-2.0 modules in the same project.
3. Release a mostly-compatible update
This is the path this commit explores. We can update our own code to
be hoauth2-2.0 compatible and use CPP to define the hoauth2-2.0-like
OAuth2 if we're still on hoauth2-1.x.
This gets us compiling in either case and "forward functional", with
the exception of users who define their own plugins (which is rare).
Because of that use-case, this should technically be a major version
bump for ourselves (though I'm open to the argument we could treat
the local-provider use-case differently), however it is still better
than Option 1 in a few ways:
- We still compile with hoauth2-1.x, so can be brought in easily as
an isolated extra-dep
- If there is a reported bug that we decide to only fix in the newer
versions, the path for the user is better: they can pull us as an
extra-dep and likely need no changes. Even if they're doing a
custom plugin, the required changes are minor
This commit is contained in:
parent
8917325409
commit
dd66460fcf
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -13,6 +13,7 @@ jobs:
|
||||
matrix:
|
||||
stack-yaml:
|
||||
- stack.yaml
|
||||
- stack-hoauth2-2.0.yaml
|
||||
- stack-lts-17.4.yaml
|
||||
- stack-lts-16.10.yaml
|
||||
- stack-lts-13.2.yaml
|
||||
|
||||
10
README.md
10
README.md
@ -91,11 +91,11 @@ oauth2MySite clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint = "https://mysite.com/oauth/authorize"
|
||||
, oauthAccessTokenEndpoint = "https://mysite.com/oauth/token"
|
||||
, oauthCallback = Nothing
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint = "https://mysite.com/oauth/authorize"
|
||||
, oauth2TokenEndpoint = "https://mysite.com/oauth/token"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
61
src/Network/OAuth/OAuth2/Compat.hs
Normal file
61
src/Network/OAuth/OAuth2/Compat.hs
Normal file
@ -0,0 +1,61 @@
|
||||
{-# LANGUAGE CPP #-}
|
||||
|
||||
module Network.OAuth.OAuth2.Compat
|
||||
( OAuth2(..)
|
||||
, authorizationUrl
|
||||
, fetchAccessToken
|
||||
, fetchAccessToken2
|
||||
, module Network.OAuth.OAuth2
|
||||
) where
|
||||
|
||||
import Network.HTTP.Conduit (Manager)
|
||||
import Network.OAuth.OAuth2 hiding
|
||||
(OAuth2(..), authorizationUrl, fetchAccessToken, fetchAccessToken2)
|
||||
import qualified Network.OAuth.OAuth2 as OAuth2
|
||||
import Network.OAuth.OAuth2.TokenRequest (Errors)
|
||||
import URI.ByteString
|
||||
|
||||
#if MIN_VERSION_hoauth2(2,0,0)
|
||||
import Network.OAuth.OAuth2 (OAuth2(..))
|
||||
|
||||
getOAuth2 :: OAuth2 -> OAuth2
|
||||
getOAuth2 = id
|
||||
|
||||
#else
|
||||
import Data.Text (Text)
|
||||
|
||||
data OAuth2 = OAuth2
|
||||
{ oauth2ClientId :: Text
|
||||
, oauth2ClientSecret :: Maybe Text
|
||||
, oauth2AuthorizeEndpoint :: URIRef Absolute
|
||||
, oauth2TokenEndpoint :: URIRef Absolute
|
||||
, oauth2RedirectUri :: Maybe (URIRef Absolute)
|
||||
}
|
||||
|
||||
getOAuth2 :: OAuth2 -> OAuth2.OAuth2
|
||||
getOAuth2 o = OAuth2.OAuth2
|
||||
{ OAuth2.oauthClientId = oauth2ClientId o
|
||||
, OAuth2.oauthClientSecret = oauth2ClientSecret o
|
||||
, OAuth2.oauthOAuthorizeEndpoint = oauth2AuthorizeEndpoint o
|
||||
, OAuth2.oauthAccessTokenEndpoint = oauth2TokenEndpoint o
|
||||
, OAuth2.oauthCallback = oauth2RedirectUri o
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
authorizationUrl :: OAuth2 -> URI
|
||||
authorizationUrl = OAuth2.authorizationUrl . getOAuth2
|
||||
|
||||
fetchAccessToken
|
||||
:: Manager
|
||||
-> OAuth2
|
||||
-> ExchangeToken
|
||||
-> IO (OAuth2Result Errors OAuth2Token)
|
||||
fetchAccessToken m = OAuth2.fetchAccessToken m . getOAuth2
|
||||
|
||||
fetchAccessToken2
|
||||
:: Manager
|
||||
-> OAuth2
|
||||
-> ExchangeToken
|
||||
-> IO (OAuth2Result Errors OAuth2Token)
|
||||
fetchAccessToken2 m = OAuth2.fetchAccessToken2 m . getOAuth2
|
||||
@ -26,8 +26,7 @@ module Yesod.Auth.OAuth2
|
||||
, getRefreshToken
|
||||
, getUserResponse
|
||||
, getUserResponseJSON
|
||||
)
|
||||
where
|
||||
) where
|
||||
|
||||
import Control.Error.Util (note)
|
||||
import Control.Monad ((<=<))
|
||||
@ -36,7 +35,7 @@ import Data.ByteString.Lazy (ByteString, fromStrict)
|
||||
import Data.Text (Text)
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Network.HTTP.Conduit (Manager)
|
||||
import Network.OAuth.OAuth2
|
||||
import Network.OAuth.OAuth2.Compat
|
||||
import Yesod.Auth
|
||||
import Yesod.Auth.OAuth2.Dispatch
|
||||
import Yesod.Core.Widget
|
||||
|
||||
@ -45,14 +45,14 @@ oauth2AzureADScoped scopes clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://login.windows.net/common/oauth2/authorize"
|
||||
`withQuery` [ scopeParam "," scopes
|
||||
, ("resource", "https://graph.microsoft.com")
|
||||
]
|
||||
, oauthAccessTokenEndpoint =
|
||||
, oauth2TokenEndpoint =
|
||||
"https://login.windows.net/common/oauth2/token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -51,11 +51,11 @@ oauth2BattleNet widget region clientId clientSecret =
|
||||
where
|
||||
host = wwwHost $ T.toLower region
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint = fromRelative "https" host "/oauth/authorize"
|
||||
, oauthAccessTokenEndpoint = fromRelative "https" host "/oauth/token"
|
||||
, oauthCallback = Nothing
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint = fromRelative "https" host "/oauth/authorize"
|
||||
, oauth2TokenEndpoint = fromRelative "https" host "/oauth/token"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -52,12 +52,12 @@ oauth2BitbucketScoped scopes clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://bitbucket.com/site/oauth2/authorize"
|
||||
`withQuery` [scopeParam "," scopes]
|
||||
, oauthAccessTokenEndpoint =
|
||||
, oauth2TokenEndpoint =
|
||||
"https://bitbucket.com/site/oauth2/access_token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -40,12 +40,12 @@ oauth2ClassLinkScoped scopes clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://launchpad.classlink.com/oauth2/v2/auth"
|
||||
`withQuery` [scopeParam "," scopes]
|
||||
, oauthAccessTokenEndpoint =
|
||||
, oauth2TokenEndpoint =
|
||||
"https://launchpad.classlink.com/oauth2/v2/token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import Network.HTTP.Conduit (Manager)
|
||||
import Network.OAuth.OAuth2
|
||||
import Network.OAuth.OAuth2.Compat
|
||||
import Network.OAuth.OAuth2.TokenRequest (Errors)
|
||||
import URI.ByteString.Extension
|
||||
import UnliftIO.Exception
|
||||
@ -104,9 +104,9 @@ withCallbackAndState name oauth2 csrf = do
|
||||
uri <- ($ PluginR name ["callback"]) <$> getParentUrlRender
|
||||
callback <- maybe (throwError $ InvalidCallbackUri uri) pure $ fromText uri
|
||||
pure oauth2
|
||||
{ oauthCallback = Just callback
|
||||
, oauthOAuthorizeEndpoint =
|
||||
oauthOAuthorizeEndpoint oauth2
|
||||
{ oauth2RedirectUri = Just callback
|
||||
, oauth2AuthorizeEndpoint =
|
||||
oauth2AuthorizeEndpoint oauth2
|
||||
`withQuery` [("state", encodeUtf8 csrf)]
|
||||
}
|
||||
|
||||
|
||||
@ -72,11 +72,11 @@ oauth2EveScoped scopes widgetType clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://login.eveonline.com/oauth/authorize"
|
||||
`withQuery` [("response_type", "code"), scopeParam " " scopes]
|
||||
, oauthAccessTokenEndpoint = "https://login.eveonline.com/oauth/token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2TokenEndpoint = "https://login.eveonline.com/oauth/token"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -46,12 +46,12 @@ oauth2GitHubScoped scopes clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://github.com/login/oauth/authorize"
|
||||
`withQuery` [scopeParam "," scopes]
|
||||
, oauthAccessTokenEndpoint =
|
||||
, oauth2TokenEndpoint =
|
||||
"https://github.com/login/oauth/access_token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -53,12 +53,12 @@ oauth2GitLabHostScopes host scopes clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
host
|
||||
`withPath` "/oauth/authorize"
|
||||
`withQuery` [scopeParam " " scopes]
|
||||
, oauthAccessTokenEndpoint = host `withPath` "/oauth/token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2TokenEndpoint = host `withPath` "/oauth/token"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -74,12 +74,12 @@ oauth2GoogleScopedWidget widget scopes clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://accounts.google.com/o/oauth2/auth"
|
||||
`withQuery` [scopeParam " " scopes]
|
||||
, oauthAccessTokenEndpoint =
|
||||
, oauth2TokenEndpoint =
|
||||
"https://www.googleapis.com/oauth2/v3/token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -51,9 +51,9 @@ oauth2Nylas clientId clientSecret =
|
||||
$ eitherDecode userResponse
|
||||
where
|
||||
oauth = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://api.nylas.com/oauth/authorize"
|
||||
`withQuery` [ ("response_type", "code")
|
||||
, ( "client_id"
|
||||
@ -64,6 +64,6 @@ oauth2Nylas clientId clientSecret =
|
||||
-- its current state, it doesn't matter because it's only one scope.
|
||||
, scopeParam "," defaultScopes
|
||||
]
|
||||
, oauthAccessTokenEndpoint = "https://api.nylas.com/oauth/token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2TokenEndpoint = "https://api.nylas.com/oauth/token"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -54,8 +54,7 @@ module Yesod.Auth.OAuth2.Prelude
|
||||
-- * Temporary, until I finish re-structuring modules
|
||||
, authOAuth2
|
||||
, authOAuth2Widget
|
||||
)
|
||||
where
|
||||
) where
|
||||
|
||||
import Control.Exception.Safe
|
||||
import Data.Aeson
|
||||
@ -65,7 +64,7 @@ import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Text.Encoding
|
||||
import Network.HTTP.Conduit
|
||||
import Network.OAuth.OAuth2
|
||||
import Network.OAuth.OAuth2.Compat
|
||||
import URI.ByteString
|
||||
import URI.ByteString.Extension
|
||||
import Yesod.Auth
|
||||
|
||||
@ -73,10 +73,10 @@ salesforceHelper name profileUri authorizeUri tokenUri scopes clientId clientSec
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
authorizeUri `withQuery` [scopeParam " " scopes]
|
||||
, oauthAccessTokenEndpoint = tokenUri
|
||||
, oauthCallback = Nothing
|
||||
, oauth2TokenEndpoint = tokenUri
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -65,11 +65,11 @@ oauth2SlackScoped scopes clientId clientSecret =
|
||||
$ eitherDecode userResponse
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://slack.com/oauth/authorize"
|
||||
`withQuery` [scopeParam "," $ map scopeText scopes]
|
||||
, oauthAccessTokenEndpoint = "https://slack.com/api/oauth.access"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2TokenEndpoint = "https://slack.com/api/oauth.access"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -34,11 +34,11 @@ oauth2Spotify scopes clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://accounts.spotify.com/authorize"
|
||||
`withQuery` [scopeParam " " scopes]
|
||||
, oauthAccessTokenEndpoint = "https://accounts.spotify.com/api/token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2TokenEndpoint = "https://accounts.spotify.com/api/token"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -41,9 +41,9 @@ oauth2Upcase clientId clientSecret =
|
||||
}
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint = "http://upcase.com/oauth/authorize"
|
||||
, oauthAccessTokenEndpoint = "http://upcase.com/oauth/token"
|
||||
, oauthCallback = Nothing
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint = "http://upcase.com/oauth/authorize"
|
||||
, oauth2TokenEndpoint = "http://upcase.com/oauth/token"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
@ -37,12 +37,12 @@ oauth2WordPressDotCom clientId clientSecret =
|
||||
|
||||
where
|
||||
oauth2 = OAuth2
|
||||
{ oauthClientId = clientId
|
||||
, oauthClientSecret = Just clientSecret
|
||||
, oauthOAuthorizeEndpoint =
|
||||
{ oauth2ClientId = clientId
|
||||
, oauth2ClientSecret = Just clientSecret
|
||||
, oauth2AuthorizeEndpoint =
|
||||
"https://public-api.wordpress.com/oauth2/authorize"
|
||||
`withQuery` [scopeParam "," ["auth"]]
|
||||
, oauthAccessTokenEndpoint =
|
||||
, oauth2TokenEndpoint =
|
||||
"https://public-api.wordpress.com/oauth2/token"
|
||||
, oauthCallback = Nothing
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
|
||||
3
stack-hoauth2-2.0.yaml
Normal file
3
stack-hoauth2-2.0.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
resolver: lts-18.23
|
||||
extra-deps:
|
||||
- hoauth2-2.0.0@sha256:4686d776272d4c57d3c8dbeb9e58b04afe4d2b410382011bd78a3d2bfb08a3fe,5662
|
||||
19
stack-hoauth2-2.0.yaml.lock
Normal file
19
stack-hoauth2-2.0.yaml.lock
Normal file
@ -0,0 +1,19 @@
|
||||
# This file was autogenerated by Stack.
|
||||
# You should not edit this file by hand.
|
||||
# For more information, please see the documentation at:
|
||||
# https://docs.haskellstack.org/en/stable/lock_files
|
||||
|
||||
packages:
|
||||
- completed:
|
||||
hackage: hoauth2-2.0.0@sha256:4686d776272d4c57d3c8dbeb9e58b04afe4d2b410382011bd78a3d2bfb08a3fe,5662
|
||||
pantry-tree:
|
||||
size: 2171
|
||||
sha256: 291b3dd90854ef44f270519ec17e34b6778f8430f6d6517bd67b0128bd549553
|
||||
original:
|
||||
hackage: hoauth2-2.0.0@sha256:4686d776272d4c57d3c8dbeb9e58b04afe4d2b410382011bd78a3d2bfb08a3fe,5662
|
||||
snapshots:
|
||||
- completed:
|
||||
size: 587819
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/23.yaml
|
||||
sha256: 7f69bb29a57495586e7e3ed31ecc59c0d2c959cb23bd52b71ca676f254c9beb1
|
||||
original: lts-18.23
|
||||
@ -1 +1 @@
|
||||
resolver: lts-17.4
|
||||
resolver: lts-18.23
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
packages: []
|
||||
snapshots:
|
||||
- completed:
|
||||
size: 563103
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/4.yaml
|
||||
sha256: f11e2153044f5f71ea7b1c9398f4721f517c9bd37642ed769647b896564021f3
|
||||
original: lts-17.4
|
||||
size: 587819
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/23.yaml
|
||||
sha256: 7f69bb29a57495586e7e3ed31ecc59c0d2c959cb23bd52b71ca676f254c9beb1
|
||||
original: lts-18.23
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
cabal-version: 1.12
|
||||
|
||||
-- This file has been generated from package.yaml by hpack version 0.33.0.
|
||||
-- This file has been generated from package.yaml by hpack version 0.34.4.
|
||||
--
|
||||
-- see: https://github.com/sol/hpack
|
||||
--
|
||||
-- hash: 1c0ae94778ce803bde4726d2be1e60cd5dcb94f8e34b7cefbbae7dafb30d1dea
|
||||
-- hash: 5f7443ec90d4a2884d1d8816ec553f50fe303c51a6d8ee956fc8d62b49333e60
|
||||
|
||||
name: yesod-auth-oauth2
|
||||
version: 0.6.3.4
|
||||
@ -35,6 +35,7 @@ flag example
|
||||
|
||||
library
|
||||
exposed-modules:
|
||||
Network.OAuth.OAuth2.Compat
|
||||
UnliftIO.Except
|
||||
URI.ByteString.Extension
|
||||
Yesod.Auth.OAuth2
|
||||
|
||||
Loading…
Reference in New Issue
Block a user