aboutsummaryrefslogtreecommitdiff
path: root/server/lib/Cafp/CookieSocket.hs
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/Cafp/CookieSocket.hs')
-rw-r--r--server/lib/Cafp/CookieSocket.hs86
1 files changed, 0 insertions, 86 deletions
diff --git a/server/lib/Cafp/CookieSocket.hs b/server/lib/Cafp/CookieSocket.hs
deleted file mode 100644
index 5770a3b..0000000
--- a/server/lib/Cafp/CookieSocket.hs
+++ /dev/null
@@ -1,86 +0,0 @@
--- | Allows websockets to reconnect and recover state by storing a cookie client
--- side.
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-module Cafp.CookieSocket
- ( Handle
- , withHandle
- , CookieName
- , acceptRequest
- , persist
- ) where
-
-import Control.Concurrent (threadDelay)
-import qualified Control.Concurrent.Async as Async
-import Control.Concurrent.MVar (MVar)
-import qualified Control.Concurrent.MVar as MVar
-import Control.Monad (forever, guard)
-import Data.Hashable (Hashable)
-import Data.HashMap.Strict (HashMap)
-import qualified Data.HashMap.Strict as HMS
-import Data.Maybe (listToMaybe)
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
-import qualified Data.Time as Time
-import Data.UUID (UUID)
-import qualified Data.UUID as UUID
-import qualified Data.UUID.V4 as UUID.V4
-import qualified Network.WebSockets as WS
-
-type CookieName = T.Text
-
-newtype Secret = Secret UUID deriving (Eq, Hashable)
-
-data Handle v = Handle
- { hMinutes :: Int -- Minutes after which expiry happens
- , hStore :: MVar (HashMap Secret (Time.UTCTime, v))
- }
-
-withHandle :: Int -> (Handle v -> IO a) -> IO a
-withHandle minutes f = do
- store <- MVar.newMVar HMS.empty
- Async.withAsync (reaper store) $ \_ -> f $ Handle minutes store
- where
- -- This is really shitty and we should probably do something with
- -- monotonic time.
- reaper store = forever $ do
- threadDelay $ minutes * 60 * 1000 * 1000
- now <- Time.getCurrentTime
- MVar.modifyMVar_ store $ pure . HMS.filter ((> now) . fst)
-
-parseCookie :: CookieName -> WS.PendingConnection -> Maybe T.Text
-parseCookie name pc = listToMaybe $ do
- (header, values) <- WS.requestHeaders $ WS.pendingRequest pc
- guard $ header == "Cookie"
- part <- T.split (== ';') $ T.decodeUtf8 values
- let (key, val) = T.break (== '=') part
- guard $ T.strip key == name
- guard $ "=" `T.isPrefixOf` val
- pure . T.strip $ T.drop 1 val
-
-makeCookie :: CookieName -> T.Text -> WS.Headers
-makeCookie name val = [("Set-Cookie", T.encodeUtf8 $ name <> "=" <> val)]
-
-acceptRequest
- :: Handle a -> CookieName -> WS.PendingConnection
- -> IO (WS.Connection, Secret, Maybe a)
-acceptRequest h name pc = case parseCookie name pc >>= UUID.fromText of
- Just uuid -> do
- conn <- WS.acceptRequest pc
- store <- MVar.readMVar (hStore h)
- pure (conn, Secret uuid, snd <$> HMS.lookup (Secret uuid) store)
- Nothing -> do
- uuid <- UUID.V4.nextRandom
- conn <- WS.acceptRequestWith pc WS.defaultAcceptRequest
- { WS.acceptHeaders =
- makeCookie name (UUID.toText uuid) <>
- WS.acceptHeaders WS.defaultAcceptRequest
- }
- pure (conn, Secret uuid, Nothing)
-
-persist :: Handle a -> Secret -> a -> IO ()
-persist h key x = MVar.modifyMVar_ (hStore h) $ \store -> do
- expiry <- Time.addUTCTime diffTime <$> Time.getCurrentTime
- pure $ HMS.insert key (expiry, x) store
- where
- diffTime = fromIntegral (60 * hMinutes h)