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 configurabledepf :: DepFdepf = linearDepreciation (Months 6) (Months 60)qdbpgSnapletInit :: SnapletInit a PQDBqdbpgSnapletInit = makeSnaplet "qdbpg" "QDB on Postgresql" Nothing $ doreturn postgresQDBrequireLogin :: Handler App App a -> Handler App App arequireLogin = AU.requireUser auth (redirect "/login")requireUserId :: (UserId -> Handler App App a) -> Handler App App arequireUserId hf = AU.requireUser auth (redirect "/login") $ doQDB{..} <- with qdb getauthedUser <- with auth AU.currentUserqdbUser <- case UserName . AU.unUid <$> (AU.userId =<< authedUser) ofNothing -> snapError 403 "User is authenticated, but session lacks user identifier"Just n -> liftPG . runReaderT $ findUserByUserName ncase qdbUser ofNothing -> snapError 403 "Unable to retrieve user record for authenticated user"Just u -> hf (u ^. userId)logWorkHandler :: EventType -> Handler App App ()logWorkHandler evType = requireUserId $ \uid -> doQDB{..} <- with qdb getaddrBytes <- getParam "btcAddr"timestamp <- liftIO getCurrentTimelet workEvent = WorkEvent evType timestampstoreEv addr = runReaderT . recordEvent uid $ LogEntry addr workEventcase fmap decodeUtf8 addrBytes >>= parseBtcAddr ofNothing -> snapError 400 $ "Unable to parse bitcoin address from " <> (tshow addrBytes)Just addr -> liftPG $ storeEv addrloggedIntervalsHandler :: Handler App App ()loggedIntervalsHandler = requireLogin $ doQDB{..} <- with qdb getwidx <- liftPG $ runReaderT readWorkIndexmodifyResponse $ addHeader "content-type" "application/json"writeLBS . A.encode $ mapKeys (^. address) widxpayoutsHandler :: Handler App App ()payoutsHandler = requireLogin $ doQDB{..} <- with qdb getptime <- liftIO $ getCurrentTimewidx <- liftPG $ runReaderT readWorkIndexmodifyResponse $ addHeader "content-type" "application/json"writeLBS . A.encode . PayoutsResponse $ payouts depf ptime widxsnapError :: MonadSnap m => Int -> Text -> m asnapError c t = domodifyResponse $ setResponseStatus c $ encodeUtf8 twriteText $ ((tshow c) <> " - " <> t)r <- getResponsefinishWith r
{-# LANGUAGE FlexibleInstances #-}{-# LANGUAGE TemplateHaskell #-}module Quixotic.Api.Types whereimport ClassyPreludeimport Control.Lensimport Control.Monad.Readerimport Control.Monad.Stateimport Database.PostgreSQL.Simpleimport Quixotic.Databaseimport Quixotic.Database.PostgreSQLimport Quixotic.TimeLogimport Quixotic.Usersimport Snap.Coreimport Snap.Snapletimport Snap.Snaplet.PostgresqlSimpleimport qualified Snap.Snaplet.Auth as AUimport Snap.Snaplet.Sessiondata QModules = QModules{ _qdb :: QDB (ReaderT Connection IO), _depf :: DepF}makeLenses ''QModulesdata App = App{ _qm :: Snaplet QModules, _sess :: Snaplet SessionManager, _db :: Snaplet Postgres, _auth :: Snaplet (AU.AuthManager App)}makeLenses ''Appinstance HasPostgres (Handler b App) wheregetPostgresState = with db getsetLocalPostgresState s = local (set (db . snapletValue) s)-- | FIXME, make configurableqdbpgSnapletInit :: SnapletInit a QModulesqdbpgSnapletInit = makeSnaplet "qdbpg" "QDB on Postgresql" Nothing $ dopure $ QModules postgresQDB $ linearDepreciation (Months 6) (Months 60)requireLogin :: Handler App App a -> Handler App App arequireLogin = AU.requireUser auth (redirect "/login")requireUserId :: (UserId -> Handler App App a) -> Handler App App arequireUserId hf = AU.requireUser auth (redirect "/login") $ doQDB{..} <- view qdb <$> with qm getauthedUser <- with auth AU.currentUserqdbUser <- case UserName . AU.unUid <$> (AU.userId =<< authedUser) ofNothing -> snapError 403 "User is authenticated, but session lacks user identifier"Just n -> liftPG . runReaderT $ findUserByUserName ncase qdbUser ofNothing -> snapError 403 "Unable to retrieve user record for authenticated user"Just u -> hf (u ^. userId)snapError :: MonadSnap m => Int -> Text -> m asnapError c t = domodifyResponse $ setResponseStatus c $ encodeUtf8 twriteText $ ((tshow c) <> " - " <> t)getResponse >>= finishWithok :: MonadSnap m => m ()ok = domodifyResponse $ setResponseCode 200getResponse >>= finishWith
{-# LANGUAGE ScopedTypeVariables #-}{-# LANGUAGE OverloadedStrings #-}{-# LANGUAGE NoImplicitPrelude #-}{-# LANGUAGE RecordWildCards #-}{-# LANGUAGE TemplateHaskell #-}module Quixotic.Api.Users( loginHandler, registerHandler) whereimport ClassyPreludeimport Data.ByteString (split)import Data.Attoparsec.ByteStringimport qualified Data.ByteString.Base64 as B64import Quixotic.Api.Typesimport Snap.Coreimport Snap.Snapletimport qualified Snap.Snaplet.Auth as AUdata AuthHeader = AuthHeader Text ByteStringauthHeaderParser :: Parser AuthHeaderauthHeaderParser = dolet isBase64Char w = (w >= 47 && w <= 57 ) ||(w >= 64 && w <= 90 ) ||(w >= 97 && w <= 122) ||(w == 43 || w == 61 )b64 <- string "Basic" *> takeWhile1 isBase64Chardecoded <- either fail pure $ B64.decode b64case 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 ''CreateUserloginHandler :: (AU.AuthUser -> Handler App App a) -> Handler App App aloginHandler onSuccess = doreq <- getRequestrawHeader <- maybe throwChallenge pure $ getHeader "Authorization" req(AuthHeader uname pwd) <- either (throwDenied . AU.AuthError) pure $ parseOnly authHeaderParser rawHeaderauthResult <- with auth $ AU.loginByUsername uname (AU.ClearText pwd) Falseeither throwDenied onSuccess authResultregisterHandler :: Handler App App ()registerHandler = okthrowChallenge :: MonadSnap m => m athrowChallenge = domodifyResponse $ (setResponseStatus 401 "Unauthorized") .(setHeader "WWW-Authenticate" "Basic realm=quixotic")getResponse >>= finishWiththrowDenied :: MonadSnap m => AU.AuthFailure -> m athrowDenied failure = domodifyResponse $ setResponseStatus 403 "Access Denied"writeText $ "Access Denied: " <> tshow failuregetResponse >>= finishWith
{-# LANGUAGE FlexibleInstances #-}{-# LANGUAGE TemplateHaskell #-}module Quixotic.Api whereimport ClassyPreludeimport Control.Lensimport Control.Monad.Stateimport qualified Data.Aeson as Aimport Data.Mapimport Quixoticimport Quixotic.Databaseimport Quixotic.Jsonimport Quixotic.TimeLogimport Quixotic.Api.Typesimport Snap.Coreimport Snap.Snapletimport Snap.Snaplet.PostgresqlSimplelogWorkHandler :: EventType -> Handler App App ()logWorkHandler evType = requireUserId $ \uid -> doQDB{..} <- view qdb <$> with qm getaddrBytes <- getParam "btcAddr"timestamp <- liftIO getCurrentTimelet workEvent = WorkEvent evType timestampstoreEv addr = runReaderT . recordEvent uid $ LogEntry addr workEventcase fmap decodeUtf8 addrBytes >>= parseBtcAddr ofNothing -> snapError 400 $ "Unable to parse bitcoin address from " <> (tshow addrBytes)Just addr -> liftPG $ storeEv addrloggedIntervalsHandler :: Handler App App ()loggedIntervalsHandler = requireLogin $ doQDB{..} <- view qdb <$> with qm getwidx <- liftPG $ runReaderT readWorkIndexmodifyResponse $ addHeader "content-type" "application/json"writeLBS . A.encode $ mapKeys (^. address) widxpayoutsHandler :: Handler App App ()payoutsHandler = requireLogin $ do(QModules QDB{..} df) <- with qm getptime <- liftIO $ getCurrentTimewidx <- liftPG $ runReaderT readWorkIndexmodifyResponse $ addHeader "content-type" "application/json"writeLBS . A.encode . PayoutsResponse $ payouts df ptime widx