packages feed

jwt 0.2.0 → 0.2.1

raw patch · 5 files changed

+88/−95 lines, 5 files

Files

changelog view
@@ -1,14 +1,23 @@+2014-03-10 0.2.1+=================++    * Add Decoding/Encoding sections+    * Make the examples runnable by doctest+    * Fix hlint warnings+    * Add 'secondsSinceEpoch' to extract the seconds from epoch from an IntDate++ 2014-03-10 0.2.0 =================  	* Export the IntDate and StringOrURI types #5a1137b -2014-03-03	0.1.1+2014-03-03  0.1.1 =================  	* Verify that invalid input to the decode* functions fails as expected -2014-03-03	0.1.0+2014-03-03  0.1.0 =================  	* Initial release
jwt.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                jwt-version:             0.2.0+version:             0.2.1 synopsis:            JSON Web Token (JWT) decoding and encoding license:             MIT license-file:        LICENSE
src/Web/JWT.hs view
@@ -27,8 +27,10 @@ module Web.JWT     (     -- * Encoding & Decoding JWTs+    -- ** Decoding       decode     , decodeAndVerifySignature+    -- ** Encoding     , encodeSigned     , encodeUnsigned @@ -43,6 +45,7 @@     -- ** JWT claims set     , intDate     , stringOrURI+    , secondsSinceEpoch     -- ** JWT header     , typ     , cty@@ -122,9 +125,12 @@ signature (Verified _ _ s) = Just s  -- | A JSON numeric value representing the number of seconds from---   1970-01-01T0:0:0Z UTC until the specified UTC date/time.+-- 1970-01-01T0:0:0Z UTC until the specified UTC date/time. newtype IntDate = IntDate Integer deriving (Show, Eq) +-- | Return the seconds since 1970-01-01T0:0:0Z UTC for the given 'IntDate'+secondsSinceEpoch :: IntDate -> NominalDiffTime+secondsSinceEpoch (IntDate s) = fromInteger s  -- | A JSON string value, with the additional requirement that while -- arbitrary string values MAY be used, any value containing a ":"@@ -197,23 +203,20 @@     def = JWTClaimsSet Nothing Nothing Nothing Nothing Nothing Nothing Nothing Map.empty  ++ -- | Encode a claims set using the given secret ----- > {-# LANGUAGE OverloadedStrings #-}--- > import           Data.Aeson--- > import qualified Data.Map as Map--- >--- > let cs = def {  -- def returns a default JWTClaimsSet--- >     iss = stringOrURI "Foo"--- >   , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]--- > }--- >     key = secret "secret-key"--- >     jwt = encodeSigned HS256 key cs------ This yields:------ > >>> jwt--- > "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiRm9vIn0.vHQHuG3ujbnBUmEp-fSUtYxk27rLiP2hrNhxpyWhb2E"+-- >>> :{+--  let+--      cs = def { -- def returns a default JWTClaimsSet+--         iss = stringOrURI "Foo"+--       , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]+--      }+--      key = secret "secret-key"+--  in encodeSigned HS256 key cs+-- :}+-- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiRm9vIn0.vHQHuG3ujbnBUmEp-fSUtYxk27rLiP2hrNhxpyWhb2E" encodeSigned :: Algorithm -> Secret -> JWTClaimsSet -> JSON encodeSigned algo secret claims = dotted [header, claim, signature]     where claim     = encodeJWT claims@@ -225,20 +228,16 @@  -- | Encode a claims set without signing it ----- > {-# LANGUAGE OverloadedStrings #-}--- > import           Data.Aeson--- > import qualified Data.Map as Map--- >--- > let cs = def {  -- def returns a default JWTClaimsSet--- >     iss = stringOrURI "Foo"--- >   , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]--- > }--- >     jwt = encodeUnsigned cs------ This yields:------ > >>> jwt--- > "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiRm9vIn0."+-- >>> :{+--  let+--      cs = def { -- def returns a default JWTClaimsSet+--      iss = stringOrURI "Foo"+--    , iat = intDate 1394700934+--    , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]+--  }+--  in encodeUnsigned cs+-- :}+-- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjEzOTQ3MDA5MzQsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlLCJpc3MiOiJGb28ifQ." encodeUnsigned :: JWTClaimsSet -> JSON encodeUnsigned claims = dotted [header, claim, ""]     where claim     = encodeJWT claims@@ -253,29 +252,23 @@ -- (e.g. the secret needs to be retrieved based on unverified information -- from the claims set). ----- > import qualified Data.Text as T--- > let input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text--- >     mJwt = decode input--- >     mHeader = fmap header mJwt--- >     mClaims = fmap claims mJwt--- >     mSignature = join $ fmap signature mJwt------ This yields:------ > >>> mHeader--- > Just (JWTHeader {typ = Just "JWT", cty = Nothing, alg = Just HS256})------ and------ > >>> mClaims--- > Just (JWTClaimsSet {iss = Nothing, sub = Nothing, aud = Nothing,--- >     exp = Nothing, nbf = Nothing, iat = Nothing, jti = Nothing,--- >     unregisteredClaims = fromList [("some",String "payload")]})+-- >>> :{+--  let+--      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text+--      mJwt = decode input+--  in fmap header mJwt+-- :}+-- Just (JWTHeader {typ = Just "JWT", cty = Nothing, alg = Just HS256}) -- -- and ----- > >>> mSignature--- > Nothing+-- >>> :{+--  let+--      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text+--      mJwt = decode input+--  in fmap claims mJwt+-- :}+-- Just (JWTClaimsSet {iss = Nothing, sub = Nothing, aud = Nothing, exp = Nothing, nbf = Nothing, iat = Nothing, jti = Nothing, unregisteredClaims = fromList [("some",String "payload")]}) decode :: JSON -> Maybe (JWT UnverifiedJWT) decode input = do     (h,c) <- extractElems $ T.splitOn "." input@@ -293,24 +286,13 @@ -- This will return a VerifiedJWT if and only if the signature can be verified -- using the given secret. ----- > import qualified Data.Text as T--- > let input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text--- >     mJwt = decodeAndVerifySignature (secret "secret") input--- >     mSignature = join $ fmap signature mJwt------ This yields:------ > >>> mJwt--- > Just (Verified (JWTHeader {typ = Just "JWT", cty = Nothing, alg = Just HS256})--- >    (JWTClaimsSet {iss = Nothing, sub = Nothing, aud = Nothing, exp = Nothing,--- >     nbf = Nothing, iat = Nothing, jti = Nothing,--- >     unregisteredClaims = fromList [("some",String "payload")]})--- >    (Signature "Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U"))------ and------ > >>> mSignature--- > Just (Signature "Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U")+-- >>> :{+--  let+--      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text+--      mJwt = decodeAndVerifySignature (secret "secret") input+--  in join $ fmap signature mJwt+-- :}+-- Just (Signature "Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U") decodeAndVerifySignature :: Secret -> T.Text -> Maybe (JWT VerifiedJWT) decodeAndVerifySignature secret' input = do         (h,c,s) <- extractElems $ T.splitOn "." input@@ -347,7 +329,7 @@ -- String cannot be converted (e.g. if the String contains a ':' but is -- *not* a valid URI). stringOrURI :: T.Text -> Maybe  StringOrURI-stringOrURI t | URI.isURI $ T.unpack t = U <$> (URI.parseURI $ T.unpack t)+stringOrURI t | URI.isURI $ T.unpack t = U <$> URI.parseURI (T.unpack t) stringOrURI t = Just (S t)  -- =================================================================================
tests/src/Web/Base64Tests.hs view
@@ -38,7 +38,7 @@  case_base64EncodeDecodeStringNoPadding = do     let header = "sdjkfhaks jdhfak sjldhfa lkjsdf"-    header @=? (base64Decode $ base64Encode header)+    header @=? base64Decode (base64Encode header)  case_base64DecodeString = do     let str = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"@@ -46,7 +46,7 @@  prop_base64_encode_decode = f     where f :: T.Text -> Bool-          f input = (base64Decode $ base64Encode input) == input+          f input = base64Decode (base64Encode input) == input   instance Arbitrary T.Text where
tests/src/Web/JWTTests.hs view
@@ -16,11 +16,11 @@ import qualified Data.Text             as T import qualified Data.Text.Lazy        as TL import           Data.Aeson.Types-import qualified Data.Aeson            as JSON import           Data.Maybe import           Data.String (fromString, IsString) import           Data.Time + import           Web.JWT  defaultTestGroup :: TestTree@@ -34,22 +34,22 @@ case_stringOrURIString = do     let str = "foo bar baz 2312j!@&^#^*!(*@"         sou = stringOrURI str-    (Just str) @=? (fmap (T.pack . show) sou)+    Just str @=? fmap (T.pack . show) sou  case_stringOrURI= do     let str = "http://user@example.com:8900/foo/bar?baz=t;"         sou = stringOrURI str-    (Just str) @=? (fmap (T.pack . show) sou)+    Just str @=? fmap (T.pack . show) sou  case_decodeJWT = do     -- Generated with ruby-jwt     let input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U"         mJwt = decode input     True @=? isJust mJwt-    True @=? (isJust $ fmap signature mJwt)+    True @=? isJust (fmap signature mJwt)     let (Just unverified) = mJwt-    (Just HS256) @=? (alg $ header unverified)-    (Just "payload") @=? (Map.lookup "some" $ unregisteredClaims $ claims unverified)+    Just HS256 @=? alg (header unverified)+    Just "payload" @=? Map.lookup "some" (unregisteredClaims $ claims unverified)  case_decodeAndVerifyJWT = do     -- Generated with ruby-jwt@@ -57,8 +57,8 @@         mJwt = decodeAndVerifySignature (secret "secret") input     True @=? isJust mJwt     let (Just verified) = mJwt-    (Just HS256) @=? (alg $ header verified)-    (Just "payload") @=? (Map.lookup "some" $ unregisteredClaims $ claims verified)+    Just HS256 @=? alg (header verified)+    Just "payload" @=? Map.lookup "some" (unregisteredClaims $ claims verified)  case_decodeAndVerifyJWTFailing = do     -- Generated with ruby-jwt, modified to be invalid@@ -69,17 +69,17 @@ case_decodeInvalidInput = do     let inputs = ["", "a.", "a.b"]         result = map decode inputs-    True @=? (all isNothing result)+    True @=? all isNothing result  case_decodeAndVerifySignatureInvalidInput = do     let inputs = ["", "a.", "a.b"]         result = map (decodeAndVerifySignature (secret "secret")) inputs-    True @=? (all isNothing result)+    True @=? all isNothing result  case_encodeJWTNoMac = do     let cs = def {         iss = stringOrURI "Foo"-      , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]+      , unregisteredClaims = Map.fromList [("http://example.com/is_root", Bool True)]     }         jwt = encodeUnsigned cs     -- Verified using https://py-jwt-decoder.appspot.com/@@ -88,29 +88,31 @@ case_encodeDecodeJWTNoMac = do     let cs = def {         iss = stringOrURI "Foo"-      , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]+      , unregisteredClaims = Map.fromList [("http://example.com/is_root", Bool True)]     }         mJwt = decode $ encodeUnsigned cs-    True @=? (isJust mJwt)+    True @=? isJust mJwt     let (Just unverified) = mJwt     cs @=? claims unverified  case_encodeDecodeJWT = do-    let cs = def {+    let now = 1394573404+        cs = def {         iss = stringOrURI "Foo"-      , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]+      , iat = intDate now+      , unregisteredClaims = Map.fromList [("http://example.com/is_root", Bool True)]     }         key = secret "secret-key"         mJwt = decode $ encodeSigned HS256 key cs-    True @=? (isJust mJwt)     let (Just unverified) = mJwt     cs @=? claims unverified+    Just now @=? fmap secondsSinceEpoch (iat (claims unverified))  case_tokenIssuer = do     let iss' = stringOrURI "Foo"         cs = def {         iss = iss'-      , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]+      , unregisteredClaims = Map.fromList [("http://example.com/is_root", Bool True)]     }         key = secret "secret-key"         t   = encodeSigned HS256 key cs@@ -129,7 +131,7 @@         cs = def {         iss = stringOrURI "Foo"       , iat = intDate now-      , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]+      , unregisteredClaims = Map.fromList [("http://example.com/is_root", Bool True)]     }     -- The expected string can be decoded using the ruby-jwt library     "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjEyMzQsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlLCJpc3MiOiJGb28ifQ.F3VCSxBBnY2caX4AH4GvIHyTVUhOnJF9Av_G_N4m710" @=? encodeSigned HS256 (secret "secret") cs@@ -138,7 +140,7 @@ prop_stringOrURIProp = f     where f :: StringOrURI -> Bool           f sou = let s = stringOrURI $ T.pack $ show sou-                  in (Just sou) == s+                  in Just sou == s  prop_encode_decode_prop = f     where f :: JWTClaimsSet -> Bool@@ -168,7 +170,7 @@  instance Arbitrary IntDate where     arbitrary = fmap (f . intDate) (arbitrary :: QC.Gen NominalDiffTime)-        where f mIntDate = fromMaybe (fromJust $ intDate 1) mIntDate+        where f = fromMaybe (fromJust $ intDate 1)  instance Arbitrary NominalDiffTime where     arbitrary = arbitrarySizedFractional@@ -177,7 +179,7 @@ instance Arbitrary StringOrURI where     arbitrary = fmap (f . stringOrURI) (arbitrary :: QC.Gen T.Text)         where-            f mSou = fromMaybe (fromJust $ stringOrURI "http://example.com") mSou+            f = fromMaybe (fromJust $ stringOrURI "http://example.com")  instance Arbitrary T.Text where     arbitrary = fromString <$> (arbitrary :: QC.Gen String)