servant-auth-server 0.4.2.0 → 0.4.3.0
raw patch · 7 files changed
+80/−53 lines, 7 filesdep +memorydep −bytestring-conversiondep −crypto-apidep −http-api-datadep ~QuickCheckdep ~aesondep ~base64-bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: memory
Dependencies removed: bytestring-conversion, crypto-api, http-api-data, markdown-unlit
Dependency ranges changed: QuickCheck, aeson, base64-bytestring, blaze-builder, bytestring, case-insensitive, cookie, data-default-class, entropy, hspec, http-client, http-types, jose, lens, lens-aeson, monad-time, mtl, semigroups, servant, servant-server, tagged, text, time, unordered-containers, wai, warp, wreq
API changes (from Hackage documentation)
+ Servant.Auth.Server.Internal.Cookie: expireTime :: UTCTime
Files
- CHANGELOG.md +8/−0
- README.lhs +0/−1
- servant-auth-server.cabal +32/−36
- src/Servant/Auth/Server/Internal/Cookie.hs +18/−6
- src/Servant/Auth/Server/Internal/JWT.hs +2/−2
- src/Servant/Auth/Server/Internal/Types.hs +4/−3
- test/Servant/Auth/ServerSpec.hs +16/−5
CHANGELOG.md view
@@ -7,6 +7,14 @@ ## [Unreleased] +## [0.4.3.0] - 2019-01-17++## Changed+- #117 Avoid running auth checks unnecessarily [@sopvop]+- #110 Get rid of crypto-api dependency [@domenkozar]+- #130 clearSession: improve cross-browser compatibility [@domenkozar]+- #136 weed out bytestring-conversion [@stephenirl]+ ## [0.4.2.0] - 2018-11-05 ### Added
README.lhs view
@@ -218,7 +218,6 @@ api = Proxy :: Proxy (API '[Cookie]) run 7249 $ serveWithContext api cfg (server defaultCookieSettings jwtCfg) - -- Here is the login handler checkCreds :: CookieSettings -> JWTSettings
servant-auth-server.cabal view
@@ -1,5 +1,5 @@ name: servant-auth-server-version: 0.4.2.0+version: 0.4.3.0 synopsis: servant-server/servant-auth compatibility description: This package provides the required instances for using the @Auth@ combinator in your 'servant' server.@@ -15,7 +15,7 @@ copyright: (c) Julian K. Arni license: BSD3 license-file: LICENSE-tested-with: GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.3, GHC == 8.6.1+tested-with: GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.2 build-type: Simple cabal-version: >= 1.10 extra-source-files:@@ -31,34 +31,32 @@ default-extensions: AutoDeriveTypeable ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators ghc-options: -Wall build-depends:- base >= 4.8 && < 4.13- , aeson >= 0.11 && < 2- , base64-bytestring >= 1 && < 2- , blaze-builder >= 0.4 && < 0.5- , bytestring >= 0.10 && < 0.11- , bytestring-conversion >= 0.3 && < 0.4- , case-insensitive >= 1.2 && < 1.3- , cookie >= 0.4 && < 0.5- , crypto-api >= 0.13 && < 0.14- , data-default-class >= 0.0 && < 0.2- , entropy >= 0.3 && < 0.5- , http-api-data >= 0.3.10 && < 0.4- , http-types >= 0.9 && < 0.13- , jose >= 0.6 && < 0.8- , lens >= 4 && < 5- , monad-time >= 0.2 && < 0.4- , mtl >= 2.2 && < 2.3- , servant >= 0.9.1 && < 0.15+ base >= 4.8 && < 4.13+ , aeson >= 1.3.1.1 && < 1.5+ , base64-bytestring >= 1.0.0.1 && < 1.1+ , blaze-builder >= 0.4.1.0 && < 0.5+ , bytestring >= 0.10.6.0 && < 0.11+ , case-insensitive >= 1.2.0.11 && < 1.3+ , cookie >= 0.4.4 && < 0.5+ , data-default-class >= 0.1.2.0 && < 0.2+ , entropy >= 0.4.1.3 && < 0.5+ , http-types >= 0.12.2 && < 0.13+ , jose >= 0.7.0.0 && < 0.9+ , lens >= 4.16.1 && < 4.18+ , memory >= 0.14.16 && < 0.15+ , monad-time >= 0.3.1.0 && < 0.4+ , mtl >= 2.2.2 && < 2.3+ , servant >= 0.13 && < 0.16 , servant-auth == 0.3.*- , servant-server >= 0.9.1 && < 0.15- , tagged >= 0.7.3 && < 0.9- , text >= 1 && < 2- , time >= 1.5 && < 1.9- , unordered-containers >= 0.2 && < 0.3- , wai >= 3.2 && < 3.3+ , servant-server >= 0.13 && < 0.16+ , tagged >= 0.8.4 && < 0.9+ , text >= 1.2.3.0 && < 1.3+ , time >= 1.5.0.1 && < 1.9+ , unordered-containers >= 0.2.9.0 && < 0.3+ , wai >= 3.2.1.2 && < 3.3 if !impl(ghc >= 8.0) build-depends:- semigroups >= 0.18.1 && <0.19+ semigroups >= 0.18.5 && <0.19 exposed-modules: Servant.Auth.Server Servant.Auth.Server.Internal@@ -88,8 +86,6 @@ , aeson , mtl , warp- , transformers- , markdown-unlit default-language: Haskell2010 if impl(ghcjs) buildable: False@@ -101,7 +97,7 @@ test default-extensions: AutoDeriveTypeable ConstraintKinds DataKinds DefaultSignatures DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable FlexibleContexts FlexibleInstances FunctionalDependencies GADTs KindSignatures MultiParamTypeClasses OverloadedStrings RankNTypes ScopedTypeVariables TypeFamilies TypeOperators ghc-options: -Wall- build-tool-depends: hspec-discover:hspec-discover+ build-tool-depends: hspec-discover:hspec-discover >=2.5.5 && <2.7 -- dependencies with bounds inherited from the library stanza build-depends:@@ -121,12 +117,12 @@ -- test dependencies build-depends: servant-auth-server- , hspec > 2 && < 3- , QuickCheck >= 2.8 && < 2.13- , http-client >= 0.4 && < 0.6- , lens-aeson- , warp- , wreq+ , hspec >= 2.5.5 && < 2.7.0+ , QuickCheck >= 2.11.3 && < 2.13+ , http-client >= 0.5.13.1 && < 0.6+ , lens-aeson >= 1.0.2 && < 1.1+ , warp >= 3.2.25 && < 3.3+ , wreq >= 0.5.2.1 && < 0.6 other-modules: Servant.Auth.ServerSpec default-language: Haskell2010
src/Servant/Auth/Server/Internal/Cookie.hs view
@@ -5,12 +5,14 @@ import Control.Monad.Reader import qualified Crypto.JOSE as Jose import qualified Crypto.JWT as Jose-import Crypto.Util (constTimeEq)+import Data.ByteArray (constEq) import qualified Data.ByteString as BS import qualified Data.ByteString.Base64 as BS64 import qualified Data.ByteString.Lazy as BSL import Data.CaseInsensitive (mk) import Data.Maybe (fromMaybe)+import Data.Time.Calendar (Day(..))+import Data.Time.Clock (UTCTime(..), secondsToDiffTime) import Network.HTTP.Types (methodGet) import Network.HTTP.Types.Header(hCookie) import Network.Wai (Request, requestHeaders, requestMethod)@@ -58,7 +60,7 @@ xsrfCookieAuthCheck xsrfCookieCfg req cookies = fromMaybe False $ do xsrfCookie <- lookup (xsrfCookieName xsrfCookieCfg) cookies xsrfHeader <- lookup (mk $ xsrfHeaderName xsrfCookieCfg) $ requestHeaders req- return $ xsrfCookie `constTimeEq` xsrfHeader+ return $ xsrfCookie `constEq` xsrfHeader -- | Makes a cookie to be used for XSRF. makeXsrfCookie :: CookieSettings -> IO SetCookie@@ -142,7 +144,12 @@ xsrfCookie <- makeXsrfCookie cookieSettings return $ Just $ addHeader sessionCookie . addHeader xsrfCookie --- | Adds headers to a response that clears all session cookies.+-- | Arbitrary cookie expiry time set back in history after unix time 0+expireTime :: UTCTime+expireTime = UTCTime (ModifiedJulianDay 50000) 0++-- | Adds headers to a response that clears all session cookies+-- | using max-age and expires cookie attributes. clearSession :: ( AddHeader "Set-Cookie" SetCookie response withOneCookie , AddHeader "Set-Cookie" SetCookie withOneCookie withTwoCookies ) => CookieSettings@@ -150,10 +157,15 @@ -> withTwoCookies clearSession cookieSettings = addHeader clearedSessionCookie . addHeader clearedXsrfCookie where- clearedSessionCookie = applySessionCookieSettings cookieSettings $ applyCookieSettings cookieSettings def+ -- According to RFC6265 max-age takes precedence, but IE/Edge ignore it completely so we set both+ cookieSettingsExpires = cookieSettings+ { cookieExpires = Just expireTime+ , cookieMaxAge = Just (secondsToDiffTime 0)+ }+ clearedSessionCookie = applySessionCookieSettings cookieSettingsExpires $ applyCookieSettings cookieSettingsExpires def clearedXsrfCookie = case cookieXsrfSetting cookieSettings of- Just xsrfCookieSettings -> applyXsrfCookieSettings xsrfCookieSettings $ applyCookieSettings cookieSettings def- Nothing -> noXsrfTokenCookie cookieSettings+ Just xsrfCookieSettings -> applyXsrfCookieSettings xsrfCookieSettings $ applyCookieSettings cookieSettingsExpires def+ Nothing -> noXsrfTokenCookie cookieSettingsExpires makeSessionCookieBS :: ToJWT v => CookieSettings -> JWTSettings -> v -> IO (Maybe BS.ByteString) makeSessionCookieBS a b c = fmap (toByteString . renderSetCookie) <$> makeSessionCookie a b c
src/Servant/Auth/Server/Internal/JWT.hs view
@@ -5,9 +5,9 @@ import Control.Monad.Reader import qualified Crypto.JOSE as Jose import qualified Crypto.JWT as Jose-import Crypto.Util (constTimeEq) import Data.Aeson (FromJSON, Result (..), ToJSON, fromJSON, toJSON)+import Data.ByteArray (constEq) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import qualified Data.HashMap.Strict as HM@@ -52,7 +52,7 @@ authHdr <- lookup "Authorization" $ requestHeaders req let bearer = "Bearer " (mbearer, rest) = BS.splitAt (BS.length bearer) authHdr- guard (mbearer `constTimeEq` bearer)+ guard (mbearer `constEq` bearer) return rest verifiedJWT <- liftIO $ runExceptT $ do unverifiedJWT <- Jose.decodeCompact $ BSL.fromStrict token
src/Servant/Auth/Server/Internal/Types.hs view
@@ -53,7 +53,7 @@ -- | An @AuthCheck@ is the function used to decide the authentication status -- (the 'AuthResult') of a request. Different @AuthCheck@s may be combined as a -- Monoid or Alternative; the semantics of this is that the *first*--- non-'Indefinite' result from left to right is used.+-- non-'Indefinite' result from left to right is used and the rest are ignored. newtype AuthCheck val = AuthCheck { runAuthCheck :: Request -> IO (AuthResult val) } deriving (Generic, Functor)@@ -61,8 +61,9 @@ instance Semigroup (AuthCheck val) where AuthCheck f <> AuthCheck g = AuthCheck $ \x -> do fx <- f x- gx <- g x- return $ fx <> gx+ case fx of+ Indefinite -> g x+ r -> pure r instance Monoid (AuthCheck val) where mempty = AuthCheck $ const $ return mempty
test/Servant/Auth/ServerSpec.hs view
@@ -25,9 +25,11 @@ import Data.Foldable (find) import Data.Monoid import Data.Time+import Data.Time.Clock (getCurrentTime) import GHC.Generics (Generic) import Network.HTTP.Client (cookie_http_only, cookie_name, cookie_value,+ cookie_expiry_time, destroyCookieJar) import Network.HTTP.Types (Status, status200, status401)@@ -45,6 +47,7 @@ import Servant hiding (BasicAuth, IsSecure (..), header) import Servant.Auth.Server+import Servant.Auth.Server.Internal.Cookie (expireTime) import Servant.Auth.Server.SetCookieOrphan () import System.IO.Unsafe (unsafePerformIO) import Test.Hspec@@ -203,13 +206,15 @@ resp <- getWith loggedInOpts (url port) resp ^? responseBody . _JSON `shouldBe` Just (length $ name user) + -- logout resp <- getWith loggedInOpts (url port ++ "/logout")- (resp ^. responseCookieJar) `shouldMatchCookieNameValues`- [ (sessionCookieName cookieCfg, "value")- , (xsrfField xsrfCookieName cookieCfg, "value")- ]- let loggedOutOpts = optsFromResp resp + -- assert cookies were expired+ now <- getCurrentTime+ let assertCookie c = now >= cookie_expiry_time c+ all assertCookie (destroyCookieJar (resp ^. responseCookieJar)) `shouldBe` True++ let loggedOutOpts = optsFromResp resp getWith loggedOutOpts (url port) `shouldHTTPErrorWith` status401 describe "With no XSRF check for GET requests" $ let@@ -268,6 +273,12 @@ [ (sessionCookieName cookieCfg, "value") , ("NO-XSRF-TOKEN", "") ]++ -- assert cookies were expired+ now <- getCurrentTime+ let assertCookie c = now >= cookie_expiry_time c+ all assertCookie (destroyCookieJar (resp ^. responseCookieJar)) `shouldBe` True+ let loggedOutOpts = optsFromResp resp getWith loggedOutOpts (url port) `shouldHTTPErrorWith` status401