shomei-jwt-0.2.0.0: test/Shomei/SigningKey/Jwks/JwtSpec.hs
-- | Scenario (g), JWKS-shape half: 'jwksDocument' for two keys is valid JSON
-- with a top-level @"keys"@ array of two objects carrying the correct @kid@s and
-- no private @"d"@ field. The kid-selection half (sign with A, verify against
-- {A, B}) lives in 'Shomei.SigningKey.Sign.JwtSpec' because it needs the signer.
module Shomei.SigningKey.Jwks.JwtSpec (tests) where
import Data.Aeson (Value (Array, Object, String))
import Data.Aeson qualified as Aeson
import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KM
import Data.ByteString.Lazy (ByteString)
import Data.Foldable (toList)
import Data.List (sort)
import Data.Text (Text)
import Shomei.SigningKey.Jwks.Jwt (jwksDocument)
import Shomei.SigningKey.Key.Jwt (generateSigningKey, keyKid)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))
tests :: TestTree
tests =
testGroup
"Jwks"
[ testCase "publishes public-only JWKS with the right kids" $ do
a <- generateSigningKey
b <- generateSigningKey
objs <- keysArray (jwksDocument [a, b])
length objs @?= 2
assertBool "no private d field" (not (any (KM.member (Key.fromText "d")) objs))
sort (kidsOf objs) @?= sort [keyKid a, keyKid b]
]
-- | Decode a JWKS document and return the objects in its @"keys"@ array.
keysArray :: ByteString -> IO [KM.KeyMap Value]
keysArray doc =
case Aeson.decode doc of
Just (Object top) ->
case KM.lookup (Key.fromText "keys") top of
Just (Array arr) -> pure [o | Object o <- toList arr]
_ -> assertFailure "JWKS has no \"keys\" array" >> pure []
_ -> assertFailure "JWKS is not a JSON object" >> pure []
-- | Extract the @"kid"@ string of each key object.
kidsOf :: [KM.KeyMap Value] -> [Text]
kidsOf objs = [k | o <- objs, Just (String k) <- [KM.lookup (Key.fromText "kid") o]]