packages feed

cj-token (empty) → 0.0.0

raw patch · 9 files changed

+299/−0 lines, 9 filesdep +QuickCheckdep +aesondep +basesetup-changed

Dependencies added: QuickCheck, aeson, base, base64-bytestring, cj-token, containers, either, hspec, jwt, text, text-conversions, time

Files

+ README.md view
@@ -0,0 +1,3 @@+# verify++A module to verify CJ tokens
+ Setup.hs view
@@ -0,0 +1,6 @@+-- This script is used to build and install your package. Typically you don't+-- need to change it. The Cabal documentation has more information about this+-- file: <https://www.haskell.org/cabal/users-guide/installing-packages.html>.+import qualified Distribution.Simple+main :: IO ()+main = Distribution.Simple.defaultMain
+ cj-token.cabal view
@@ -0,0 +1,68 @@+-- This file has been generated from package.yaml by hpack version 0.15.0.+--+-- see: https://github.com/sol/hpack++name:           cj-token+version:        0.0.0+synopsis:       A new Haskeleton package.+description:    cj-token is a new Haskeleton package.+category:       Other+maintainer:     Author name here+license:        ISC+build-type:     Simple+cabal-version:  >= 1.10++extra-source-files:+    package.yaml+    README.md+    stack.yaml++library+  hs-source-dirs:+      library+  default-extensions: ConstraintKinds DeriveGeneric DuplicateRecordFields FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving KindSignatures LambdaCase MultiParamTypeClasses NamedFieldPuns OverloadedStrings RankNTypes ScopedTypeVariables TypeApplications TypeOperators+  ghc-options: -Wall+  build-depends:+      aeson < 2+    , base < 5+    , base64-bytestring < 2+    , containers < 1+    , either < 5+    , jwt < 1+    , text < 2+    , time < 2+    , text-conversions < 1+  exposed-modules:+      CJ.Auth.Token+  default-language: Haskell2010++executable cj-token+  main-is: Main.hs+  hs-source-dirs:+      executable+  default-extensions: ConstraintKinds DeriveGeneric DuplicateRecordFields FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving KindSignatures LambdaCase MultiParamTypeClasses NamedFieldPuns OverloadedStrings RankNTypes ScopedTypeVariables TypeApplications TypeOperators+  ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N+  build-depends:+      base+    , cj-token+  default-language: Haskell2010++test-suite cj-token-test-suite+  type: exitcode-stdio-1.0+  main-is: Main.hs+  hs-source-dirs:+      test-suite+  default-extensions: ConstraintKinds DeriveGeneric DuplicateRecordFields FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving KindSignatures LambdaCase MultiParamTypeClasses NamedFieldPuns OverloadedStrings RankNTypes ScopedTypeVariables TypeApplications TypeOperators+  ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N+  build-depends:+      base+    , jwt+    , hspec+    , time+    , text+    , text-conversions+    , QuickCheck+    , cj-token+  other-modules:+      CJ.Auth.TokenSpec+  default-language: Haskell2010
+ executable/Main.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = return ()
+ library/CJ/Auth/Token.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE OverloadedStrings #-}+module CJ.Auth.Token+  ( UserBearerToken(..)+  , JSONToken(..)+  , decodeToken+  , encodeToken+  ) where++import qualified Data.ByteString.Base64.URL as BS64+import qualified Data.Map as M+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Web.JWT as JWT+import Control.Monad+import Data.Aeson (FromJSON (..), Value, toJSON)+import Data.Aeson.Types (parseMaybe)+import Data.Either.Combinators+import Data.Maybe+import Data.Time.Clock.POSIX (POSIXTime)+import Web.JWT+  ( Algorithm(HS256)+  , JWTClaimsSet(JWTClaimsSet)+  , Secret+  , claims+  , decodeAndVerifySignature+  , def+  , encodeSigned+  , numericDate+  , secondsSinceEpoch+  , unregisteredClaims+  )++class JSONToken a where+  toClaims :: a -> JWTClaimsSet+  fromClaims :: JWTClaimsSet -> Maybe a++data UserBearerToken = UserBearerToken+  { _bearerUserId :: T.Text+  , _bearerAppId :: T.Text+  } deriving (Eq, Show)++instance JSONToken UserBearerToken where+  toClaims UserBearerToken{ _bearerUserId = uid, _bearerAppId = aid } =+    def { unregisteredClaims = M.fromList [ ("userId", toJSON uid)+                                          , ("appId",  toJSON aid) ] }++  fromClaims JWTClaimsSet{ unregisteredClaims = clms } =+      UserBearerToken <$> uid <*> aid+    where uid = fromValue =<< M.lookup "userId" clms+          aid = fromValue =<< M.lookup "appId"  clms+++fromValue :: FromJSON a => Value -> Maybe a+fromValue = parseMaybe parseJSON++encodeJWT :: Secret -> JWTClaimsSet -> T.Text+encodeJWT = encodeSigned HS256++decodableToken :: T.Text -> Bool+decodableToken token = isJust $ do+    (header : claimsPayload : _) <- return $ T.splitOn "." token+    void $ verifyUtf8Base64 header+    verifyUtf8Base64 claimsPayload+  where+    verifyUtf8Base64 base64Str = rightToMaybe $ TE.decodeUtf8' (BS64.decodeLenient $ TE.encodeUtf8 base64Str)++encodeToken :: (JSONToken a) => Secret -> a -> POSIXTime -> T.Text+encodeToken secret token expTime = encodeJWT secret expiringClaims+  where baseClaims = toClaims token+        expiringClaims = baseClaims { JWT.exp = numericDate expTime }++decodeToken :: (JSONToken a) => Secret -> T.Text -> POSIXTime -> Maybe a+decodeToken secret token currentTime =+    fromClaims =<< verifyFresh currentTime =<< decodedClaims+  where jwt = if decodableToken token then decodeAndVerifySignature secret token else Nothing+        decodedClaims = claims <$> jwt++verifyFresh :: POSIXTime -> JWTClaimsSet -> Maybe JWTClaimsSet+verifyFresh currentTime clms@JWTClaimsSet{ JWT.exp = (Just expirationTime) }+  | secondsSinceEpoch expirationTime < currentTime = Nothing+  | otherwise                                      = Just clms+verifyFresh _ clms = Just clms
+ package.yaml view
@@ -0,0 +1,70 @@+name: cj-token+version: 0.0.0+category: Other+synopsis: A new Haskeleton package.+description: cj-token is a new Haskeleton package.+maintainer: Author name here+license: ISC+extra-source-files:+- package.yaml+- README.md+- stack.yaml+ghc-options: -Wall+default-extensions:+- ConstraintKinds+- DeriveGeneric+- DuplicateRecordFields+- FlexibleContexts+- FlexibleInstances+- GADTs+- GeneralizedNewtypeDeriving+- KindSignatures+- LambdaCase+- MultiParamTypeClasses+- NamedFieldPuns+- OverloadedStrings+- RankNTypes+- ScopedTypeVariables+- TypeApplications+- TypeOperators+library:+  dependencies:+  - aeson < 2+  - base < 5+  - base64-bytestring < 2+  - containers < 1+  - either < 5+  - jwt < 1+  - text < 2+  - time < 2+  - text-conversions < 1+  source-dirs: library+executables:+  cj-token:+    dependencies:+    - base+    - cj-token+    ghc-options:+    - -rtsopts+    - -threaded+    - -with-rtsopts=-N+    main: Main.hs+    source-dirs: executable+tests:+  cj-token-test-suite:+    dependencies:+    - base+    - jwt+    - hspec+    - time+    - text+    - text-conversions+    - QuickCheck+    - cj-token++    ghc-options:+    - -rtsopts+    - -threaded+    - -with-rtsopts=-N+    main: Main.hs+    source-dirs: test-suite
+ stack.yaml view
@@ -0,0 +1,11 @@++resolver: lts-8.11++packages:+- '.'++extra-deps: []++flags: {}++extra-package-dbs: []
+ test-suite/CJ/Auth/TokenSpec.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module CJ.Auth.TokenSpec (spec) where++import Test.Hspec+import Test.QuickCheck++import qualified Web.JWT as JWT+import qualified Data.Text as T+import Data.String (fromString)+import CJ.Auth.Token++instance Arbitrary T.Text where+  arbitrary = fromString <$> arbitrary++spec :: Spec+spec = do+  describe "UserBearerToken" $ do+    describe "encodeToken / decodeToken" $+      it "decode . encode = id" $+        property $ \x y ->+          let token = UserBearerToken{ _bearerUserId = x+                                 , _bearerAppId  = y }+              secret = JWT.secret ""+              encoded = encodeToken secret token 100+              decoded = decodeToken secret encoded 0+          in decoded == Just token++    describe "decodeToken" $+      it "returns Nothing if the token is expired" $+        property $ \x y ->+          let token = UserBearerToken{ _bearerUserId = x, _bearerAppId = y }+              secret = JWT.secret ""+              time = 0+              encoded = encodeToken secret token time+              decoded = decodeToken secret encoded 100 :: (Maybe UserBearerToken)+          in decoded `shouldBe` Nothing+++  describe "decodeToken" $+    it "returns Nothing if token is garbage" $ do+      shouldBe (decodeToken (JWT.secret "secret") "" 123) (Nothing :: Maybe TClaim)+      shouldBe (decodeToken (JWT.secret "secret") "xyz.abc.123" 123) (Nothing :: Maybe TClaim)+      shouldBe (decodeToken (JWT.secret "secret") "ybGcJ9.eyJzWV9.TJVAFh7HgQ" 123) (Nothing :: Maybe TClaim)+      shouldBe (decodeToken (JWT.secret "secret") "eyJvbmUiOiIxIiwiYWxnIjoiSFMyNTYifQ" 123) (Nothing :: Maybe TClaim)+      shouldBe (decodeToken (JWT.secret "secret") "eyJvbmUiOiIxIiwiYWxnIjoiSFMyNTYifQ.eyJvbmUiOiIxIiwiYWxnIjoiSFMyNTYifQ" 123) (Nothing :: Maybe TClaim)++data TClaim = TClaim+  deriving (Eq, Show)++instance JSONToken TClaim where+  toClaims _ = JWT.def+  fromClaims _ = Just TClaim
+ test-suite/Main.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}