module Jose.Types
( Jwt (..)
, Jwe
, Jws
, JwtClaims (..)
, JwtHeader (..)
, JwsHeader (..)
, JweHeader (..)
, JwtContent (..)
, JwtEncoding (..)
, JwtError (..)
, IntDate (..)
, Payload (..)
, KeyId (..)
, parseHeader
, encodeHeader
, defJwsHdr
, defJweHdr
)
where
import Control.Applicative
import Data.Aeson
import Data.Aeson.Types
import Data.Char (toUpper, toLower)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as H
import Data.Int (Int64)
import Data.Time.Clock (UTCTime)
import Data.Time.Clock.POSIX
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Data.Vector (singleton)
import GHC.Generics
import Jose.Jwa (JweAlg(..), JwsAlg (..), Enc(..))
newtype Jwt = Jwt { unJwt :: ByteString } deriving (Show, Eq)
data Payload = Nested Jwt
| Claims ByteString
deriving (Show, Eq)
type Jws = (JwsHeader, ByteString)
type Jwe = (JweHeader, ByteString)
data JwtContent = Unsecured !ByteString | Jws !Jws | Jwe !Jwe deriving (Show, Eq)
data JwtEncoding
= JwsEncoding JwsAlg
| JweEncoding JweAlg Enc
deriving (Eq, Show)
data JwtHeader = JweH JweHeader
| JwsH JwsHeader
| UnsecuredH
deriving (Show)
data KeyId
= KeyId Text
| UTCKeyId UTCTime
deriving (Eq, Show, Ord)
instance ToJSON KeyId
where
toJSON (KeyId t) = toJSON t
toJSON (UTCKeyId t) = toJSON t
instance FromJSON KeyId
where
parseJSON = withText "KeyId" $ \t -> do
let asTime = fromJSON (String t) :: Result UTCTime
case asTime of
Success d -> pure (UTCKeyId d)
_ -> pure (KeyId t)
data JwsHeader = JwsHeader {
jwsAlg :: JwsAlg
, jwsTyp :: Maybe Text
, jwsCty :: Maybe Text
, jwsKid :: Maybe KeyId
} deriving (Eq, Show, Generic)
data JweHeader = JweHeader {
jweAlg :: JweAlg
, jweEnc :: Enc
, jweTyp :: Maybe Text
, jweCty :: Maybe Text
, jweZip :: Maybe Text
, jweKid :: Maybe KeyId
} deriving (Eq, Show, Generic)
newtype IntDate = IntDate POSIXTime deriving (Show, Eq, Ord)
instance FromJSON IntDate where
parseJSON = withScientific "IntDate" $ \n ->
pure . IntDate . fromIntegral $ (round n :: Int64)
instance ToJSON IntDate where
toJSON (IntDate t) = Number $ fromIntegral (round t :: Int64)
data JwtClaims = JwtClaims
{ jwtIss :: !(Maybe Text)
, jwtSub :: !(Maybe Text)
, jwtAud :: !(Maybe [Text])
, jwtExp :: !(Maybe IntDate)
, jwtNbf :: !(Maybe IntDate)
, jwtIat :: !(Maybe IntDate)
, jwtJti :: !(Maybe Text)
} deriving (Show, Generic)
instance FromJSON JwtClaims where
parseJSON v@(Object o) = case H.lookup "aud" o of
Just (a@(String _)) -> genericParseJSON claimsOptions $ Object $ H.insert "aud" (Array $ singleton a) o
_ -> genericParseJSON claimsOptions v
parseJSON _ = fail "JwtClaims must be an object"
instance ToJSON JwtClaims where
toJSON = genericToJSON claimsOptions
instance ToJSON Jwt where
toJSON (Jwt bytes) = String (TE.decodeUtf8 bytes)
instance FromJSON Jwt where
parseJSON (String token) = pure $ Jwt (TE.encodeUtf8 token)
parseJSON _ = fail "Jwt must be a string"
claimsOptions :: Options
claimsOptions = prefixOptions "jwt"
defJwsHdr :: JwsHeader
defJwsHdr = JwsHeader RS256 Nothing Nothing Nothing
defJweHdr :: JweHeader
defJweHdr = JweHeader RSA_OAEP A128GCM Nothing Nothing Nothing Nothing
data JwtError = KeyError Text
| BadAlgorithm Text
| BadDots Int
| BadHeader Text
| BadClaims
| BadSignature
| BadCrypto
| Base64Error String
deriving (Eq, Show)
instance ToJSON JwsHeader where
toJSON = genericToJSON jwsOptions
instance FromJSON JwsHeader where
parseJSON = genericParseJSON jwsOptions
instance ToJSON JweHeader where
toJSON = genericToJSON jweOptions
instance FromJSON JweHeader where
parseJSON = genericParseJSON jweOptions
instance FromJSON JwtHeader where
parseJSON v@(Object o) = case H.lookup "alg" o of
Just (String "none") -> pure UnsecuredH
_ -> case H.lookup "enc" o of
Nothing -> JwsH <$> parseJSON v
_ -> JweH <$> parseJSON v
parseJSON _ = fail "JwtHeader must be an object"
encodeHeader :: ToJSON a => a -> ByteString
encodeHeader h = BL.toStrict $ encode h
parseHeader :: ByteString -> Either JwtError JwtHeader
parseHeader hdr = either (Left . BadHeader . T.pack) return $ eitherDecodeStrict' hdr
jwsOptions :: Options
jwsOptions = prefixOptions "jws"
jweOptions :: Options
jweOptions = prefixOptions "jwe"
prefixOptions :: String -> Options
prefixOptions prefix = omitNothingOptions
{ fieldLabelModifier = dropPrefix $ length prefix
, constructorTagModifier = addPrefix prefix
}
where
omitNothingOptions = defaultOptions { omitNothingFields = True }
dropPrefix l s = let remainder = drop l s
in (toLower . head) remainder : tail remainder
addPrefix p s = p ++ toUpper (head s) : tail s