packages feed

yesod-auth-oauth2 0.0.12 → 0.1.0

raw patch · 7 files changed

+244/−261 lines, 7 filesdep +randomdep −uuiddep ~basedep ~hoauth2PVP ok

version bump matches the API change (PVP)

Dependencies added: random

Dependencies removed: uuid

Dependency ranges changed: base, hoauth2

API changes (from Hackage documentation)

- Yesod.Auth.OAuth2.Google: oauth2Google :: YesodAuth m => Text -> Text -> AuthPlugin m
- Yesod.Auth.OAuth2.Learn: instance FromJSON LearnResponse
- Yesod.Auth.OAuth2.Learn: instance FromJSON LearnUser
- Yesod.Auth.OAuth2.Learn: oauth2Learn :: YesodAuth m => Text -> Text -> AuthPlugin m
+ Yesod.Auth.OAuth2.Upcase: instance FromJSON UpcaseResponse
+ Yesod.Auth.OAuth2.Upcase: instance FromJSON UpcaseUser
+ Yesod.Auth.OAuth2.Upcase: oauth2Upcase :: YesodAuth m => Text -> Text -> AuthPlugin m

Files

Yesod/Auth/OAuth2.hs view
@@ -1,11 +1,13 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} -- | -- -- Generic OAuth2 plugin for Yesod ----- * See Yesod.Auth.OAuth2.Learn for example usage.+-- * See Yesod.Auth.OAuth2.GitHub for example usage. -- module Yesod.Auth.OAuth2     ( authOAuth2@@ -14,23 +16,29 @@     , module Network.OAuth.OAuth2     ) where +#if __GLASGOW_HASKELL__ < 710+import Control.Applicative ((<$>))+#endif+ import Control.Exception.Lifted import Control.Monad.IO.Class import Data.ByteString (ByteString)-import Data.Text (Text)+import Data.Monoid ((<>))+import Data.Text (Text, pack) import Data.Text.Encoding (decodeUtf8With, encodeUtf8) import Data.Text.Encoding.Error (lenientDecode) import Data.Typeable+import Network.HTTP.Conduit (Manager) import Network.OAuth.OAuth2-import Network.HTTP.Conduit(Manager)+import System.Random import Yesod.Auth import Yesod.Core import Yesod.Form -import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Lazy as BL  -- | Provider name and Aeson parse error-data YesodOAuth2Exception = InvalidProfileResponse Text BSL.ByteString+data YesodOAuth2Exception = InvalidProfileResponse Text BL.ByteString     deriving (Show, Typeable)  instance Exception YesodOAuth2Exception@@ -49,35 +57,52 @@            -> AuthPlugin m authOAuth2 name oauth getCreds = AuthPlugin name dispatch login -    where-        url = PluginR name ["callback"]+  where+    url = PluginR name ["callback"] -        withCallback = do-            tm <- getRouteToParent-            render <- lift $ getUrlRender-            return $ oauth { oauthCallback = Just $ encodeUtf8 $ render $ tm url }+    withCallback csrfToken = do+        tm <- getRouteToParent+        render <- lift getUrlRender+        return oauth+            { oauthCallback = Just $ encodeUtf8 $ render $ tm url+            , oauthOAuthorizeEndpoint = oauthOAuthorizeEndpoint oauth+                <> "&state=" <> encodeUtf8 csrfToken+            } -        dispatch "GET" ["forward"] = do-            authUrl <- fmap (bsToText . authorizationUrl) withCallback-            lift $ redirect authUrl+    dispatch "GET" ["forward"] = do+        csrfToken <- liftIO generateToken+        setSession tokenSessionKey csrfToken+        authUrl <- bsToText . authorizationUrl <$> withCallback csrfToken+        lift $ redirect authUrl -        dispatch "GET" ["callback"] = do-            code <- lift $ runInputGet $ ireq textField "code"-            oauth' <- withCallback-            master <- lift getYesod-            result <- liftIO $ fetchAccessToken (authHttpManager master) oauth' (encodeUtf8 code)-            case result of-                Left _ -> permissionDenied "Unable to retreive OAuth2 token"-                Right token -> do-                    creds <- liftIO $ getCreds (authHttpManager master) token-                    lift $ setCredsRedirect creds+    dispatch "GET" ["callback"] = do+        newToken <- lookupGetParam "state"+        oldToken <- lookupSession tokenSessionKey+        deleteSession tokenSessionKey+        case newToken of+            Just csrfToken | newToken == oldToken -> do+                code <- lift $ runInputGet $ ireq textField "code"+                oauth' <- withCallback csrfToken+                master <- lift getYesod+                result <- liftIO $ fetchAccessToken (authHttpManager master) oauth' (encodeUtf8 code)+                case result of+                    Left _ -> permissionDenied "Unable to retreive OAuth2 token"+                    Right token -> do+                        creds <- liftIO $ getCreds (authHttpManager master) token+                        lift $ setCredsRedirect creds+            _ ->+                permissionDenied "Invalid OAuth2 state token" -        dispatch _ _ = notFound+    dispatch _ _ = notFound -        login tm = do-            render <- getUrlRender-            let oaUrl = render $ tm $ oauth2Url name-            [whamlet| <a href=#{oaUrl}>Login via #{name} |]+    generateToken = pack . take 30 . randomRs ('a', 'z') <$> newStdGen++    tokenSessionKey :: Text+    tokenSessionKey = "_yesod_oauth2_" <> name++    login tm = [whamlet|+        <a href=@{tm $ oauth2Url name}>Login via #{name}+        |]  bsToText :: ByteString -> Text bsToText = decodeUtf8With lenientDecode
Yesod/Auth/OAuth2/Github.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} -- | --@@ -13,36 +14,35 @@     , module Yesod.Auth.OAuth2     ) where -import Control.Applicative ((<$>), (<*>))+#if __GLASGOW_HASKELL__ < 710+import Control.Applicative ((<$>), (<*>), pure)+#endif+ import Control.Exception.Lifted import Control.Monad (mzero) import Data.Aeson+import Data.Monoid ((<>)) import Data.Text (Text)-import Data.Monoid (mappend) import Data.Text.Encoding (encodeUtf8, decodeUtf8)+import Network.HTTP.Conduit (Manager) import Yesod.Auth import Yesod.Auth.OAuth2-import Yesod.Core-import Yesod.Form-import Network.HTTP.Conduit(Manager)-import Data.UUID (toString)-import Data.UUID.V4 (nextRandom)-import qualified Data.ByteString as BS+ import qualified Data.Text as T  data GithubUser = GithubUser-    { githubUserId    :: Int-    , githubUserName  :: Maybe Text+    { githubUserId :: Int+    , githubUserName :: Maybe Text     , githubUserLogin :: Text     , githubUserAvatarUrl :: Text     }  instance FromJSON GithubUser where-    parseJSON (Object o) =-        GithubUser <$> o .: "id"-                   <*> o .:? "name"-                   <*> o .: "login"-                   <*> o .: "avatar_url"+    parseJSON (Object o) = GithubUser+        <$> o .: "id"+        <*> o .:? "name"+        <*> o .: "login"+        <*> o .: "avatar_url"      parseJSON _ = mzero @@ -51,8 +51,8 @@     }  instance FromJSON GithubUserEmail where-    parseJSON (Object o) =-        GithubUserEmail <$> o .: "email"+    parseJSON (Object o) = GithubUserEmail+        <$> o .: "email"      parseJSON _ = mzero @@ -67,37 +67,15 @@              -> Text -- ^ Client Secret              -> [Text] -- ^ List of scopes to request              -> AuthPlugin m-oauth2GithubScoped clientId clientSecret scopes = basicPlugin {apDispatch = dispatch}-    where-        oauth = OAuth2-                { oauthClientId            = encodeUtf8 clientId-                , oauthClientSecret        = encodeUtf8 clientSecret-                , oauthOAuthorizeEndpoint  = encodeUtf8 $ "https://github.com/login/oauth/authorize?scope=" `T.append` T.intercalate "," scopes-                , oauthAccessTokenEndpoint = "https://github.com/login/oauth/access_token"-                , oauthCallback            = Nothing-                }--        withState state = authOAuth2 "github"-            (oauth {oauthOAuthorizeEndpoint = oauthOAuthorizeEndpoint oauth `BS.append` "&state=" `BS.append` encodeUtf8 state})-            fetchGithubProfile--        basicPlugin = authOAuth2 "github" oauth fetchGithubProfile--        dispatch "GET" ["forward"] = do-            state <- liftIO $ fmap (T.pack . toString) nextRandom-            setSession "githubState" state-            apDispatch (withState state) "GET" ["forward"]--        dispatch "GET" ["callback"] = do-            state <- lift $ runInputGet $ ireq textField "state"-            savedState <- lookupSession "githubState"-            _ <- apDispatch basicPlugin "GET" ["callback"]-            case savedState of-                Just saved | saved == state -> apDispatch basicPlugin "GET" ["callback"]-                Just saved -> invalidArgs ["state: " `mappend` state `mappend` ", and not: " `mappend` saved]-                _ -> invalidArgs ["state: " `mappend` state]--        dispatch method ps = apDispatch basicPlugin method ps+oauth2GithubScoped clientId clientSecret scopes = authOAuth2 "github" oauth fetchGithubProfile+  where+    oauth = OAuth2+        { oauthClientId = encodeUtf8 clientId+        , oauthClientSecret = encodeUtf8 clientSecret+        , oauthOAuthorizeEndpoint = encodeUtf8 $ "https://github.com/login/oauth/authorize?scope=" <> T.intercalate "," scopes+        , oauthAccessTokenEndpoint = "https://github.com/login/oauth/access_token"+        , oauthCallback = Nothing+        }  fetchGithubProfile :: Manager -> AccessToken -> IO (Creds m) fetchGithubProfile manager token = do@@ -111,14 +89,17 @@         (_, Left err) -> throwIO $ InvalidProfileResponse "github" err  toCreds :: GithubUser -> [GithubUserEmail] -> AccessToken -> Creds m-toCreds user userMail token = Creds "github"-    (T.pack $ show $ githubUserId user)-    cExtra-    where-        cExtra = [ ("email", githubUserEmail $ head userMail)-                 , ("login", githubUserLogin user)-                 , ("avatar_url", githubUserAvatarUrl user)-                 , ("access_token", decodeUtf8 $ accessToken token)-                 ] ++ (maybeName $ githubUserName user)-        maybeName Nothing     = []-        maybeName (Just name) = [("name", name)]+toCreds user userMail token = Creds+    { credsPlugin = "github"+    , credsIdent = T.pack $ show $ githubUserId user+    , credsExtra =+        [ ("email", githubUserEmail $ head userMail)+        , ("login", githubUserLogin user)+        , ("avatar_url", githubUserAvatarUrl user)+        , ("access_token", decodeUtf8 $ accessToken token)+        ] ++ maybeName (githubUserName user)+    }++  where+    maybeName Nothing = []+    maybeName (Just name) = [("name", name)]
− Yesod/Auth/OAuth2/Google.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- |------ OAuth2 plugin for http://google.com------ * Note: this module is unfinished, do not use.----module Yesod.Auth.OAuth2.Google-    ( oauth2Google-    , module Yesod.Auth.OAuth2-    ) where--import Data.Text (Text)-import Data.Text.Encoding (encodeUtf8)-import Yesod.Auth-import Yesod.Auth.OAuth2--oauth2Google :: YesodAuth m-             => Text -- ^ Client ID-             -> Text -- ^ Client Secret-             -> AuthPlugin m-oauth2Google clientId clientSecret = authOAuth2 "google"-    (OAuth2-        { oauthClientId            = encodeUtf8 clientId-        , oauthClientSecret        = encodeUtf8 clientSecret-        , oauthOAuthorizeEndpoint  = "https://accounts.google.com/o/oauth2/auth"-        , oauthAccessTokenEndpoint = "https://accounts.google.com/o/oauth2/token"-        , oauthCallback            = Nothing-        })-    undefined -- TODO
− Yesod/Auth/OAuth2/Learn.hs
@@ -1,78 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- |------ OAuth2 plugin for http://learn.thoughtbot.com------ * Authenticates against learn--- * Uses learn user id as credentials identifier--- * Returns first_name, last_name, and email as extras----module Yesod.Auth.OAuth2.Learn-    ( oauth2Learn-    , module Yesod.Auth.OAuth2-    ) where--import Control.Applicative ((<$>), (<*>))-import Control.Exception.Lifted-import Control.Monad (mzero)-import Data.Aeson-import Data.Text (Text)-import Data.Text.Encoding (encodeUtf8)-import Yesod.Auth-import Yesod.Auth.OAuth2-import Network.HTTP.Conduit(Manager)-import qualified Data.Text as T--data LearnUser = LearnUser-    { learnUserId        :: Int-    , learnUserFirstName :: Text-    , learnUserLastName  :: Text-    , learnUserEmail     :: Text-    }--instance FromJSON LearnUser where-    parseJSON (Object o) =-        LearnUser <$> o .: "id"-                  <*> o .: "first_name"-                  <*> o .: "last_name"-                  <*> o .: "email"--    parseJSON _ = mzero--data LearnResponse = LearnResponse LearnUser--instance FromJSON LearnResponse where-    parseJSON (Object o) =-        LearnResponse <$> o .: "user"--    parseJSON _ = mzero--oauth2Learn :: YesodAuth m-            => Text -- ^ Client ID-            -> Text -- ^ Client Secret-            -> AuthPlugin m-oauth2Learn clientId clientSecret = authOAuth2 "learn"-    (OAuth2-        { oauthClientId            = encodeUtf8 clientId-        , oauthClientSecret        = encodeUtf8 clientSecret-        , oauthOAuthorizeEndpoint  = "http://learn.thoughtbot.com/oauth/authorize"-        , oauthAccessTokenEndpoint = "http://learn.thoughtbot.com/oauth/token"-        , oauthCallback            = Nothing-        })-    fetchLearnProfile--fetchLearnProfile :: Manager -> AccessToken -> IO (Creds m)-fetchLearnProfile manager token = do-    result <- authGetJSON manager token "http://learn.thoughtbot.com/api/v1/me.json"--    case result of-        Right (LearnResponse user) -> return $ toCreds user-        Left err -> throwIO $ InvalidProfileResponse "learn" err--toCreds :: LearnUser -> Creds m-toCreds user = Creds "learn"-    (T.pack $ show $ learnUserId user)-    [ ("first_name", learnUserFirstName user)-    , ("last_name" , learnUserLastName user)-    , ("email"     , learnUserEmail user)-    ]
Yesod/Auth/OAuth2/Spotify.hs view
@@ -1,11 +1,18 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-}-+-- |+--+-- OAuth2 plugin for http://spotify.com+-- module Yesod.Auth.OAuth2.Spotify     ( oauth2Spotify     , module Yesod.Auth.OAuth2     ) where -import Control.Applicative ((<$>), (<*>))+#if __GLASGOW_HASKELL__ < 710+import Control.Applicative ((<$>), (<*>), pure)+#endif+ import Control.Exception.Lifted import Control.Monad (mzero) import Data.Aeson@@ -16,6 +23,7 @@ import Network.HTTP.Conduit(Manager) import Yesod.Auth import Yesod.Auth.OAuth2+ import qualified Data.ByteString as B import qualified Data.Text as T @@ -26,49 +34,50 @@     }  instance FromJSON SpotifyUserImage where-    parseJSON (Object v) = SpotifyUserImage <$>-                           v .: "height" <*>-                           v .: "width" <*>-                           v .: "url"+    parseJSON (Object v) = SpotifyUserImage+        <$> v .: "height"+        <*> v .: "width"+        <*> v .: "url"      parseJSON _ = mzero  data SpotifyUser = SpotifyUser-    { spotifyUserId          :: Text-    , spotifyUserHref        :: Text-    , spotifyUserUri         :: Text+    { spotifyUserId :: Text+    , spotifyUserHref :: Text+    , spotifyUserUri :: Text     , spotifyUserDisplayName :: Maybe Text-    , spotifyUserProduct     :: Maybe Text-    , spotifyUserCountry     :: Maybe Text-    , spotifyUserEmail       :: Maybe Text-    , spotifyUserImages      :: Maybe [SpotifyUserImage]+    , spotifyUserProduct :: Maybe Text+    , spotifyUserCountry :: Maybe Text+    , spotifyUserEmail :: Maybe Text+    , spotifyUserImages :: Maybe [SpotifyUserImage]     }  instance FromJSON SpotifyUser where-    parseJSON (Object v) = SpotifyUser <$>-                           v .: "id" <*>-                           v .: "href" <*>-                           v .: "uri" <*>-                           v .:? "display_name" <*>-                           v .:? "product" <*>-                           v .:? "country" <*>-                           v .:? "email" <*>-                           v .:? "images"+    parseJSON (Object v) = SpotifyUser+        <$> v .: "id"+        <*> v .: "href"+        <*> v .: "uri"+        <*> v .:? "display_name"+        <*> v .:? "product"+        <*> v .:? "country"+        <*> v .:? "email"+        <*> v .:? "images"+     parseJSON _ = mzero  oauth2Spotify :: YesodAuth m-             => Text -- ^ Client ID-             -> Text -- ^ Client Secret-             -> [ByteString] -- ^ Scopes-             -> AuthPlugin m+              => Text -- ^ Client ID+              -> Text -- ^ Client Secret+              -> [ByteString] -- ^ Scopes+              -> AuthPlugin m oauth2Spotify clientId clientSecret scope = authOAuth2 "spotify"-    (OAuth2-        { oauthClientId            = encodeUtf8 clientId-        , oauthClientSecret        = encodeUtf8 clientSecret-        , oauthOAuthorizeEndpoint  = B.append "https://accounts.spotify.com/authorize?scope=" (B.intercalate "%20" scope)+    OAuth2+        { oauthClientId = encodeUtf8 clientId+        , oauthClientSecret = encodeUtf8 clientSecret+        , oauthOAuthorizeEndpoint = B.append "https://accounts.spotify.com/authorize?scope=" (B.intercalate "%20" scope)         , oauthAccessTokenEndpoint = "https://accounts.spotify.com/api/token"-        , oauthCallback            = Nothing-        })+        , oauthCallback = Nothing+        }     fetchSpotifyProfile  fetchSpotifyProfile :: Manager -> AccessToken -> IO (Creds m)@@ -79,9 +88,11 @@         Left err -> throwIO $ InvalidProfileResponse "spotify" err  toCreds :: SpotifyUser -> Creds m-toCreds user = Creds "spotify"-    (spotifyUserId user)-    (mapMaybe getExtra extrasTemplate)+toCreds user = Creds+    { credsPlugin = "spotify"+    , credsIdent = spotifyUserId user+    , credsExtra = mapMaybe getExtra extrasTemplate+    }    where     userImage :: Maybe SpotifyUserImage@@ -90,18 +101,15 @@     userImagePart :: (SpotifyUserImage -> Maybe a) -> Maybe a     userImagePart getter = userImage >>= getter -    extrasTemplate = [ ("href"        , Just $ spotifyUserHref user)-                     , ("uri"         , Just $ spotifyUserUri user)+    extrasTemplate = [ ("href", Just $ spotifyUserHref user)+                     , ("uri", Just $ spotifyUserUri user)                      , ("display_name", spotifyUserDisplayName user)-                     , ("product"     , spotifyUserProduct user)-                     , ("country"     , spotifyUserCountry user)-                     , ("email"       , spotifyUserEmail user)-                     , ("image_url"   , userImage >>=-                                        return . spotifyUserImageUrl)-                     , ("image_height", userImagePart spotifyUserImageHeight >>=-                                        return . T.pack . show)-                     , ("image_width" , userImagePart spotifyUserImageWidth >>=-                                        return . T.pack . show)+                     , ("product", spotifyUserProduct user)+                     , ("country", spotifyUserCountry user)+                     , ("email", spotifyUserEmail user)+                     , ("image_url", spotifyUserImageUrl <$> userImage)+                     , ("image_height", T.pack . show <$> userImagePart spotifyUserImageHeight)+                     , ("image_width", T.pack . show <$> userImagePart spotifyUserImageWidth)                      ]      getExtra :: (Text, Maybe Text) -> Maybe (Text, Text)
+ Yesod/Auth/OAuth2/Upcase.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+-- |+--+-- OAuth2 plugin for http://upcase.com+--+-- * Authenticates against upcase+-- * Uses upcase user id as credentials identifier+-- * Returns first_name, last_name, and email as extras+--+module Yesod.Auth.OAuth2.Upcase+    ( oauth2Upcase+    , module Yesod.Auth.OAuth2+    ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative ((<$>), (<*>), pure)+#endif++import Control.Exception.Lifted+import Control.Monad (mzero)+import Data.Aeson+import Data.Text (Text)+import Data.Text.Encoding (encodeUtf8)+import Yesod.Auth+import Yesod.Auth.OAuth2+import Network.HTTP.Conduit(Manager)+import qualified Data.Text as T++data UpcaseUser = UpcaseUser+    { upcaseUserId :: Int+    , upcaseUserFirstName :: Text+    , upcaseUserLastName :: Text+    , upcaseUserEmail :: Text+    }++instance FromJSON UpcaseUser where+    parseJSON (Object o) = UpcaseUser+        <$> o .: "id"+        <*> o .: "first_name"+        <*> o .: "last_name"+        <*> o .: "email"++    parseJSON _ = mzero++data UpcaseResponse = UpcaseResponse UpcaseUser++instance FromJSON UpcaseResponse where+    parseJSON (Object o) = UpcaseResponse+        <$> o .: "user"++    parseJSON _ = mzero++oauth2Upcase :: YesodAuth m+            => Text -- ^ Client ID+            -> Text -- ^ Client Secret+            -> AuthPlugin m+oauth2Upcase clientId clientSecret = authOAuth2 "upcase"+    OAuth2+        { oauthClientId = encodeUtf8 clientId+        , oauthClientSecret = encodeUtf8 clientSecret+        , oauthOAuthorizeEndpoint = "http://upcase.com/oauth/authorize"+        , oauthAccessTokenEndpoint = "http://upcase.com/oauth/token"+        , oauthCallback = Nothing+        }+    fetchUpcaseProfile++fetchUpcaseProfile :: Manager -> AccessToken -> IO (Creds m)+fetchUpcaseProfile manager token = do+    result <- authGetJSON manager token "http://upcase.com/api/v1/me.json"++    case result of+        Right (UpcaseResponse user) -> return $ toCreds user+        Left err -> throwIO $ InvalidProfileResponse "upcase" err++toCreds :: UpcaseUser -> Creds m+toCreds user = Creds+    { credsPlugin = "upcase"+    , credsIdent = T.pack $ show $ upcaseUserId user+    , credsExtra =+        [ ("first_name", upcaseUserFirstName user)+        , ("last_name" , upcaseUserLastName user)+        , ("email"     , upcaseUserEmail user)+        ]+    }
yesod-auth-oauth2.cabal view
@@ -1,18 +1,16 @@ name:            yesod-auth-oauth2-version:         0.0.12+version:         0.1.0 license:         BSD3 license-file:    LICENSE author:          Tom Streller-maintainer:      Tom Streller-synopsis:        Library to authenticate with OAuth 2.0 for Yesod web applications.-description:     OAuth 2.0 authentication+maintainer:      Pat Brisbin <pat@thoughtbot.com>+synopsis:        OAuth 2.0 authentication plugins+description:     Library to authenticate with OAuth 2.0 for Yesod web applications. category:        Web stability:       Experimental cabal-version:   >= 1.6 build-type:      Simple-homepage:        http://github.com/scan/yesod-auth-oauth2--flag ghc7+homepage:        http://github.com/thoughtbot/yesod-auth-oauth2  flag network-uri    description: Get Network.URI from the network-uri package@@ -24,34 +22,28 @@     else         build-depends: network < 2.6 -    if flag(ghc7)-        build-depends:   base                >= 4.3      && < 5-        cpp-options:     -DGHC7-    else-        build-depends:   base                >= 4         && < 4.3--    build-depends:   bytestring              >= 0.9.1.4+    build-depends:   base                    >= 4.5       && < 5+                   , bytestring              >= 0.9.1.4                    , http-conduit            >= 2.0       && < 3.0                    , http-types              >= 0.8       && < 0.9                    , aeson                   >= 0.6       && < 0.9                    , yesod-core              >= 1.2       && < 1.5                    , authenticate            >= 1.3.2.7   && < 1.4+                   , random                    , yesod-auth              >= 1.3       && < 1.5                    , text                    >= 0.7       && < 2.0                    , yesod-form              >= 1.3       && < 1.5                    , transformers            >= 0.2.2     && < 0.5-                   , hoauth2                 >= 0.4.1     && < 0.5+                   , hoauth2                 >= 0.4.7     && < 0.5                    , lifted-base             >= 0.2       && < 0.4-                   , uuid                    >= 1.3       && < 1.4      exposed-modules: Yesod.Auth.OAuth2-                     Yesod.Auth.OAuth2.Google-                     Yesod.Auth.OAuth2.Learn                      Yesod.Auth.OAuth2.Github                      Yesod.Auth.OAuth2.Spotify+                     Yesod.Auth.OAuth2.Upcase      ghc-options:     -Wall  source-repository head   type:     git-  location: git://github.com/scan/authenticate-oauth2.git+  location: https://github.com/thoughtbot/authenticate-oauth2.git