{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE NoFieldSelectors #-}
module Wai.CryptoCookie
(
defaultConfig
, Config (..)
, Env
, newEnv
, middleware
, msgFromRequestCookie
, setCookie
, expireCookie
, Encryption (..)
, autoKeyFileBase16
, readKeyFileBase16
, readKeyFile
, writeKeyFile
) where
import Control.Monad.IO.Class
import Data.Aeson qualified as Ae
import Data.ByteArray.Encoding qualified as BA
import Data.ByteArray.Sized qualified as BAS
import Data.ByteString qualified as B
import Data.ByteString.Lazy qualified as BL
import Data.IORef
import Data.Kind (Type)
import Data.Time.Clock.POSIX qualified as Time
import Network.Wai qualified as Wai
import Wai.CSRF qualified
import Wai.CryptoCookie.Encryption
import Wai.CryptoCookie.Encryption.AEAD_AES_128_GCM_SIV ()
import Wai.CryptoCookie.Encryption.AEAD_AES_256_GCM_SIV ()
import Web.Cookie qualified as WC
defaultConfig
:: (Ae.ToJSON msg, Ae.FromJSON msg)
=> Key "AEAD_AES_256_GCM_SIV"
-> Config Wai.CSRF.Token msg
defaultConfig :: forall msg.
(ToJSON msg, FromJSON msg) =>
Key "AEAD_AES_256_GCM_SIV" -> Config Token msg
defaultConfig Key "AEAD_AES_256_GCM_SIV"
key =
Config
{ cookieName :: ByteString
cookieName = ByteString
"SESSION"
, Key "AEAD_AES_256_GCM_SIV"
key :: Key "AEAD_AES_256_GCM_SIV"
key :: Key "AEAD_AES_256_GCM_SIV"
key
, aadEncode :: Token -> ByteString
aadEncode = \(Wai.CSRF.Token SizedByteArray 32 ByteString
t) ->
ByteString -> ByteString
BL.fromStrict (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ SizedByteArray 32 ByteString -> ByteString
forall (n :: Nat) ba. SizedByteArray n ba -> ba
BAS.unSizedByteArray SizedByteArray 32 ByteString
t
, msgEncode :: msg -> ByteString
msgEncode = msg -> ByteString
forall a. ToJSON a => a -> ByteString
Ae.encode
, msgDecode :: ByteString -> Maybe msg
msgDecode = ByteString -> Maybe msg
forall a. FromJSON a => ByteString -> Maybe a
Ae.decode
}
data Config (aad :: Type) (msg :: Type) = forall e.
(Encryption e) =>
Config
{ forall aad msg. Config aad msg -> ByteString
cookieName :: B.ByteString
, ()
key :: Key e
, forall aad msg. Config aad msg -> aad -> ByteString
aadEncode :: aad -> BL.ByteString
, forall aad msg. Config aad msg -> msg -> ByteString
msgEncode :: msg -> BL.ByteString
, forall aad msg. Config aad msg -> ByteString -> Maybe msg
msgDecode :: BL.ByteString -> Maybe msg
}
data Env (aad :: Type) (msg :: Type) = Env
{ forall aad msg. Env aad msg -> ByteString
cookieName :: B.ByteString
, forall aad msg. Env aad msg -> aad -> msg -> IO ByteString
encodeEncrypt :: aad -> msg -> IO BL.ByteString
, forall aad msg. Env aad msg -> aad -> ByteString -> Maybe msg
decryptDecode :: aad -> BL.ByteString -> Maybe msg
}
newEnv :: (MonadIO m) => Config aad msg -> m (Env aad msg)
newEnv :: forall (m :: * -> *) aad msg.
MonadIO m =>
Config aad msg -> m (Env aad msg)
newEnv c :: Config aad msg
c@Config{Key e
key :: ()
key :: Key e
key} = IO (Env aad msg) -> m (Env aad msg)
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO do
let dc :: Decrypt e
dc = Key e -> Decrypt e
forall k (e :: k). Encryption e => Key e -> Decrypt e
initDecrypt Key e
key
IORef (Encrypt e)
ecRef <- Encrypt e -> IO (IORef (Encrypt e))
forall a. a -> IO (IORef a)
newIORef (Encrypt e -> IO (IORef (Encrypt e)))
-> IO (Encrypt e) -> IO (IORef (Encrypt e))
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Key e -> IO (Encrypt e)
forall k (e :: k) (m :: * -> *).
(Encryption e, MonadRandom m) =>
Key e -> m (Encrypt e)
forall (m :: * -> *). MonadRandom m => Key e -> m (Encrypt e)
initEncrypt Key e
key
Env aad msg -> IO (Env aad msg)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
Env
{ encodeEncrypt :: aad -> msg -> IO ByteString
encodeEncrypt = \aad
aad0 msg
msg0 -> do
let !ByteString
aad1 :: BL.ByteString = Config aad msg
c.aadEncode aad
aad0
!ByteString
msg1 :: BL.ByteString = Config aad msg
c.msgEncode msg
msg0
Encrypt e
ec <- IORef (Encrypt e)
-> (Encrypt e -> (Encrypt e, Encrypt e)) -> IO (Encrypt e)
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' IORef (Encrypt e)
ecRef \Encrypt e
ec -> (Encrypt e -> Encrypt e
forall k (e :: k). Encryption e => Encrypt e -> Encrypt e
advance Encrypt e
ec, Encrypt e
ec)
ByteString -> IO ByteString
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ByteString -> IO ByteString) -> ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ Encrypt e -> ByteString -> ByteString -> ByteString
forall k (e :: k).
Encryption e =>
Encrypt e -> ByteString -> ByteString -> ByteString
encrypt Encrypt e
ec ByteString
aad1 ByteString
msg1
, decryptDecode :: aad -> ByteString -> Maybe msg
decryptDecode = \aad
aad0 !ByteString
cry -> do
let !aad1 :: ByteString
aad1 = Config aad msg
c.aadEncode aad
aad0
case Decrypt e -> ByteString -> ByteString -> Either String ByteString
forall k (e :: k).
Encryption e =>
Decrypt e -> ByteString -> ByteString -> Either String ByteString
decrypt Decrypt e
dc ByteString
aad1 ByteString
cry of
Right ByteString
msg -> Config aad msg
c.msgDecode ByteString
msg
Either String ByteString
_ -> Maybe msg
forall a. Maybe a
Nothing
, cookieName :: ByteString
cookieName = Config aad msg
c.cookieName
}
middleware
:: Env aad msg
-> (Maybe (aad, Maybe msg) -> Wai.Application)
-> Maybe aad
-> Wai.Application
middleware :: forall aad msg.
Env aad msg
-> (Maybe (aad, Maybe msg) -> Application)
-> Maybe aad
-> Application
middleware Env aad msg
env Maybe (aad, Maybe msg) -> Application
fapp Maybe aad
yaad Request
req Response -> IO ResponseReceived
respond = do
let ymsg :: Maybe msg
ymsg = Env aad msg -> Request -> aad -> Maybe msg
forall aad msg. Env aad msg -> Request -> aad -> Maybe msg
msgFromRequestCookie Env aad msg
env Request
req (aad -> Maybe msg) -> Maybe aad -> Maybe msg
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Maybe aad
yaad
Maybe (aad, Maybe msg) -> Application
fapp ((aad -> (aad, Maybe msg)) -> Maybe aad -> Maybe (aad, Maybe msg)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (,Maybe msg
ymsg) Maybe aad
yaad) Request
req Response -> IO ResponseReceived
respond
msgFromRequestCookie :: Env aad msg -> Wai.Request -> aad -> Maybe msg
msgFromRequestCookie :: forall aad msg. Env aad msg -> Request -> aad -> Maybe msg
msgFromRequestCookie Env aad msg
env Request
r aad
aad = do
[ByteString
d64] <- [ByteString] -> Maybe [ByteString]
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([ByteString] -> Maybe [ByteString])
-> [ByteString] -> Maybe [ByteString]
forall a b. (a -> b) -> a -> b
$ ByteString -> [(ByteString, ByteString)] -> [ByteString]
forall k v. Eq k => k -> [(k, v)] -> [v]
lookupMany Env aad msg
env.cookieName ([(ByteString, ByteString)] -> [ByteString])
-> [(ByteString, ByteString)] -> [ByteString]
forall a b. (a -> b) -> a -> b
$ Request -> [(ByteString, ByteString)]
requestCookies Request
r
case Base -> ByteString -> Either String ByteString
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
Base -> bin -> Either String bout
BA.convertFromBase Base
BA.Base64URLUnpadded ByteString
d64 of
Right ByteString
cry -> Env aad msg
env.decryptDecode aad
aad (ByteString -> Maybe msg) -> ByteString -> Maybe msg
forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
BL.fromStrict ByteString
cry
Left String
_ -> Maybe msg
forall a. Maybe a
Nothing
setCookie :: (MonadIO m) => Env aad msg -> aad -> msg -> m WC.SetCookie
setCookie :: forall (m :: * -> *) aad msg.
MonadIO m =>
Env aad msg -> aad -> msg -> m SetCookie
setCookie Env aad msg
env aad
aad msg
msg = IO SetCookie -> m SetCookie
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO do
ByteString
cry <- Env aad msg
env.encodeEncrypt aad
aad msg
msg
SetCookie -> IO SetCookie
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SetCookie -> IO SetCookie) -> SetCookie -> IO SetCookie
forall a b. (a -> b) -> a -> b
$
(Env aad msg -> SetCookie
forall aad msg. Env aad msg -> SetCookie
expireCookie Env aad msg
env)
{ WC.setCookieExpires = Nothing
, WC.setCookieMaxAge = Nothing
, WC.setCookieValue =
BA.convertToBase BA.Base64URLUnpadded $ BL.toStrict cry
}
expireCookie :: Env aad msg -> WC.SetCookie
expireCookie :: forall aad msg. Env aad msg -> SetCookie
expireCookie Env aad msg
env =
SetCookie
WC.defaultSetCookie
{ WC.setCookieDomain = Nothing
, WC.setCookieExpires = Just (Time.posixSecondsToUTCTime 0)
, WC.setCookieHttpOnly = True
, WC.setCookieMaxAge = Just (negate 1)
, WC.setCookieName = env.cookieName
, WC.setCookiePath = Just "/"
, WC.setCookieSameSite = Just WC.sameSiteLax
, WC.setCookieSecure = True
, WC.setCookieValue = ""
}
requestCookies :: Wai.Request -> [(B.ByteString, B.ByteString)]
requestCookies :: Request -> [(ByteString, ByteString)]
requestCookies Request
r =
ByteString -> [(ByteString, ByteString)]
WC.parseCookies (ByteString -> [(ByteString, ByteString)])
-> [ByteString] -> [(ByteString, ByteString)]
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< HeaderName -> [(HeaderName, ByteString)] -> [ByteString]
forall k v. Eq k => k -> [(k, v)] -> [v]
lookupMany HeaderName
"Cookie" (Request -> [(HeaderName, ByteString)]
Wai.requestHeaders Request
r)
lookupMany :: (Eq k) => k -> [(k, v)] -> [v]
lookupMany :: forall k v. Eq k => k -> [(k, v)] -> [v]
lookupMany k
k = (k -> Bool) -> [(k, v)] -> [v]
forall k v. Eq k => (k -> Bool) -> [(k, v)] -> [v]
findMany (k -> k -> Bool
forall a. Eq a => a -> a -> Bool
== k
k)
findMany :: (Eq k) => (k -> Bool) -> [(k, v)] -> [v]
findMany :: forall k v. Eq k => (k -> Bool) -> [(k, v)] -> [v]
findMany k -> Bool
f = ((k, v) -> v) -> [(k, v)] -> [v]
forall a b. (a -> b) -> [a] -> [b]
map (k, v) -> v
forall a b. (a, b) -> b
snd ([(k, v)] -> [v]) -> ([(k, v)] -> [(k, v)]) -> [(k, v)] -> [v]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((k, v) -> Bool) -> [(k, v)] -> [(k, v)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(k
a, v
_) -> k -> Bool
f k
a)