about summary refs log tree commit diff
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2020-08-08T13·47+0100
committerWilliam Carroll <wpcarro@gmail.com>2020-08-08T13·47+0100
commit8a7a3b29a9413d634b8f8a71119cc54a6132df41 (patch)
treefc32f99e9f1a9a7b9ef52f364718c56d33ccedcf
parentf1883b279037375c66cf683b7392652624381c59 (diff)
Add tests for "exp" field of the JWT
Assert that the exp field of the JWT is "fresh".
-rw-r--r--website/sandbox/learnpianochords/src/server/Fixtures.hs24
-rw-r--r--website/sandbox/learnpianochords/src/server/GoogleSignIn.hs24
-rw-r--r--website/sandbox/learnpianochords/src/server/Spec.hs22
-rw-r--r--website/sandbox/learnpianochords/src/server/TestUtils.hs4
4 files changed, 61 insertions, 13 deletions
diff --git a/website/sandbox/learnpianochords/src/server/Fixtures.hs b/website/sandbox/learnpianochords/src/server/Fixtures.hs
index ea7e0301ec4f..169e29632bd3 100644
--- a/website/sandbox/learnpianochords/src/server/Fixtures.hs
+++ b/website/sandbox/learnpianochords/src/server/Fixtures.hs
@@ -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
diff --git a/website/sandbox/learnpianochords/src/server/GoogleSignIn.hs b/website/sandbox/learnpianochords/src/server/GoogleSignIn.hs
index f138f2b615c8..e83ec2cfdb45 100644
--- a/website/sandbox/learnpianochords/src/server/GoogleSignIn.hs
+++ b/website/sandbox/learnpianochords/src/server/GoogleSignIn.hs
@@ -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
diff --git a/website/sandbox/learnpianochords/src/server/Spec.hs b/website/sandbox/learnpianochords/src/server/Spec.hs
index 96f10a9c4332..097ae3d5158d 100644
--- a/website/sandbox/learnpianochords/src/server/Spec.hs
+++ b/website/sandbox/learnpianochords/src/server/Spec.hs
@@ -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
diff --git a/website/sandbox/learnpianochords/src/server/TestUtils.hs b/website/sandbox/learnpianochords/src/server/TestUtils.hs
index c586f7f219ba..e62950369e65 100644
--- a/website/sandbox/learnpianochords/src/server/TestUtils.hs
+++ b/website/sandbox/learnpianochords/src/server/TestUtils.hs
@@ -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