BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC
EYGIUUQZSCUCLEF6IFIN6RD3TCSOTTSQEQ2SV7OJRVVNFE6NJQPQC
NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC
TLQ72DSJD7GGPWN6HGBHAVPBRQFKEQ6KSK43U7JWWID4ZWAF47JAC
5W5M56VJFJEBXMGBVKGCKPHOEMVTKUOQMLPJP7VNDQLTYNJXXLHQC
64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC
WO2MINIF4TXOHWSE7JWXRZYN64XRVLYIRFMF4SMPSOXKA2V77KMQC
EMVTF2IWNQGRL44FC4JNG5FYYQTZSFPNM6SOM7IAEH6T7PPK2NVAC
IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC
OBFPJS2GHO2PEHBHGEHKIUOUAFIQHPIZXEVD2YIE3ZIE2PVMH5VAC
EQXRXRZDYCM7BDAVBOXQYPG6C7IJT3OFGNIXCDAHJJBRKAXNGL7AC
WZUHEZSBRKHQMNWDKVG4X6DDIQEAXTGI6IGAJ5ERPRQ3W2KUMX4QC
ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC
TNR3TEHKVADAEZSTOD2XLSUTSW5AWST2YUW4CWK5KE7DSC6XHZNAC
W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC
Z3M53KTLZMPOISMHE25SZJSWX5TA37IV33IRE7KNRAD3PKEAEJXQC
64VI73NPSFNWTL6UXM6YHRFLNJZ3NUJ2R3CL53MO2HSZWFGBIRTQC
2Y2QZFVFSKXEFEGYJB5A7GI735ONWPCF7DVTIY5T73AUEVTZTBBQC
RSEB2NFGUBTFESE5BJKDUVQL5Y5ZVGY5O4CJX2LNP63MS3NRHHZQC
TCOAKCGGHOIRJCTZYEZQ3K6KCGL2LGAYGYFRGSPCHBTJJY2V6AXAC
7XN3I3QJHYMKU2DCUXX34WQMSJ4ZJOWW7FME34EANO3E5W4Q632AC
type PQDB = QDB (ReaderT Connection IO)
data App = App
{ _qdb :: Snaplet PQDB
, _sess :: Snaplet SessionManager
, _db :: Snaplet Postgres
, _auth :: Snaplet (AU.AuthManager App)
}
makeLenses ''App
-- | FIXME, make configurable
depf :: DepF
depf = linearDepreciation (Months 6) (Months 60)
qdbpgSnapletInit :: SnapletInit a PQDB
qdbpgSnapletInit = makeSnaplet "qdbpg" "QDB on Postgresql" Nothing $ do
return postgresQDB
requireLogin :: Handler App App a -> Handler App App a
requireLogin = AU.requireUser auth (redirect "/login")
requireUserId :: (UserId -> Handler App App a) -> Handler App App a
requireUserId hf = AU.requireUser auth (redirect "/login") $ do
QDB{..} <- with qdb get
authedUser <- with auth AU.currentUser
qdbUser <- case UserName . AU.unUid <$> (AU.userId =<< authedUser) of
Nothing -> snapError 403 "User is authenticated, but session lacks user identifier"
Just n -> liftPG . runReaderT $ findUserByUserName n
case qdbUser of
Nothing -> snapError 403 "Unable to retrieve user record for authenticated user"
Just u -> hf (u ^. userId)
logWorkHandler :: EventType -> Handler App App ()
logWorkHandler evType = requireUserId $ \uid -> do
QDB{..} <- with qdb get
addrBytes <- getParam "btcAddr"
timestamp <- liftIO getCurrentTime
let workEvent = WorkEvent evType timestamp
storeEv addr = runReaderT . recordEvent uid $ LogEntry addr workEvent
case fmap decodeUtf8 addrBytes >>= parseBtcAddr of
Nothing -> snapError 400 $ "Unable to parse bitcoin address from " <> (tshow addrBytes)
Just addr -> liftPG $ storeEv addr
loggedIntervalsHandler :: Handler App App ()
loggedIntervalsHandler = requireLogin $ do
QDB{..} <- with qdb get
widx <- liftPG $ runReaderT readWorkIndex
modifyResponse $ addHeader "content-type" "application/json"
writeLBS . A.encode $ mapKeys (^. address) widx
payoutsHandler :: Handler App App ()
payoutsHandler = requireLogin $ do
QDB{..} <- with qdb get
ptime <- liftIO $ getCurrentTime
widx <- liftPG $ runReaderT readWorkIndex
modifyResponse $ addHeader "content-type" "application/json"
writeLBS . A.encode . PayoutsResponse $ payouts depf ptime widx
snapError :: MonadSnap m => Int -> Text -> m a
snapError c t = do
modifyResponse $ setResponseStatus c $ encodeUtf8 t
writeText $ ((tshow c) <> " - " <> t)
r <- getResponse
finishWith r
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
module Quixotic.Api.Types where
import ClassyPrelude
import Control.Lens
import Control.Monad.Reader
import Control.Monad.State
import Database.PostgreSQL.Simple
import Quixotic.Database
import Quixotic.Database.PostgreSQL
import Quixotic.TimeLog
import Quixotic.Users
import Snap.Core
import Snap.Snaplet
import Snap.Snaplet.PostgresqlSimple
import qualified Snap.Snaplet.Auth as AU
import Snap.Snaplet.Session
data QModules = QModules
{ _qdb :: QDB (ReaderT Connection IO)
, _depf :: DepF
}
makeLenses ''QModules
data App = App
{ _qm :: Snaplet QModules
, _sess :: Snaplet SessionManager
, _db :: Snaplet Postgres
, _auth :: Snaplet (AU.AuthManager App)
}
makeLenses ''App
instance HasPostgres (Handler b App) where
getPostgresState = with db get
setLocalPostgresState s = local (set (db . snapletValue) s)
-- | FIXME, make configurable
qdbpgSnapletInit :: SnapletInit a QModules
qdbpgSnapletInit = makeSnaplet "qdbpg" "QDB on Postgresql" Nothing $ do
pure $ QModules postgresQDB $ linearDepreciation (Months 6) (Months 60)
requireLogin :: Handler App App a -> Handler App App a
requireLogin = AU.requireUser auth (redirect "/login")
requireUserId :: (UserId -> Handler App App a) -> Handler App App a
requireUserId hf = AU.requireUser auth (redirect "/login") $ do
QDB{..} <- view qdb <$> with qm get
authedUser <- with auth AU.currentUser
qdbUser <- case UserName . AU.unUid <$> (AU.userId =<< authedUser) of
Nothing -> snapError 403 "User is authenticated, but session lacks user identifier"
Just n -> liftPG . runReaderT $ findUserByUserName n
case qdbUser of
Nothing -> snapError 403 "Unable to retrieve user record for authenticated user"
Just u -> hf (u ^. userId)
snapError :: MonadSnap m => Int -> Text -> m a
snapError c t = do
modifyResponse $ setResponseStatus c $ encodeUtf8 t
writeText $ ((tshow c) <> " - " <> t)
getResponse >>= finishWith
ok :: MonadSnap m => m ()
ok = do
modifyResponse $ setResponseCode 200
getResponse >>= finishWith
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
module Quixotic.Api.Users
( loginHandler
, registerHandler
) where
import ClassyPrelude
import Data.ByteString (split)
import Data.Attoparsec.ByteString
import qualified Data.ByteString.Base64 as B64
import Quixotic.Api.Types
import Snap.Core
import Snap.Snaplet
import qualified Snap.Snaplet.Auth as AU
data AuthHeader = AuthHeader Text ByteString
authHeaderParser :: Parser AuthHeader
authHeaderParser = do
let isBase64Char w = (w >= 47 && w <= 57 ) ||
(w >= 64 && w <= 90 ) ||
(w >= 97 && w <= 122) ||
(w == 43 || w == 61 )
b64 <- string "Basic" *> takeWhile1 isBase64Char
decoded <- either fail pure $ B64.decode b64
case split 58 decoded of
(uname : pwd : []) -> pure $ AuthHeader (decodeUtf8 uname) pwd
_ -> fail "Could not unpack auth header into username and password components"
-- data CreateUser = CreateUser
-- { _user :: User
-- , _password :: ByteString
-- }
-- makeLenses ''CreateUser
loginHandler :: (AU.AuthUser -> Handler App App a) -> Handler App App a
loginHandler onSuccess = do
req <- getRequest
rawHeader <- maybe throwChallenge pure $ getHeader "Authorization" req
(AuthHeader uname pwd) <- either (throwDenied . AU.AuthError) pure $ parseOnly authHeaderParser rawHeader
authResult <- with auth $ AU.loginByUsername uname (AU.ClearText pwd) False
either throwDenied onSuccess authResult
registerHandler :: Handler App App ()
registerHandler = ok
throwChallenge :: MonadSnap m => m a
throwChallenge = do
modifyResponse $ (setResponseStatus 401 "Unauthorized") .
(setHeader "WWW-Authenticate" "Basic realm=quixotic")
getResponse >>= finishWith
throwDenied :: MonadSnap m => AU.AuthFailure -> m a
throwDenied failure = do
modifyResponse $ setResponseStatus 403 "Access Denied"
writeText $ "Access Denied: " <> tshow failure
getResponse >>= finishWith
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
module Quixotic.Api where
import ClassyPrelude
import Control.Lens
import Control.Monad.State
import qualified Data.Aeson as A
import Data.Map
import Quixotic
import Quixotic.Database
import Quixotic.Json
import Quixotic.TimeLog
import Quixotic.Api.Types
import Snap.Core
import Snap.Snaplet
import Snap.Snaplet.PostgresqlSimple
logWorkHandler :: EventType -> Handler App App ()
logWorkHandler evType = requireUserId $ \uid -> do
QDB{..} <- view qdb <$> with qm get
addrBytes <- getParam "btcAddr"
timestamp <- liftIO getCurrentTime
let workEvent = WorkEvent evType timestamp
storeEv addr = runReaderT . recordEvent uid $ LogEntry addr workEvent
case fmap decodeUtf8 addrBytes >>= parseBtcAddr of
Nothing -> snapError 400 $ "Unable to parse bitcoin address from " <> (tshow addrBytes)
Just addr -> liftPG $ storeEv addr
loggedIntervalsHandler :: Handler App App ()
loggedIntervalsHandler = requireLogin $ do
QDB{..} <- view qdb <$> with qm get
widx <- liftPG $ runReaderT readWorkIndex
modifyResponse $ addHeader "content-type" "application/json"
writeLBS . A.encode $ mapKeys (^. address) widx
payoutsHandler :: Handler App App ()
payoutsHandler = requireLogin $ do
(QModules QDB{..} df) <- with qm get
ptime <- liftIO $ getCurrentTime
widx <- liftPG $ runReaderT readWorkIndex
modifyResponse $ addHeader "content-type" "application/json"
writeLBS . A.encode . PayoutsResponse $ payouts df ptime widx