chakra-0.1.1: src/Chakra/JWT.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |Contains JWT authentication settings
module Chakra.JWT where
import Crypto.JOSE (JWK, JWKSet (..))
import Crypto.JWT (StringOrURI, string, uri)
import qualified Data.Aeson as Aeson
import Network.URI (parseURI)
import RIO
import RIO.ByteString (readFile)
import qualified RIO.Text as T
import Servant.Auth.Server (IsMatch (..), JWTSettings (..),
generateKey)
import System.Environment (lookupEnv)
-- |Build JWT settings to be used in Servant Auth context
-- Looks for `JWK_AUDIENCES` and `JWK_PATH` in environment values
-- to load the sig file and value to verify the incoming jwt audience claim
getJWTAuthSettings :: MonadUnliftIO m => m JWTSettings
getJWTAuthSettings = do
jwkSet <- liftIO acquireJwks
signKey <- liftIO generateKey
audienceCfg <- liftIO $ lookupEnv "JWK_AUDIENCES"
let audMatches = maybe (const Matches) checkAud audienceCfg
checkAud audConfig = \tokenAud ->
if RIO.preview uri tokenAud == parseURI audConfig || RIO.preview string tokenAud == Just (T.pack audConfig) then
Matches else DoesNotMatch
return $ buildJWTSettings signKey jwkSet audMatches
buildJWTSettings :: JWK -> JWKSet -> (StringOrURI -> IsMatch) -> JWTSettings
buildJWTSettings signKey jwkSet audMatches =
JWTSettings
{ signingKey = signKey,
jwtAlg = Nothing,
validationKeys = vkeys signKey jwkSet,
audienceMatches = audMatches
}
where
vkeys k (JWKSet x) = JWKSet (x ++ [k])
acquireJwks :: IO JWKSet
acquireJwks = do
envUrl <- lookupEnv "JWK_PATH"
let jwkPath = fromMaybe "secrets/jwk.sig" envUrl
fileContent <- readFile jwkPath
let parsed = Aeson.eitherDecodeStrict fileContent
return $ either (\e -> error $ "Invalid JWK file: " <> e) id parsed