packages feed

wai-middleware-auth 0.1.2.1 → 0.2.0.0

raw patch · 8 files changed

+170/−55 lines, 8 filesdep +hedgehogdep +timedep ~basedep ~hoauth2PVP ok

version bump matches the API change (PVP)

Dependencies added: hedgehog, time

Dependency ranges changed: base, hoauth2

API changes (from Hackage documentation)

- Network.Wai.Middleware.Auth.OAuth2: instance GHC.Exception.Exception Network.Wai.Middleware.Auth.OAuth2.URIParseException
- Network.Wai.Middleware.Auth.Provider: [authUserIdentity] :: AuthUser -> !UserIdentity
+ Network.Wai.Auth.Internal: OAuth2TokenBinary :: OAuth2Token -> OAuth2TokenBinary
+ Network.Wai.Auth.Internal: [unOAuth2TokenBinary] :: OAuth2TokenBinary -> OAuth2Token
+ Network.Wai.Auth.Internal: instance Data.Binary.Class.Binary Network.Wai.Auth.Internal.OAuth2TokenBinary
+ Network.Wai.Auth.Internal: instance GHC.Show.Show Network.Wai.Auth.Internal.OAuth2TokenBinary
+ Network.Wai.Auth.Internal: newtype OAuth2TokenBinary
+ Network.Wai.Middleware.Auth: getDeleteSessionHeader :: AuthSettings -> Header
+ Network.Wai.Middleware.Auth.OAuth2: getAccessToken :: Request -> Maybe OAuth2Token
+ Network.Wai.Middleware.Auth.OAuth2: instance GHC.Exception.Type.Exception Network.Wai.Middleware.Auth.OAuth2.URIParseException
+ Network.Wai.Middleware.Auth.Provider: [authLoginState] :: AuthUser -> !UserIdentity
+ Network.Wai.Middleware.Auth.Provider: authUserIdentity :: AuthUser -> UserIdentity
+ Network.Wai.Middleware.Auth.Provider: type AuthLoginState = ByteString
- Network.Wai.Middleware.Auth.Provider: handleLogin :: AuthProvider ap => ap -> Request -> [Text] -> Render ProviderUrl -> (UserIdentity -> IO Response) -> (Status -> ByteString -> IO Response) -> IO Response
+ Network.Wai.Middleware.Auth.Provider: handleLogin :: AuthProvider ap => ap -> Request -> [Text] -> Render ProviderUrl -> (AuthLoginState -> IO Response) -> (Status -> ByteString -> IO Response) -> IO Response

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@+# 0.2.0.0+========++* Drop compatiblity with hoauth2 versions <= 1.0.0.+* Add a function for getting the oauth2 token from an authenticated request.+* Modify encoding of oauth2 session cookies. As a consequence existing cookies will be invalid.+ 0.1.2.1 ======= 
src/Network/Wai/Auth/ClientSession.hs view
@@ -3,6 +3,7 @@ module Network.Wai.Auth.ClientSession   ( loadCookieValue   , saveCookieValue+  , deleteCookieValue   , Key   , getDefaultKey   ) where@@ -15,6 +16,8 @@ import qualified Data.ByteString.Lazy       as L import           Data.Int                   (Int64) import           Data.Maybe                 (listToMaybe)+import           Data.Time.Clock            (UTCTime(UTCTime))+import           Data.Time.Calendar         (fromGregorian) import           Foreign.C.Types            (CTime (..)) import           GHC.Generics               (Generic) import           Network.HTTP.Types         (Header)@@ -23,9 +26,9 @@ import           Web.ClientSession          (Key, decrypt, encryptIO,                                              getDefaultKey) import           Web.Cookie                 (def, parseCookies, renderSetCookie,-                                             setCookieHttpOnly, setCookieMaxAge,-                                             setCookieName, setCookiePath,-                                             setCookieValue)+                                             setCookieExpires, setCookieHttpOnly,+                                             setCookieMaxAge, setCookieName,+                                             setCookiePath, setCookieValue)  data Wrapper value = Wrapper   { contained :: value@@ -78,4 +81,19 @@         , setCookiePath = Just "/"         , setCookieHttpOnly = True         , setCookieMaxAge = Just $ fromIntegral age+        })++deleteCookieValue+  :: S.ByteString -- ^ cookie name+  -> Header+deleteCookieValue name =+  ( "Set-Cookie"+  , toByteString $+    renderSetCookie+      def+        { setCookieName = name+        , setCookieValue = ""+        , setCookiePath = Just "/"+        , setCookieHttpOnly = True+        , setCookieExpires = Just $ UTCTime (fromGregorian 1970 01 01) 0         })
+ src/Network/Wai/Auth/Internal.hs view
@@ -0,0 +1,26 @@+module Network.Wai.Auth.Internal+  ( OAuth2TokenBinary(..)+  ) where++import           Data.Binary                          (Binary(get, put))+import qualified Network.OAuth.OAuth2                 as OA2++newtype OAuth2TokenBinary =+  OAuth2TokenBinary { unOAuth2TokenBinary :: OA2.OAuth2Token }+  deriving (Show)++instance Binary OAuth2TokenBinary where+  put (OAuth2TokenBinary token) = do+    put $ OA2.atoken $ OA2.accessToken token+    put $ OA2.rtoken <$> OA2.refreshToken token+    put $ OA2.expiresIn token+    put $ OA2.tokenType token+    put $ OA2.idtoken <$> OA2.idToken token+  get = do+    accessToken <- OA2.AccessToken <$> get+    refreshToken <- fmap OA2.RefreshToken <$> get+    expiresIn <- get+    tokenType <- get+    idToken <- fmap OA2.IdToken <$> get+    pure $ OAuth2TokenBinary $+      OA2.OAuth2Token accessToken refreshToken expiresIn tokenType idToken
src/Network/Wai/Middleware/Auth.hs view
@@ -21,6 +21,7 @@     , smartAppRoot     , waiMiddlewareAuthVersion     , getAuthUser+    , getDeleteSessionHeader     ) where  import           Blaze.ByteString.Builder             (fromByteString)@@ -37,8 +38,9 @@ import           Data.Version                         (Version) import           Foreign.C.Types                      (CTime (..)) import           GHC.Generics                         (Generic)-import           Network.HTTP.Types                   (status200, status303,-                                                       status404, status501)+import           Network.HTTP.Types                   (Header, status200,+                                                       status303, status404,+                                                       status501) import           Network.Wai                          (Middleware, Request,                                                        pathInfo, rawPathInfo,                                                        rawQueryString,@@ -221,13 +223,13 @@                           onFailure                             status501                             "Empty user identity is not allowed"-                        onSuccess userIdentity = do+                        onSuccess authLoginState = do                           CTime now <- epochTime                           cookie <-                             saveAuthState $                             AuthLoggedIn $                             AuthUser-                            { authUserIdentity = userIdentity+                            { authLoginState = authLoginState                             , authProviderName =                                 encodeUtf8 $ getProviderName provider                             , authLoginTime = fromIntegral now@@ -252,6 +254,7 @@                         providerUrlRenderer                         onSuccess                         onFailure+                ["health"] -> respond $ responseLBS status200 [] "OK"                 _ -> respond $ responseLBS status404 [] "Unknown URL"           -- Workaround for Chrome asking for favicon.ico, causing a wrong           -- redirect url to be stored in a cookie.@@ -296,3 +299,8 @@ waiMiddlewareAuthVersion :: Version waiMiddlewareAuthVersion = Paths.version +-- | Get a response header to delete the users current session.+--+-- @since 0.2.0+getDeleteSessionHeader :: AuthSettings -> Header+getDeleteSessionHeader = deleteCookieValue . asStateKey
src/Network/Wai/Middleware/Auth/OAuth2.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP               #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE TemplateHaskell   #-}@@ -8,8 +7,10 @@   , oAuth2Parser   , URIParseException(..)   , parseAbsoluteURI+  , getAccessToken   ) where +import           Data.Binary                          (encode, decode) import           Control.Monad.Catch import           Data.Aeson.TH                        (defaultOptions,                                                        deriveJSON,@@ -20,23 +21,21 @@ import           Data.Monoid                          ((<>)) import           Data.Proxy                           (Proxy (..)) import qualified Data.Text                            as T-import           Data.Text.Encoding                   (encodeUtf8)+import           Data.Text.Encoding                   (encodeUtf8,+                                                       decodeUtf8With)+import           Data.Text.Encoding.Error             (lenientDecode) import           Network.HTTP.Client.TLS              (getGlobalManager) import           Network.HTTP.Types                   (status303, status403,                                                        status404, status501) import qualified Network.OAuth.OAuth2                 as OA2-import           Network.Wai                          (queryString, responseLBS)+import           Network.Wai                          (Request, queryString,+                                                       responseLBS)+import           Network.Wai.Auth.Internal            (OAuth2TokenBinary(..)) import           Network.Wai.Auth.Tools               (toLowerUnderscore)+import qualified Network.Wai.Middleware.Auth          as MA import           Network.Wai.Middleware.Auth.Provider import qualified URI.ByteString                       as U--#if MIN_VERSION_hoauth2(1,0,0)-import           Data.Text.Encoding                   (decodeUtf8With)-import           Data.Text.Encoding.Error             (lenientDecode) import           URI.ByteString                       (URI)-#else-type URI = OA2.URI-#endif  -- | General OAuth2 authentication `Provider`. data OAuth2 = OAuth2@@ -66,9 +65,6 @@     Left err  -> throwM $ URIParseException err     Right url -> return url --#if MIN_VERSION_hoauth2(1,0,0)- parseAbsoluteURI' :: MonadThrow m => T.Text -> m U.URI parseAbsoluteURI' = parseAbsoluteURI @@ -88,34 +84,10 @@ getRedirectURI :: U.URIRef a -> S.ByteString getRedirectURI = U.serializeURIRef' -getAccessToken :: OA2.OAuth2Token -> S.ByteString-getAccessToken = encodeUtf8 . OA2.atoken . OA2.accessToken+encodeAccessToken :: OA2.OAuth2Token -> S.ByteString+encodeAccessToken = SL.toStrict . encode . OAuth2TokenBinary -#else -parseAbsoluteURI' :: MonadThrow m => T.Text -> m URI-parseAbsoluteURI' urlTxt = U.serializeURIRef' <$> parseAbsoluteURI urlTxt--getExchangeToken :: S.ByteString -> S.ByteString-getExchangeToken = id--appendQueryParams :: URI -> [(S.ByteString, S.ByteString)] -> URI-appendQueryParams uri params = OA2.appendQueryParam uri params--getClientId :: T.Text -> S.ByteString-getClientId = encodeUtf8--getClientSecret :: T.Text -> S.ByteString-getClientSecret = encodeUtf8--getRedirectURI :: URI -> S.ByteString-getRedirectURI = id--getAccessToken :: OA2.AccessToken -> S.ByteString-getAccessToken = OA2.accessToken--#endif- -- | Aeson parser for `OAuth2` provider. -- -- @since 0.1.0@@ -159,7 +131,7 @@                eRes <- OA2.fetchAccessToken man oauth2 $ getExchangeToken code                case eRes of                  Left err    -> onFailure status501 $ S8.pack $ show err-                 Right token -> onSuccess $ getAccessToken token+                 Right token -> onSuccess $ encodeAccessToken token              _ ->                case lookup "error" params of                  (Just (Just "access_denied")) ->@@ -180,3 +152,14 @@   $(deriveJSON defaultOptions { fieldLabelModifier = toLowerUnderscore . drop 3} ''OAuth2)++-- | Get the @AccessToken@ for the current user.+--+-- If called on a @Request@ behind the middleware, should always return a+-- @Just@ value.+--+-- @since 0.2.0.0+getAccessToken :: Request -> Maybe OA2.OAuth2Token+getAccessToken req = do+  user <- MA.getAuthUser req+  unOAuth2TokenBinary <$> decode (SL.fromStrict (authLoginState user))
src/Network/Wai/Middleware/Auth/Provider.hs view
@@ -17,7 +17,9 @@   , parseProviders   -- * User   , AuthUser(..)+  , AuthLoginState   , UserIdentity+  , authUserIdentity   -- * Template   , mkRouteRender   , providersTemplate@@ -71,7 +73,7 @@ --         \@?{(ProviderUrl ["login", "complete"], [("user", "Hamlet")])} --         @ -----     * @(`UserIdentity` -> `IO` `Response`)@ - Action to call on a successfull login.+--     * @(`AuthLoginState` -> `IO` `Response`)@ - Action to call on a successfull login. --     * @(`Status` -> `S.ByteString` -> `IO` `Response`)@ - Should be called in case of --     a failure with login process by supplying a --     status and a short error message.@@ -100,7 +102,7 @@     -> Request     -> [T.Text]     -> Render ProviderUrl-    -> (UserIdentity -> IO Response)+    -> (AuthLoginState -> IO Response)     -> (Status -> S.ByteString -> IO Response)     -> IO Response @@ -137,12 +139,19 @@   } deriving (Show)  --- | An arbitrary user identifer, eg. a username or an email address.+-- | An arbitrary state that comes with logged in user, eg. a username, token or an email address.+type AuthLoginState = S.ByteString+ type UserIdentity = S.ByteString+{-# DEPRECATED UserIdentity "In favor of `AuthLoginState`" #-} +authUserIdentity :: AuthUser -> UserIdentity+authUserIdentity = authLoginState+{-# DEPRECATED authUserIdentity "In favor of `authLoginState`" #-}+ -- | Representation of a user for a particular `Provider`. data AuthUser = AuthUser-  { authUserIdentity :: !UserIdentity+  { authLoginState :: !UserIdentity   , authProviderName :: !S.ByteString   , authLoginTime    :: !Int64   } deriving (Generic, Show)
+ test/Main.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import           Data.Binary                   (encode, decode)+import qualified Data.Text                     as T+import           Hedgehog+import           Hedgehog.Gen                  as Gen+import           Hedgehog.Range                as Range+import           Network.Wai.Auth.Internal     (OAuth2TokenBinary(..))+import qualified Network.OAuth.OAuth2.Internal as OA2++main :: IO Bool+main =+  checkParallel $ Group "Main" [+    ("oAuth2TokenBinaryDuality", oAuth2TokenBinaryDuality)+  ]++oAuth2TokenBinaryDuality :: Property+oAuth2TokenBinaryDuality = property $ do+  token <- forAll oauth2TokenBinary+  tripping token encode (Just . decode)++oauth2TokenBinary :: Gen OAuth2TokenBinary+oauth2TokenBinary = do+  accessToken <- OA2.AccessToken <$> anyText+  refreshToken <- Gen.maybe $ OA2.RefreshToken <$> anyText+  expiresIn <- Gen.maybe $ Gen.int (Range.linear 0 1000)+  tokenType <- Gen.maybe $ anyText+  idToken <- Gen.maybe $ OA2.IdToken <$> anyText+  pure $ OAuth2TokenBinary $+    OA2.OAuth2Token accessToken refreshToken expiresIn tokenType idToken++anyText :: Gen T.Text+anyText = Gen.text (Range.linear 0 100) Gen.unicodeAll++-- The `OAuth2Token` type from the `hoauth2` library does not have a `Eq`+-- instance, and it's constituent parts don't have a `Generic` instance. Hence+-- this orphan instance here.+instance Eq OAuth2TokenBinary where+  (OAuth2TokenBinary t1) == (OAuth2TokenBinary t2) =+    all id+      [ OA2.atoken (OA2.accessToken t1) == OA2.atoken (OA2.accessToken t2)+      , (OA2.rtoken <$> OA2.refreshToken t1) == (OA2.rtoken <$> OA2.refreshToken t2)+      , OA2.expiresIn t1 == OA2.expiresIn t2+      , OA2.tokenType t1 == OA2.tokenType t2+      , (OA2.idtoken <$> OA2.idToken t1) == (OA2.idtoken <$> OA2.idToken t2)+      ]
wai-middleware-auth.cabal view
@@ -1,7 +1,8 @@+cabal-version:       1.18 name:                wai-middleware-auth-version:             0.1.2.1+version:             0.2.0.0 synopsis:            Authentication middleware that secures WAI application-description:         See README+description:         Please see the README and Haddocks at <https://www.stackage.org/package/wai-middleware-auth> license:             MIT license-file:        LICENSE author:              Alexey Kuleshevich@@ -9,7 +10,6 @@ category:            Web build-type:          Simple extra-doc-files:     README.md CHANGELOG.md-cabal-version:       >=1.10  library   exposed-modules:     Network.Wai.Middleware.Auth@@ -18,6 +18,7 @@                        Network.Wai.Middleware.Auth.OAuth2.Google                        Network.Wai.Middleware.Auth.Provider                        Network.Wai.Auth.Executable+                       Network.Wai.Auth.Internal   other-modules:       Paths_wai_middleware_auth                        Network.Wai.Auth.AppRoot                        Network.Wai.Auth.Config@@ -35,7 +36,7 @@                      , clientsession                      , cookie                      , exceptions-                     , hoauth2              >= 0.5.0+                     , hoauth2              >= 1.0                      , http-client                      , http-client-tls                      , http-conduit@@ -45,6 +46,7 @@                      , safe-exceptions                      , shakespeare                      , text+                     , time                      , unix-compat                      , unordered-containers                      , uri-bytestring@@ -68,6 +70,19 @@                      , optparse-simple                      , wai-middleware-auth                      , warp+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N++test-suite spec+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  main-is:             Main.hs+  hs-source-dirs:      test+  build-depends:       base+                     , binary+                     , hedgehog+                     , hoauth2+                     , text+                     , wai-middleware-auth   ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N  source-repository head