mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-24 16:03:37 +03:00
69501b2657
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3979 GitOrigin-RevId: 5f5ebfb25ff9729e34397084d86b0fe968099b25
102 lines
5.1 KiB
Haskell
102 lines
5.1 KiB
Haskell
module Hasura.Server.Auth.JWTSpec (spec) where
|
|
|
|
import Control.Arrow
|
|
import Data.ByteString.UTF8 qualified as BS
|
|
import Data.Text.Encoding (encodeUtf8)
|
|
import Data.Time (NominalDiffTime, UTCTime (..), addUTCTime, defaultTimeLocale, formatTime, fromGregorian, secondsToDiffTime, secondsToNominalDiffTime)
|
|
import Hasura.Logging (Hasura, Logger (..))
|
|
import Hasura.Prelude
|
|
import Hasura.Server.Auth.JWT qualified as JWT
|
|
import Hasura.Server.Auth.JWT.Logging (JwkFetchError)
|
|
import Network.HTTP.Types (Header, ResponseHeaders)
|
|
import Test.Hspec
|
|
|
|
spec :: Spec
|
|
spec = do
|
|
determineJwkExpiryLifetimeTests
|
|
|
|
determineJwkExpiryLifetimeTests :: Spec
|
|
determineJwkExpiryLifetimeTests = describe "determineJwkExpiryLifetime" $ do
|
|
it "no-cache in Cache-Control means an immediate expiry" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
let cacheControl = cacheControlHeader "max-age=10, no-cache"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Right . Just $ secondsToNominalDiffTime 0)
|
|
|
|
it "must-revalidate without max-age in Cache-Control means an immediate expiry" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
let cacheControl = cacheControlHeader "must-revalidate"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Right . Just $ secondsToNominalDiffTime 0)
|
|
|
|
it "must-revalidate with max-age in Cache-Control uses max-age for token expiry" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
let cacheControl = cacheControlHeader "max-age=10, must-revalidate"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Right . Just $ secondsToNominalDiffTime 10)
|
|
|
|
it "no-store in Cache-Control means an immediate expiry" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
let cacheControl = cacheControlHeader "max-age=10, no-store"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Right . Just $ secondsToNominalDiffTime 0)
|
|
|
|
it "max-age in Cache-Control without no-cache, no-store is used for token expiry" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
let cacheControl = cacheControlHeader "public, max-age=10"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Right . Just $ secondsToNominalDiffTime 10)
|
|
|
|
it "s-maxage in Cache-Control without no-cache, no-store is used for token expiry" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
let cacheControl = cacheControlHeader "public, s-maxage=10"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Right . Just $ secondsToNominalDiffTime 10)
|
|
|
|
it "Expires header is used as a fallback if Cache-Control contains nothing indicative" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
let cacheControl = cacheControlHeader "public"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Right . Just $ secondsToNominalDiffTime 60)
|
|
|
|
it "Expires header is used as a fallback if Cache-Control is missing" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
result <- determineJwkExpiryLifetime' [expires]
|
|
result `shouldBe` (Right . Just $ secondsToNominalDiffTime 60)
|
|
|
|
it "If no relevant headers, then return Nothing" $ do
|
|
result <- determineJwkExpiryLifetime' [("X-SomeOtherHeader", "Irrelevant")]
|
|
result `shouldBe` (Right Nothing)
|
|
|
|
it "If max-age in Cache-Control is corrupt, then return an error" $ do
|
|
let expires = expiresHeader (addUTCTime (secondsToNominalDiffTime 60) currentTimeForTest)
|
|
let cacheControl = cacheControlHeader "max-age=lolbroken"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Left ())
|
|
|
|
it "If Expires is corrupt, then return an error" $ do
|
|
let expires = ("Expires", "lolbroken")
|
|
let cacheControl = cacheControlHeader "public"
|
|
result <- determineJwkExpiryLifetime' [expires, cacheControl]
|
|
result `shouldBe` (Left ())
|
|
|
|
determineJwkExpiryLifetime' :: MonadIO m => ResponseHeaders -> m (Either () (Maybe NominalDiffTime))
|
|
determineJwkExpiryLifetime' headers =
|
|
discardJwkFetchError <$> runExceptT (JWT.determineJwkExpiryLifetime (pure currentTimeForTest) voidLogger headers)
|
|
|
|
currentTimeForTest :: UTCTime
|
|
currentTimeForTest = UTCTime (fromGregorian 2021 01 21) (secondsToDiffTime 0)
|
|
|
|
voidLogger :: Logger Hasura
|
|
voidLogger = (Logger $ void . return)
|
|
|
|
cacheControlHeader :: Text -> Header
|
|
cacheControlHeader val = ("Cache-Control", encodeUtf8 val)
|
|
|
|
expiresHeader :: UTCTime -> Header
|
|
expiresHeader val = ("Expires", BS.fromString $ formatTime defaultTimeLocale "%a, %d %b %Y %T GMT" val)
|
|
|
|
-- JwkFetchError is not Eq, so we'll discard it for testing
|
|
discardJwkFetchError :: Either JwkFetchError a -> Either () a
|
|
discardJwkFetchError = left (const ())
|