-- | This module exports tools for safely storing encrypted data on client-side
-- cookies through "Network.Wai".
--
-- This module is designed to be imported as follows:
--
-- @
-- import qualified "Wai.CryptoCookie"
-- @
--
-- One example of how to obtain a new "Network.Wai".'Network.Wai.Middleware',
--
-- @
-- do (__middleware__, __lookup__) <- do
--       key <- "Wai.CryptoCookie".'autoKeyFileBase16' \"\/run\/my-cookie-encryption-key\"
--       "Wai.CryptoCookie".'middleware' ("Wai.CryptoCookie".'defaultConfig' key)
-- @
--
-- The obtained @__middleware__@ shall be applied to your
-- "Network.Wai".'Wai.Application'.
--
-- The obtained @__lookup__@ function can be used to obtain the 'CryptoCookie'
-- associated with each 'Wai.Request'. It returns 'Nothing' if this particular
-- @__middleware__@ was not used on the given 'Wai.Request'.
--
-- Finally, interact with the 'CryptoCookie' data using 'get' or 'set'.
--
-- == Do I store session data on the client or on the server?
--
-- It's not so much about /where/ to store the session data, but about /how/ to
-- store it and /how/ to expire it.  Here are some ideas. But please, do your
-- own research.
--
-- 1. __Data on server, identifier on both__: In this approach, all the data is
-- stored on the server. On the 'CryptoCookie', simply 'set' a unique session
-- identifier and later 'get' it back and use it to find the associated session
-- data on your server-side database. In order to expire the session, all the
-- server have to do is remove this session identifier from its database.
-- /Choose this option unless you know what you are doing,/
-- /it doesn't require you to plan ahead too much./
--
-- 2. __Data on the client, identifier on both__: In this approach, all the
-- data and session identifier is stored on the 'CryptoCookie'. On your
-- server-side database, store the session identifier and a timestamp
-- representing its creation time or last session activity time.
-- Before accepting the session data from the 'CryptoCookie' as valid, check
-- that the session identifier exists in your database, and that the time since
-- the timestamp is acceptable.  This approach is simpler on your server-side
-- database, but it can lead to more network traffic, and schema migrations for
-- session data will be complex if you care about backwards compatibility
-- with currently active sessions.
--
-- 3. __Everything on the client__: You can store everything in the
-- 'CryptoCookie'.  However, you will be more suceptible to /replay attacks/
-- because you won't have control over session expiration beyond comparing the
-- current time against the session creation timestamp or last activity
-- timestamp previously set in the session data.  You can force all the
-- existing sessions to “expire” by rotating your encryption 'Key'. Also, this
-- approach can lead to more network traffic, and schema migrations for session
-- data will be complex if you care about backwards compatibility with
-- currently active sessions.
module Wai.CryptoCookie
   ( -- * Cookie data
    CryptoCookie
   , get
   , set

    -- * Middleware
   , middleware
   , defaultConfig
   , Config (..)
   , autoKeyFileBase16
   , readKeyFileBase16
   )
where

import Data.Aeson qualified as Ae
import Web.Cookie (SetCookie (..), defaultSetCookie, sameSiteLax)

import Wai.CryptoCookie.Encoding
import Wai.CryptoCookie.Encryption
import Wai.CryptoCookie.Encryption.AEAD_AES_128_GCM_SIV ()
import Wai.CryptoCookie.Encryption.AEAD_AES_256_GCM_SIV ()
import Wai.CryptoCookie.Middleware

-- | Default 'Config':
--
-- * 'Encoding' is 'aeson'.
--
-- * 'Encryption' scheme is the nonce-misuse resistant @AEAD_AES_256_GCM_SIV@
-- as defined in in <https://tools.ietf.org/html/rfc8452 RFC 8452>.
--
--      As an AEAD encryption scheme, you can be confident that a successfully
--      decrypted cookie could only have been encrypted by the same
--      'Key'.  This makes this encryption scheme suitable for
--      storing user session authentication identifiers generated by the server.
--
-- * Cookie name is @SESSION@.
--
--      * @HttpOnly@: yes
--
--      * @Max-Age@: 16 hours
--
--      * @Path@: @\/@
--
--      * @SameSite@: @Lax@
--
--      * @Secure@: yes
--
--      * @Domain@: not set
defaultConfig
   :: (Ae.FromJSON a, Ae.ToJSON a)
   => Key "AEAD_AES_256_GCM_SIV"
   -- ^ Consider using 'autoKeyFileBase16' or
   -- 'readKeyFileBase16' for safely reading a 'Key' from a
   -- 'FilePath'. Alternatively, if you have the base-16 representation of the
   -- 'Key' in JSON configuration, you coulud use
   -- 'Data.Aeson.FromJSON'.
   -> Config a
defaultConfig :: forall a.
(FromJSON a, ToJSON a) =>
Key "AEAD_AES_256_GCM_SIV" -> Config a
defaultConfig Key "AEAD_AES_256_GCM_SIV"
key =
   Config
      { Key "AEAD_AES_256_GCM_SIV"
key :: Key "AEAD_AES_256_GCM_SIV"
key :: Key "AEAD_AES_256_GCM_SIV"
key
      , encoding :: Encoding a
encoding = Encoding a
forall a. (FromJSON a, ToJSON a) => Encoding a
aeson
      , setCookie :: SetCookie
setCookie =
         SetCookie
defaultSetCookie
            { setCookieDomain = Nothing
            , setCookieExpires = Nothing
            , setCookieHttpOnly = True
            , setCookieMaxAge = Just (16 * 60 * 60)
            , setCookieName = "SESSION"
            , setCookiePath = Just "/"
            , setCookieSameSite = Just sameSiteLax
            , setCookieSecure = True
            , setCookieValue = error "setCookieValue"
            }
      }