{-# LANGUAGE
OverloadedStrings
, ScopedTypeVariables
, FlexibleContexts
#-}
module Main where
import STM.Auth
import STM.Templates
import Network.Wai.Trans
import Network.Wai.Handler.Warp
import Network.Wai.Parse
import Network.HTTP.Types
import Web.Routes.Nested
import Data.Attoparsec.Text
import Text.Regex
import Data.Monoid
import qualified Data.Text.Lazy as LT
import qualified Data.ByteString as BS
import qualified Data.IntMap as IntMap
import Data.ByteArray (convert)
import Control.Concurrent.STM
import Control.Error.Util
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.Trans.Maybe
import Crypto.Random
import Crypto.Hash
data LoginError = InvalidLoginAttempt
defApp :: Application
defApp _ respond = respond $ textOnlyStatus status404 "404 :("
main :: IO ()
main = do
cacheVar <- newTVarIO IntMap.empty
uIdVar <- newTVarIO 0
salt <- do (n :: BS.ByteString) <- liftIO $ getRandomBytes 256
let n' = hash n :: Digest SHA512
n'' = convert n' :: BS.ByteString
return n''
let routedApp app req resp = runReaderT (routeActionAuth authenticate routes app req resp) $ AuthEnv cacheVar uIdVar salt
routes = do
hereAction rootHandle
parent ("foo" </> o_) $ do
hereAction fooHandle
auth ShouldBeLoggedIn unauthHandle DontProtectHere
handleAction ("bar" </> o_) barHandle
handleAction doubleRoute doubleHandle
handleAction emailRoute emailHandle
handleAction ("baz" </> o_) bazHandle
handleAction ("login" </> o_) loginHandle
parent ("logout" </> o_) $ do
hereAction logoutHandle
auth ShouldBeLoggedIn unauthHandle ProtectHere
notFoundAction notFoundHandle
run 3000 $ routedApp $ liftApplication defApp
where
rootHandle = get $ text "Home"
-- `/foo`
fooHandle = get $ text "foo!"
-- `/foo/bar`
barHandle = get $ do
text "bar!"
json ("json bar!" :: LT.Text)
-- `/foo/1234e12`
doubleRoute = p_ ("double", double) </> o_
doubleHandle d = get $ text $ LT.pack (show d) <> " foos"
-- `/athan@foo.com`
emailRoute = r_ ("email", mkRegex "(^[-a-zA-Z0-9_.]+@[-a-zA-Z0-9]+\\.[-a-zA-Z0-9.]+$)") </> o_
emailHandle e = get $ text $ LT.pack (show e) <> " email"
-- `/baz`
bazHandle = do
get $ text "baz!"
let uploader req = do liftIO $ print =<< strictRequestBody req
return undefined
uploadHandle (Left Nothing) = text "Upload Failed"
uploadHandle (Left (Just _)) = text "Should never happen"
uploadHandle (Right _) = text "Woah! Upload content!"
-- Hidden flaw ^ UserSession & LoginError are only options
post uploader uploadHandle
loginHandle = do
get $ lucid loginPage
postReq (login 128) loginResp
where
loginResp _ (Left Nothing) = textStatus status500 "Server malfunction :E"
loginResp _ (Left (Just InvalidLoginAttempt)) = textStatusWith clearSessionResponse status403 "Bad login"
loginResp _ (Right u) = textWith (mapResponseHeaders (++ makeSessionCookies u)) "logged-in"
login s req =
case requestBodyLength req of
KnownLength b | b <= s -> do
(ps,_) <- liftIO $ parseRequestBody (const $ const $ const $ return ()) req
if firstIs (\(x,y) -> BS.null x || BS.null y) ps || length ps < 2
then throwError $ Just InvalidLoginAttempt
else do
mSuccess <- runMaybeT $ do
user <- hoistMaybe $ lookup "user" ps
pass <- hoistMaybe $ lookup "password" ps
liftIO $ putStrLn $ "<~!~> Attempted Login: " ++ show user ++ ", " ++ show pass ++ "\n"
nUserSess <- newUserSession
lift $ writeUserSession nUserSess
return nUserSess
note' (Just InvalidLoginAttempt) mSuccess
_ -> throwError $ Just InvalidLoginAttempt
where
firstIs _ [] = True
firstIs f (x:_) = f x
logoutHandle = getReq $ \req -> do
resp <- case getUserSession $ requestHeaders req of
Nothing -> return "no session :s"
Just userSession -> do
lift $ deleteUserSession (userSessID userSession)
return "logged-out"
textWith clearSessionResponse resp
unauthHandle SessionNotInCache = get $ textStatusWith clearSessionResponse status401 "Unauthorized! - session not in cache"
unauthHandle SessionTimedOut = get $ textStatusWith clearSessionResponse status401 "Unauthorized! - session timed out"
unauthHandle SessionMismatch = get $ textStatusWith clearSessionResponse status401 "Unauthorized! - session mismatch"
unauthHandle InvalidRequestNonce = get $ textStatusWith clearSessionResponse status401 "Unauthorized! - you're doing something sneaky!"
unauthHandle NoSessionHeaders = get $ textStatusWith clearSessionResponse status401 "Unauthorized! - are you sure you're logged in?"
notFoundHandle = get $ textStatus status404 "Not Found :("