{-#LANGUAGE NoImplicitPrelude #-}
{-#LANGUAGE OverloadedStrings #-}
{-#LANGUAGE OverloadedLists #-}
{-#LANGUAGE LambdaCase #-}
{-#LANGUAGE ScopedTypeVariables #-}
{-#LANGUAGE FlexibleInstances #-}
{-#LANGUAGE FlexibleContexts #-}
{-#LANGUAGE MultiParamTypeClasses #-}
module Web.Sprinkles.Sessions
( SessionHandle
, sessionID
, sessionGet
, sessionPut
, resumeSession
, newSession
, setSessionCookie
)
where

import Web.Sprinkles.Prelude
import Web.Sprinkles.SessionStore
import Web.Sprinkles.SessionHandle
import Web.Sprinkles.Logger as Logger
import Web.Sprinkles.Project
import Web.Sprinkles.ProjectConfig
import Web.Sprinkles.ServerConfig
import Network.Wai
import qualified Data.ByteString.Char8 as Char8
import Data.Char (isSpace)
import qualified Crypto.Nonce as Nonce
import Data.RandomString (randomStr)

setSessionCookie :: Project -> Request -> SessionHandle -> Response -> Response
setSessionCookie project request session =
    let sessionConfig = projectSessionConfig project
        cookieValue = mconcat [ (sessCookieName sessionConfig)
                              , "="
                              , (sessionID session)
                              ]
        cookieHeader = ("Set-Cookie", cookieValue)
    in mapResponseHeaders (cookieHeader:)


resumeSession :: Project -> Request -> IO (Maybe SessionHandle)
resumeSession project request = do
    let sessionConfig = projectSessionConfig project
        sessionStore = projectSessionStore project
    let cookieHeaderMay = lookup "Cookie" (requestHeaders request)
    maybe (return Nothing) (loadSession sessionStore) $
        cookieHeaderMay >>=
            parseCookieHeader >>=
            lookup (sessCookieName sessionConfig)

verifyCSRF :: Project -> Request -> IO Bool
verifyCSRF project request = do
    let sessionConfig = projectSessionConfig project
        sessionStore = projectSessionStore project
    resumeSession project request >>= \case
        Nothing ->
            return False
        Just handle -> do
            let csrfHeaderMay = decodeUtf8 <$> lookup "X-Form-Token" (requestHeaders request)
            csrfTokenMay <- sessionGet handle "csrf"
            return $
                (csrfHeaderMay == csrfTokenMay) &&
                isJust csrfHeaderMay

newSession :: Project -> Request -> IO (Maybe SessionHandle)
newSession project request = do
    ssid <- Nonce.new >>= Nonce.nonce128url
    csrfToken <- Nonce.new >>= Nonce.nonce128url
    let sessionConfig = projectSessionConfig project
        sessionStore = projectSessionStore project
    ssCreateSession sessionStore ssid NeverExpires
    let handle = makeSessionHandle sessionStore ssid
    sessionPut handle "csrf" . decodeUtf8 $ csrfToken
    return . Just $ handle

parseCookieHeader :: ByteString -> Maybe [(ByteString, ByteString)]
parseCookieHeader headerVal =
    let parts = map (Char8.dropWhile isSpace) $
                    Char8.split ';' headerVal
        splitOnce sep str =
            let (a, b) = Char8.break (== sep) str
            in (a, Char8.drop 1 b)
    in Just (map (splitOnce '=') parts)

loadSession :: SessionStore -> ByteString -> IO (Maybe SessionHandle)
loadSession ss ssid = do
    exists <- ssDoesSessionExist ss ssid
    if exists
        then
            return . Just $ makeSessionHandle ss ssid
        else
            return Nothing