servant-auth-server 0.4.5.1 → 0.4.6.0
raw patch · 9 files changed
+85/−60 lines, 9 filesdep ~QuickCheckdep ~aesondep ~basePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: QuickCheck, aeson, base, base64-bytestring, http-client, lens, memory, servant, servant-auth, servant-server
API changes (from Hackage documentation)
- Servant.Auth.Server.Internal: instance (n Data.Type.Equality.~ 'Servant.Auth.Server.Internal.AddSetCookie.S ('Servant.Auth.Server.Internal.AddSetCookie.S 'Servant.Auth.Server.Internal.AddSetCookie.Z), Servant.Server.Internal.HasServer (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookiesApi n api) ctxs, Servant.Auth.Server.Internal.Class.AreAuths auths ctxs v, Servant.Server.Internal.HasServer api ctxs, Servant.Auth.Server.Internal.AddSetCookie.AddSetCookies n (Servant.Server.Internal.ServerT api Servant.Server.Internal.Handler.Handler) (Servant.Server.Internal.ServerT (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookiesApi n api) Servant.Server.Internal.Handler.Handler), Servant.Auth.Server.Internal.JWT.ToJWT v, Servant.Server.Internal.Context.HasContextEntry ctxs Servant.Auth.Server.Internal.ConfigTypes.CookieSettings, Servant.Server.Internal.Context.HasContextEntry ctxs Servant.Auth.Server.Internal.ConfigTypes.JWTSettings) => Servant.Server.Internal.HasServer (Servant.Auth.Auth auths v Servant.API.Sub.:> api) ctxs
- Servant.Auth.Server.Internal.Class: instance Servant.Auth.Server.Internal.JWT.FromJWT usr => Servant.Auth.Server.Internal.Class.IsAuth Servant.Auth.Cookie usr
- Servant.Auth.Server.Internal.Class: instance Servant.Auth.Server.Internal.JWT.FromJWT usr => Servant.Auth.Server.Internal.Class.IsAuth Servant.Auth.JWT usr
- Servant.Auth.Server.Internal.JWT: class FromJWT a
- Servant.Auth.Server.Internal.JWT: class ToJWT a
- Servant.Auth.Server.Internal.JWT: decodeJWT :: (FromJWT a, FromJSON a) => ClaimsSet -> Either Text a
- Servant.Auth.Server.Internal.JWT: encodeJWT :: (ToJWT a, ToJSON a) => a -> ClaimsSet
+ Servant.Auth.Server: verifyJWT :: FromJWT a => JWTSettings -> ByteString -> IO (Maybe a)
+ Servant.Auth.Server.Internal: instance (n Data.Type.Equality.~ 'Servant.Auth.Server.Internal.AddSetCookie.S ('Servant.Auth.Server.Internal.AddSetCookie.S 'Servant.Auth.Server.Internal.AddSetCookie.Z), Servant.Server.Internal.HasServer (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookiesApi n api) ctxs, Servant.Auth.Server.Internal.Class.AreAuths auths ctxs v, Servant.Server.Internal.HasServer api ctxs, Servant.Auth.Server.Internal.AddSetCookie.AddSetCookies n (Servant.Server.Internal.ServerT api Servant.Server.Internal.Handler.Handler) (Servant.Server.Internal.ServerT (Servant.Auth.Server.Internal.AddSetCookie.AddSetCookiesApi n api) Servant.Server.Internal.Handler.Handler), Servant.Auth.JWT.ToJWT v, Servant.Server.Internal.Context.HasContextEntry ctxs Servant.Auth.Server.Internal.ConfigTypes.CookieSettings, Servant.Server.Internal.Context.HasContextEntry ctxs Servant.Auth.Server.Internal.ConfigTypes.JWTSettings) => Servant.Server.Internal.HasServer (Servant.Auth.Auth auths v Servant.API.Sub.:> api) ctxs
+ Servant.Auth.Server.Internal.Class: instance Servant.Auth.JWT.FromJWT usr => Servant.Auth.Server.Internal.Class.IsAuth Servant.Auth.Cookie usr
+ Servant.Auth.Server.Internal.Class: instance Servant.Auth.JWT.FromJWT usr => Servant.Auth.Server.Internal.Class.IsAuth Servant.Auth.JWT usr
+ Servant.Auth.Server.Internal.JWT: verifyJWT :: FromJWT a => JWTSettings -> ByteString -> IO (Maybe a)
- Servant.Auth.Server: decodeJWT :: (FromJWT a, FromJSON a) => ClaimsSet -> Either Text a
+ Servant.Auth.Server: decodeJWT :: FromJWT a => ClaimsSet -> Either Text a
- Servant.Auth.Server: encodeJWT :: (ToJWT a, ToJSON a) => a -> ClaimsSet
+ Servant.Auth.Server: encodeJWT :: ToJWT a => a -> ClaimsSet
Files
- CHANGELOG.md +10/−0
- README.lhs +1/−1
- servant-auth-server.cabal +10/−10
- src/Servant/Auth/Server.hs +2/−0
- src/Servant/Auth/Server/Internal.hs +1/−0
- src/Servant/Auth/Server/Internal/BasicAuth.hs +25/−0
- src/Servant/Auth/Server/Internal/Class.hs +2/−1
- src/Servant/Auth/Server/Internal/Cookie.hs +14/−13
- src/Servant/Auth/Server/Internal/JWT.hs +20/−35
CHANGELOG.md view
@@ -7,9 +7,19 @@ ## [Unreleased] +## [0.4.6.0] - 2020-10-06++## Changed++- expose verifyJWT and use it in two places [@domenkozar]+- support GHC 8.10 [@domenkozar]+- move ToJWT/FromJWT to servant-auth [@erewok]+- #165 fix AnySite with Cookie 3.5.0 [@odr]+ ## [0.4.5.1] - 2020-02-06 ## Changed+ - #158 servant 0.17 support [@phadej] ## [0.4.5.0] - 2019-12-28
README.lhs view
@@ -1,6 +1,6 @@ # servant-auth -[](https://travis-ci.org/haskell-servant/servant-auth)+ These packages provides safe and easy-to-use authentication options for `servant`. The same API can be protected via:
servant-auth-server.cabal view
@@ -1,5 +1,5 @@ name: servant-auth-server-version: 0.4.5.1+version: 0.4.6.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 == 8.0.2, GHC == 8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.1+tested-with: GHC == 8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.1, GHC==8.10.2 build-type: Simple cabal-version: >= 1.10 extra-source-files:@@ -31,9 +31,9 @@ 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.9 && < 4.14- , aeson >= 1.3.1.1 && < 1.5- , base64-bytestring >= 1.0.0.1 && < 1.1+ base >= 4.10 && < 4.15+ , aeson >= 1.3.1.1 && < 1.6+ , base64-bytestring >= 1.0.0.1 && < 1.2 , blaze-builder >= 0.4.1.0 && < 0.5 , bytestring >= 0.10.6.0 && < 0.11 , case-insensitive >= 1.2.0.11 && < 1.3@@ -42,13 +42,13 @@ , 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.19+ , lens >= 4.16.1 && < 4.20 , memory >= 0.14.16 && < 0.16 , monad-time >= 0.3.1.0 && < 0.4 , mtl >= 2.2.2 && < 2.3- , servant >= 0.13 && < 0.18- , servant-auth == 0.3.*- , servant-server >= 0.13 && < 0.18+ , servant >= 0.13 && < 0.19+ , servant-auth == 0.4.*+ , servant-server >= 0.13 && < 0.19 , tagged >= 0.8.4 && < 0.9 , text >= 1.2.3.0 && < 1.3 , time >= 1.5.0.1 && < 1.10@@ -119,7 +119,7 @@ build-depends: servant-auth-server , hspec >= 2.5.5 && < 2.8- , QuickCheck >= 2.11.3 && < 2.14+ , QuickCheck >= 2.11.3 && < 2.15 , http-client >= 0.5.13.1 && < 0.7 , lens-aeson >= 1.0.2 && < 1.2 , warp >= 3.2.25 && < 3.4
src/Servant/Auth/Server.hs view
@@ -132,6 +132,7 @@ , writeKey , readKey , makeJWT+ , verifyJWT -- ** Re-exports , Default(def)@@ -142,6 +143,7 @@ import Data.ByteString (ByteString, writeFile, readFile) import Data.Default.Class (Default (def)) import Servant.Auth+import Servant.Auth.JWT import Servant.Auth.Server.Internal () import Servant.Auth.Server.Internal.BasicAuth import Servant.Auth.Server.Internal.Class
src/Servant/Auth/Server/Internal.hs view
@@ -9,6 +9,7 @@ Proxy (..), HasContextEntry(getContextEntry)) import Servant.Auth+import Servant.Auth.JWT (ToJWT) import Servant.Auth.Server.Internal.AddSetCookie import Servant.Auth.Server.Internal.Class
src/Servant/Auth/Server/Internal/BasicAuth.hs view
@@ -19,6 +19,31 @@ wwwAuthenticatedErr :: BS.ByteString -> ServerError wwwAuthenticatedErr realm = err401 { errHeaders = [mkBAChallengerHdr realm] } +-- | A type holding the configuration for Basic Authentication. +-- It is defined as a type family with no arguments, so that+-- it can be instantiated to whatever type you need to+-- authenticate your users (use @type instance BasicAuthCfg = ...@).+-- +-- Note that the instantiation is application-wide,+-- i.e. there can be only one instance.+-- As a consequence, it should not be instantiated in a library.+-- +-- Basic Authentication expects an element of type 'BasicAuthCfg'+-- to be in the 'Context'; that element is then passed automatically+-- to the instance of 'FromBasicAuthData' together with the+-- authentication data obtained from the client.+-- +-- If you do not need a configuration for Basic Authentication,+-- you can use just @BasicAuthCfg = ()@, and recall to also+-- add @()@ to the 'Context'.+-- A basic but more interesting example is to take as 'BasicAuthCfg' +-- a list of authorised username/password pairs:+-- +-- > deriving instance Eq BasicAuthData+-- > type instance BasicAuthCfg = [BasicAuthData]+-- > instance FromBasicAuthData User where+-- > fromBasicAuthData authData authCfg =+-- > if elem authData authCfg then ... type family BasicAuthCfg class FromBasicAuthData a where
src/Servant/Auth/Server/Internal/Class.hs view
@@ -5,11 +5,12 @@ import Data.Monoid import Servant hiding (BasicAuth) +import Servant.Auth.JWT import Servant.Auth.Server.Internal.Types import Servant.Auth.Server.Internal.ConfigTypes import Servant.Auth.Server.Internal.BasicAuth import Servant.Auth.Server.Internal.Cookie-import Servant.Auth.Server.Internal.JWT+import Servant.Auth.Server.Internal.JWT (jwtAuthCheck) -- | @IsAuth a ctx v@ indicates that @a@ is an auth type that expects all -- elements of @ctx@ to be the in the Context and whose authentication check
src/Servant/Auth/Server/Internal/Cookie.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} module Servant.Auth.Server.Internal.Cookie where import Blaze.ByteString.Builder (toByteString)@@ -20,14 +21,14 @@ import System.Entropy (getEntropy) import Web.Cookie +import Servant.Auth.JWT (FromJWT (decodeJWT), ToJWT) import Servant.Auth.Server.Internal.ConfigTypes-import Servant.Auth.Server.Internal.JWT (FromJWT (decodeJWT), ToJWT,- makeJWT)+import Servant.Auth.Server.Internal.JWT (makeJWT, verifyJWT) import Servant.Auth.Server.Internal.Types cookieAuthCheck :: FromJWT usr => CookieSettings -> JWTSettings -> AuthCheck usr-cookieAuthCheck ccfg jwtCfg = do+cookieAuthCheck ccfg jwtSettings = do req <- ask jwtCookie <- maybe mempty return $ do cookies' <- lookup hCookie $ requestHeaders req@@ -38,16 +39,10 @@ return $ xsrfCookieAuthCheck xsrfCookieCfg req cookies -- session cookie *must* be HttpOnly and Secure lookup (sessionCookieName ccfg) cookies- verifiedJWT <- liftIO $ runExceptT $ do- unverifiedJWT <- Jose.decodeCompact $ BSL.fromStrict jwtCookie- Jose.verifyClaims (jwtSettingsToJwtValidationSettings jwtCfg)- (validationKeys jwtCfg)- unverifiedJWT+ verifiedJWT <- liftIO $ verifyJWT jwtSettings jwtCookie case verifiedJWT of- Left (_ :: Jose.JWTError) -> mzero- Right v -> case decodeJWT v of- Left _ -> mzero- Right v' -> return v'+ Nothing -> mzero+ Just v -> return v xsrfCheckRequired :: CookieSettings -> Request -> Maybe XsrfCookieSettings xsrfCheckRequired cookieSettings req = do@@ -120,11 +115,17 @@ applySessionCookieSettings cookieSettings setCookie = setCookie { setCookieName = sessionCookieName cookieSettings , setCookieSameSite = case cookieSameSite cookieSettings of- AnySite -> Nothing+ AnySite -> anySite SameSiteStrict -> Just sameSiteStrict SameSiteLax -> Just sameSiteLax , setCookieHttpOnly = True }+ where+#if MIN_VERSION_cookie(0,4,5)+ anySite = Just sameSiteNone+#else+ anySite = Nothing+#endif -- | For a JWT-serializable session, returns a function that decorates a -- provided response object with XSRF and session cookies. This should be used
src/Servant/Auth/Server/Internal/JWT.hs view
@@ -16,37 +16,15 @@ import Data.Time (UTCTime) import Network.Wai (requestHeaders) +import Servant.Auth.JWT (FromJWT(..), ToJWT(..)) import Servant.Auth.Server.Internal.ConfigTypes import Servant.Auth.Server.Internal.Types --- This should probably also be from ClaimSet------ | How to decode data from a JWT.------ The default implementation assumes the data is stored in the unregistered--- @dat@ claim, and uses the @FromJSON@ instance to decode value from there.-class FromJWT a where- decodeJWT :: Jose.ClaimsSet -> Either T.Text a- default decodeJWT :: FromJSON a => Jose.ClaimsSet -> Either T.Text a- decodeJWT m = case HM.lookup "dat" (m ^. Jose.unregisteredClaims) of- Nothing -> Left "Missing 'dat' claim"- Just v -> case fromJSON v of- Error e -> Left $ T.pack e- Success a -> Right a --- | How to encode data from a JWT.------ The default implementation stores data in the unregistered @dat@ claim, and--- uses the type's @ToJSON@ instance to encode the data.-class ToJWT a where- encodeJWT :: a -> Jose.ClaimsSet- default encodeJWT :: ToJSON a => a -> Jose.ClaimsSet- encodeJWT a = Jose.addClaim "dat" (toJSON a) Jose.emptyClaimsSet- -- | A JWT @AuthCheck@. You likely won't need to use this directly unless you -- are protecting a @Raw@ endpoint. jwtAuthCheck :: FromJWT usr => JWTSettings -> AuthCheck usr-jwtAuthCheck config = do+jwtAuthCheck jwtSettings = do req <- ask token <- maybe mempty return $ do authHdr <- lookup "Authorization" $ requestHeaders req@@ -54,18 +32,10 @@ (mbearer, rest) = BS.splitAt (BS.length bearer) authHdr guard (mbearer `constEq` bearer) return rest- verifiedJWT <- liftIO $ runExceptT $ do- unverifiedJWT <- Jose.decodeCompact $ BSL.fromStrict token- Jose.verifyClaims (jwtSettingsToJwtValidationSettings config)- (validationKeys config)- unverifiedJWT+ verifiedJWT <- liftIO $ verifyJWT jwtSettings token case verifiedJWT of- Left (_ :: Jose.JWTError) -> mzero- Right v -> case decodeJWT v of- Left _ -> mzero- Right v' -> return v'--+ Nothing -> mzero+ Just v -> return v -- | Creates a JWT containing the specified data. The data is stored in the -- @dat@ claim. The 'Maybe UTCTime' argument indicates the time at which the@@ -84,3 +54,18 @@ addExp claims = case expiry of Nothing -> claims Just e -> claims & Jose.claimExp ?~ Jose.NumericDate e+++verifyJWT :: FromJWT a => JWTSettings -> BS.ByteString -> IO (Maybe a)+verifyJWT jwtCfg input = do+ verifiedJWT <- liftIO $ runExceptT $ do+ unverifiedJWT <- Jose.decodeCompact (BSL.fromStrict input)+ Jose.verifyClaims+ (jwtSettingsToJwtValidationSettings jwtCfg)+ (validationKeys jwtCfg)+ unverifiedJWT+ return $ case verifiedJWT of+ Left (_ :: Jose.JWTError) -> Nothing+ Right v -> case decodeJWT v of+ Left _ -> Nothing+ Right v' -> Just v'