Add tests for "exp" field of the JWT

Assert that the exp field of the JWT is "fresh".
This commit is contained in:
William Carroll 2020-08-08 14:47:39 +01:00
parent f1883b2790
commit 8a7a3b29a9
4 changed files with 61 additions and 13 deletions

View file

@ -9,6 +9,8 @@ import Utils
import qualified Data.Map as Map
import qualified GoogleSignIn
import qualified TestUtils
import qualified Data.Time.Clock.POSIX as POSIX
import qualified System.IO.Unsafe as Unsafe
--------------------------------------------------------------------------------
-- | These are the JWT fields that I'd like to overwrite in the `googleJWT`
@ -17,15 +19,23 @@ data JWTFields = JWTFields
{ overwriteSigner :: Signer
, overwriteAuds :: [StringOrURI]
, overwriteIss :: StringOrURI
, overwriteExp :: NumericDate
}
defaultJWTFields :: JWTFields
defaultJWTFields = JWTFields
{ overwriteSigner = hmacSecret "secret"
, overwriteAuds = ["771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"]
|> fmap TestUtils.unsafeStringOrURI
, overwriteIss = TestUtils.unsafeStringOrURI "accounts.google.com"
}
defaultJWTFields = do
let tenDaysFromToday = POSIX.getPOSIXTime
|> Unsafe.unsafePerformIO
|> (\x -> x * 60 * 60 * 25 * 10)
|> numericDate
|> TestUtils.unsafeJust
JWTFields
{ overwriteSigner = hmacSecret "secret"
, overwriteAuds = ["771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"]
|> fmap TestUtils.unsafeStringOrURI
, overwriteIss = TestUtils.unsafeStringOrURI "accounts.google.com"
, overwriteExp = tenDaysFromToday
}
googleJWT :: JWTFields -> GoogleSignIn.EncodedJWT
googleJWT JWTFields{..} =
@ -49,7 +59,7 @@ googleJWT JWTFields{..} =
, sub = stringOrURI "114079822315085727057"
, aud = overwriteAuds |> Right |> Just
-- TODO: Replace date creation with a human-readable date constructor.
, Web.JWT.exp = numericDate 1596756453
, Web.JWT.exp = Just overwriteExp
, nbf = Nothing
-- TODO: Replace date creation with a human-readable date constructor.
, iat = numericDate 1596752853

View file

@ -9,6 +9,8 @@ import Utils
import qualified Network.HTTP.Simple as HTTP
import qualified Data.Text as Text
import qualified Web.JWT as JWT
import qualified Data.Time.Clock.POSIX as POSIX
--------------------------------------------------------------------------------
newtype EncodedJWT = EncodedJWT Text
@ -21,7 +23,9 @@ data ValidationResult
| NoMatchingClientIDs [StringOrURI]
| WrongIssuer StringOrURI
| StringOrURIParseFailure Text
| MissingIssuer
| TimeConversionFailure
| MissingRequiredClaim Text
| StaleExpiry NumericDate
deriving (Eq, Show)
-- | Returns True when the supplied `jwt` meets the following criteria:
@ -73,10 +77,18 @@ jwtIsValid skipHTTP (EncodedJWT encodedJWT) = do
if not $ clientID `elem` audValues then
pure $ NoMatchingClientIDs audValues
else
case jwt |> claims |> iss of
Nothing -> pure MissingIssuer
Just jwtIssuer ->
case (jwt |> claims |> iss, jwt |> claims |> JWT.exp) of
(Nothing, _) -> pure $ MissingRequiredClaim "iss"
(_, Nothing) -> pure $ MissingRequiredClaim "exp"
(Just jwtIssuer, Just jwtExpiry) ->
if not $ jwtIssuer `elem` parsedIssuers then
pure $ WrongIssuer jwtIssuer
else
pure Valid
else do
mCurrentTime <- POSIX.getPOSIXTime |> fmap numericDate
case mCurrentTime of
Nothing -> pure TimeConversionFailure
Just currentTime ->
if not $ currentTime <= jwtExpiry then
pure $ StaleExpiry jwtExpiry
else
pure Valid

View file

@ -4,11 +4,13 @@ module Spec where
--------------------------------------------------------------------------------
import Test.Hspec
import Utils
import Web.JWT (numericDate)
import GoogleSignIn (ValidationResult(..))
import qualified GoogleSignIn
import qualified Fixtures as F
import qualified TestUtils
import qualified Data.Time.Clock.POSIX as POSIX
--------------------------------------------------------------------------------
main :: IO ()
@ -44,3 +46,23 @@ main = hspec $ do
encodedJWT = F.defaultJWTFields { F.overwriteIss = erroneousIssuer }
|> F.googleJWT
jwtIsValid' encodedJWT `shouldReturn` Valid
it "fails validation when the exp field has expired" $ do
let mErroneousExp = numericDate 0
case mErroneousExp of
Nothing -> True `shouldBe` False
Just erroneousExp -> do
let encodedJWT = F.defaultJWTFields { F.overwriteExp = erroneousExp }
|> F.googleJWT
jwtIsValid' encodedJWT `shouldReturn` StaleExpiry erroneousExp
it "passes validation when the exp field is current" $ do
mFreshExp <- POSIX.getPOSIXTime
|> fmap (\x -> x * 60 * 60 * 24 * 10) -- 10 days later
|> fmap numericDate
case mFreshExp of
Nothing -> True `shouldBe` False
Just freshExp -> do
let encodedJWT = F.defaultJWTFields { F.overwriteExp = freshExp }
|> F.googleJWT
jwtIsValid' encodedJWT `shouldReturn` Valid

View file

@ -10,3 +10,7 @@ unsafeStringOrURI x =
case stringOrURI (cs x) of
Nothing -> error $ "Failed to convert to StringOrURI: " ++ x
Just x -> x
unsafeJust :: Maybe a -> a
unsafeJust Nothing = error "Attempted to force a Nothing to be a something"
unsafeJust (Just x) = x