Add captcha check to register route.
[?]
Sep 14, 2020, 3:05 AM
U256ZALIPTVWLNACYPIMWLNEYDQWP7CHF4Y4CGMILQTONJHMGQVQCDependencies
- [2]
O2BZOX7MAdd signup form, captcha check. - [3]
GMYPBCWEMake docker-compose work. - [4]
SEWTRB6SImplement payment request creation functions. - [5]
7KZP4RHZSwitch from Data.Time to Data.Thyme - [6]
J6S23MDGUse server timestamps for interval start and end. - [7]
NTPC7KJETrivial changes, feature scratchpad. - [8]
6L5BK5EHUse generic SMTP rather than Sendmail-specific mail client. - [9]
EFSXYZPOAutoformat everything with brittany. - [10]
NEDDHXUKReformat via stylish-haskell - [11]
Y35QCWYWMinor improvement in WorkIndex type to eliminate duplicated information. - [12]
5XFJNUAZStart of addition of project infrastructure. - [13]
TNR3TEHKSwitch to Postgres + snaplet arch compiles. - [14]
7DBNV3GVInitial, stack-based impl of time log event reduction. - [15]
LD4GLVSFMore database stuff. - [16]
4U7F3CPITHE GREAT RENAMING OF THINGS! - [17]
2XQD6KKKAdd invitation logic and clean up DBProg error handling. - [18]
RN7EI6INUpdate database layer to use CreditTo - [19]
4FDQGIXNMake payment request retrieval key an opaque 32-bit hash. - [20]
LTSVBVA2Update to a recent haskoin-core. Fix Stack build. - [21]
GCVQD44VCreate amends endpoint, switch to UUID primary keys - [22]
HMDM3B55Implement core of payments/billing infrastructure. - [23]
KEP5WUFJConvert project to stack-based build. - [24]
BROSTG5KBeginning of modularization of server. - [25]
MB5SHULBAdd route for accepting an invitation with an existing account - [26]
ASF3UPJLAdd auction creation and bid handlers - [27]
Q5X5RYQLstylish-haskell reformatting - [28]
NJNMO72SAdd zcash.com submodule and update client to modern halogen. - [29]
B6HWAPDPModularize & update to recent haskoin. - [30]
QMRKFEPGRefactor QDB to use a free monad algebra instead. - [31]
IPG33FAWAdd billing daemon - [32]
BXGLKYRXAdded primitive user registration handler. - [33]
IZEVQF62Work in progress replacing sqlite with postgres. - [34]
4R7XIYK3Switch from ClassyPrelude to Relude - [35]
AWWC6P5ZAdd migration to include payment network with addresses. - [36]
RFYEVKZQAdd nix-shell based build environment. - [37]
64C6AWH6Rename Ananke -> Quixotic, project reboot. - [38]
WFZDMVUXRename ADB -> QDB - [39]
O722AOKEAdd route to allow crediting of events to users/projects. - [40]
73NDXDEZBegin implementation of billing event persistence. - [41]
SLL7262CMake depreciation functions more flexible. - [42]
UWMGUJOWAutoformat sources. - [43]
PBD7LZYQPostgres & auth are beginning to function. - [44]
4QX5E5ACInitial compilation of payouts function succeeds. - [45]
HALRDT2FAdded initial auction create route. - [46]
TLQ72DSJLenses, sqlite-simple - [47]
PT4276XCAdd logout functionality. - [48]
I2KHGVD4Require project permissions for access to most data. - [49]
DFOBMSAOInitial work on payments API - [50]
JFOEOFGAstylish-haskell formatting. - [*]
NVOCQVASInitial failing tests. - [*]
2WOOGXDHUse dbmigrations to manage database state. - [*]
V2VDN77HEnable postgres configuration via environment variable for Heroku. - [*]
MJ6R42RCUtility methods for reading key & cert data. - [*]
ADMKQQGCInitial empty Snap project.
Change contents
- replacement in DEVELOPMENT.md at line 51
Once all the containers are up, you'll need to run the database migrationsas follows:Database Configuration----------------------All database DDL state is handled using the Haskell dbmigrations tool.Once all the containers are up, you'll need to run the existing databasemigrations as follows: - edit in DEVELOPMENT.md at line 63[3.1759]
New migrations can be created with:~~~bashmoo-postgresql new --config-file ./local/conf/server/aftok-migrations.cfg kebab-case-descriptive-name~~~ - edit in aftok.cabal at line 35
Aftok.Currency.ZCash - edit in aftok.cabal at line 159
, from-sum - replacement in client/src/Aftok/Api/Account.purs at line 107
pure SignupOKlet signupJSON ={ username: req.username, password: req.password, recoveryType: case req.recoverBy ofRecoverByEmail _ => "email"RecoverByZAddr _ => "zaddr", email: case req.recoverBy ofRecoverByEmail email -> Just emailRecoverByZAddr _ -> Nothing, zaddr: case req.recoverBy ofRecoverByEmail _ -> NothingRecoverByZAddr zaddr -> Just zaddr} - file addition: ZCash.hs[3.4250]
{-# LANGUAGE TemplateHaskell #-}module Aftok.Currency.ZCash whereimport Control.Lens ( makePrisms)newtype ZAddr = ZAddr { zaddrText :: Text }deriving (Eq, Ord, Show)makePrisms ''ZAddr - edit in lib/Aftok/Database/PostgreSQL.hs at line 44
import Aftok.Currency.ZCash (ZAddr(..), _ZAddr) - edit in lib/Aftok/Database/PostgreSQL.hs at line 79
null :: RowParser Nullnull = field - replacement in lib/Aftok/Database/PostgreSQL.hs at line 95
addressParser :: NetworkMode -> RowParser (NetworkId, Address)addressParser mode = dobtcAddressParser :: NetworkMode -> RowParser (NetworkId, Address)btcAddressParser mode = do - replacement in lib/Aftok/Database/PostgreSQL.hs at line 154
CreditToCurrency <$> (addressParser mode <* nullField <* nullField)CreditToCurrency <$> (btcAddressParser mode <* nullField <* nullField) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 188
userParser :: NetworkMode -> RowParser BTCUseruserParser mode =userParser :: RowParser UseruserParser = - replacement in lib/Aftok/Database/PostgreSQL.hs at line 192
<*> ((null *> null *> pure Nothing) <|> fmap Just (addressParser mode))<*> (Email <$> field)<*> ((maybe empty pure =<< fmap (RecoverByEmail . Email) <$> field)<|>(maybe empty pure =<< fmap (RecoverByZAddr . ZAddr) <$> field)) - edit in lib/Aftok/Database/PostgreSQL.hs at line 526
mode <- askNetworkModelet nidMay = fst <$> _userAddress user'addrMay :: Maybe TextaddrMay = donetwork <- toNetwork mode <$> nidMayaddress <- snd <$> _userAddress user'addrToText network address - replacement in lib/Aftok/Database/PostgreSQL.hs at line 528
[sql| INSERT INTO users (handle, default_payment_network, default_payment_addr, email)[sql| INSERT INTO users (handle, recovery_email, recovery_zaddr) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 531[3.22386]→[3.14186:14219](∅→∅),[3.9464]→[3.14186:14219](∅→∅),[3.14219]→[3.9464:9478](∅→∅),[3.9464]→[3.9464:9478](∅→∅),[3.9478]→[3.22387:22421](∅→∅)
, renderNetworkId <$> nidMay, addrMay, user' ^. userEmail . _Email, user' ^? userAccountRecovery . _RecoverByEmail . _Email, user' ^? userAccountRecovery . _RecoverByZAddr . _ZAddr - edit in lib/Aftok/Database/PostgreSQL.hs at line 536
mode <- askNetworkMode - replacement in lib/Aftok/Database/PostgreSQL.hs at line 537
(userParser mode)[sql| SELECT handle, default_payment_network, default_payment_addr, email FROM users WHERE id = ? |]userParser[sql| SELECT handle, recovery_email, recovery_zaddr FROM users WHERE id = ? |] - edit in lib/Aftok/Database/PostgreSQL.hs at line 542
headMay <$> pquery((,) <$> idParser UserId <*> userParser)[sql| SELECT id, handle, recovery_email, recovery_zaddr FROM users WHERE handle = ? |](Only h)pgEval (FindUserPaymentAddress (UserId uid)) = do - replacement in lib/Aftok/Database/PostgreSQL.hs at line 550[3.22487]→[3.22487:22537](∅→∅),[3.22537]→[3.1431:1544](∅→∅),[3.1544]→[3.9812:9825](∅→∅),[3.18549]→[3.9812:9825](∅→∅),[3.9812]→[3.9812:9825](∅→∅)
((,) <$> idParser UserId <*> userParser mode)[sql| SELECT id, handle, default_payment_network, default_payment_addr, email FROM users WHERE handle = ? |](Only h)(btcAddressParser mode)[sql| SELECT default_payment_network, default_payment_addr FROM users WHERE id = ? |](Only uid) - edit in lib/Aftok/Database.hs at line 39
type BTCUser = User BTCNet - replacement in lib/Aftok/Database.hs at line 41
CreateUser :: BTCUser -> DBOp UserIdFindUser :: UserId -> DBOp (Maybe BTCUser)FindUserByName :: UserName -> DBOp (Maybe (UserId, BTCUser))CreateUser :: User -> DBOp UserIdFindUser :: UserId -> DBOp (Maybe User)FindUserByName :: UserName -> DBOp (Maybe (UserId, User))FindUserPaymentAddress :: UserId -> DBOp (Maybe (BTCNet)) - replacement in lib/Aftok/Database.hs at line 116
createUser :: (MonadDB m) => BTCUser -> m UserIdcreateUser :: (MonadDB m) => User -> m UserId - replacement in lib/Aftok/Database.hs at line 119
findUser :: (MonadDB m) => UserId -> MaybeT m BTCUserfindUser :: (MonadDB m) => UserId -> MaybeT m User - replacement in lib/Aftok/Database.hs at line 122
findUserByName :: (MonadDB m) => UserName -> MaybeT m (UserId, BTCUser)findUserByName :: (MonadDB m) => UserName -> MaybeT m (UserId, User) - edit in lib/Aftok/Database.hs at line 124
findUserPaymentAddress :: (MonadDB m) => UserId -> MaybeT m (BTCNet)findUserPaymentAddress = MaybeT . liftdb . FindUserPaymentAddress - replacement in lib/Aftok/Payments/Types.hs at line 75
-- using error here is reasonable since it would indicate-- a serialization problem-- using error here is reasonable since it would indicate-- a serialization problem - edit in lib/Aftok/Payments.hs at line 49
, userAddress - replacement in lib/Aftok/Payments.hs at line 275
user <- findUser uidaddr <- MaybeT . pure . fmap snd $ user ^. userAddress(_, addr) <- findUserPaymentAddress uid - replacement in lib/Aftok/TimeLog.hs at line 160
workIndex:: forall a f . (Ord a, Foldable f) => f (LogEntry a) -> (WorkIndex a)workIndex :: forall a f . (Ord a, Foldable f) => f (LogEntry a) -> (WorkIndex a) - edit in lib/Aftok/Types.hs at line 9
import Data.Maybe ( Maybe ) - edit in lib/Aftok/Types.hs at line 16
import Aftok.Currency.ZCash ( ZAddr ) - replacement in lib/Aftok/Types.hs at line 28
data User a = User{ _username :: !UserName, _userAddress :: !(Maybe a), _userEmail :: !Emaildata AccountRecovery= RecoverByEmail Email| RecoverByZAddr ZAddrmakePrisms ''AccountRecoverydata User = User{ _username :: !UserName, _userAccountRecovery :: !AccountRecovery - file addition: 2020-09-14_23-27-54_users-account-recovery.txt[53.1]
Description: (Describe migration here.)Created: 2020-09-14 23:27:57.342707094 UTCDepends: 2020-06-06_03-53-54_add-payment-networksApply: |ALTER TABLE users RENAME COLUMN email TO recovery_email;ALTER TABLE users ALTER COLUMN recovery_email DROP NOT NULL;ALTER TABLE users ADD COLUMN recovery_zaddr text;Revert: |ALTER TABLE users RENAME COLUMN recovery_email TO email;ALTER TABLE users ALTER COLUMN email SET NOT NULL;ALTER TABLE users DROP COLUMN recovery_zaddr; - edit in server/Aftok/QConfig.hs at line 25
import Aftok.Snaplet.Users (CaptchaConfig(..)) - replacement in server/Aftok/QConfig.hs at line 37
, _recaptchaSecret :: Maybe Text, _recaptchaSecret :: CaptchaConfig - replacement in server/Aftok/QConfig.hs at line 66
<*> C.lookup cfg "recaptchaSecret"<*> (CaptchaConfig <$> C.require cfg "recaptchaSecret") - replacement in server/Aftok/Snaplet/Projects.hs at line 66
uid <- requireUserIdpid <- requireProjectIduid <- requireUserIdpid <- requireProjectId - replacement in server/Aftok/Snaplet/Projects.hs at line 69
t <- liftIO C.getCurrentTime(Just u, Just p, invCode) <-t <- liftIO C.getCurrentTime(Just p, invCode) <- - replacement in server/Aftok/Snaplet/Projects.hs at line 72
$ (,,)<$> (runMaybeT $ findUser uid)<*> (runMaybeT $ findUserProject uid pid)$ (,)<$> (runMaybeT $ findUserProject uid pid) - replacement in server/Aftok/Snaplet/Projects.hs at line 77
(u ^. userEmail)(Email "noreply@aftok.com") - edit in server/Aftok/Snaplet/Users.hs at line 17
import Control.FromSum ( fromMaybeM ) - replacement in server/Aftok/Snaplet/Users.hs at line 19
import Data.Aeson ((.:), (.:?))import Data.Aeson ( (.:), (.:?)) - replacement in server/Aftok/Snaplet/Users.hs at line 27
import Haskoin.Address ( textToAddr )import Network.HTTP.Client ( parseRequest, responseBody, responseStatus, httpLbs)import Network.HTTP.Client ( parseRequest, responseBody, responseStatus, httpLbs) - replacement in server/Aftok/Snaplet/Users.hs at line 33
import Network.HTTP.Client.MultipartFormData (formDataBody, partBS)import Network.HTTP.Client.MultipartFormData( formDataBody, partBS) - replacement in server/Aftok/Snaplet/Users.hs at line 39
import Aftok.Typesimport Aftok.Currency.Bitcoin ( NetworkId(..), toNetwork)import Aftok.Currency.ZCash ( ZAddr(..) ) - edit in server/Aftok/Snaplet/Users.hs at line 42
import Aftok.Types - replacement in server/Aftok/Snaplet/Users.hs at line 47
import qualified Snap.Core as Simport qualified Snap.Snaplet as Simport qualified Snap.Core as Simport qualified Snap.Snaplet as S - replacement in server/Aftok/Snaplet/Users.hs at line 51
data CUser = CU{ _cuser :: User Textdata RegisterRequest = RegisterRequest{ _cuser :: User - edit in server/Aftok/Snaplet/Users.hs at line 54
, _captchaToken :: Text - replacement in server/Aftok/Snaplet/Users.hs at line 57
makeLenses ''CUsermakeLenses ''RegisterRequest - replacement in server/Aftok/Snaplet/Users.hs at line 59
instance A.FromJSON CUser whereparseJSON (A.Object v) =let parseUser =User<$> (UserName <$> v .: "username")<*> (v .: "btcAddr")<*> (Email <$> v .: "email")instance A.FromJSON RegisterRequest whereparseJSON (A.Object v) = dorecoveryType <- v .: "recoveryType"recovery <- case (recoveryType :: Text) of"email" -> RecoverByEmail . Email <$> v .: "email""zaddr" -> RecoverByZAddr . ZAddr <$> v .: "zaddr"_ -> Prelude.emptyuser <- User <$> (UserName <$> v .: "username")<*> pure recovery - replacement in server/Aftok/Snaplet/Users.hs at line 69[3.10652]→[3.9105:9234](∅→∅),[3.9234]→[3.10784:10820](∅→∅),[3.10784]→[3.10784:10820](∅→∅),[3.10820]→[3.56169:56251](∅→∅)
parseInvitationCodes c = either(\e -> fail $ "Invitation code was rejected as invalid: " <> e)pure(traverse parseInvCode c)in CU<$> parseUser<*> (fromString <$> v .: "password")RegisterRequest user<$> (fromString <$> v .: "password")<*> (v .: "captchaToken") - edit in server/Aftok/Snaplet/Users.hs at line 74
whereparseInvitationCodes c = either(\e -> fail $ "Invitation code was rejected as invalid: " <> e)pure(traverse parseInvCode c) - replacement in server/Aftok/Snaplet/Users.hs at line 81[3.4425]→[3.18267:18311](∅→∅),[3.11004]→[3.657:678](∅→∅),[3.18311]→[3.657:678](∅→∅),[3.4886]→[3.657:678](∅→∅),[3.678]→[2.10500:10534](∅→∅),[2.10534]→[3.11005:11064](∅→∅),[3.3592]→[3.11005:11064](∅→∅),[3.11064]→[3.56317:56389](∅→∅),[3.56389]→[2.10535:10556](∅→∅),[2.10556]→[3.56416:56451](∅→∅),[3.56416]→[3.56416:56451](∅→∅),[3.56451]→[3.35003:35029](∅→∅),[3.11096]→[3.35003:35029](∅→∅),[3.35029]→[3.56452:56465](∅→∅),[3.56465]→[3.59698:59777](∅→∅)
registerHandler :: S.Handler App App UserIdregisterHandler = dorbody <- S.readRequestBody 4096-- allow any number of 'invitationCode' query parametersuserData <- maybe (snapError 400 "Could not parse user data") pure$ A.decode rbodyt <- liftIO C.getCurrentTimenmode <- getNetworkModelet addr =textToAddr (toNetwork nmode BTC) =<< (userData ^. cuser . userAddress)registerHandler :: CaptchaConfig -> S.Handler App App UserIdregisterHandler cfg = dorbody <- S.readRequestBody 4096userData <- fromMaybeM (snapError 400 "Could not parse user data") (A.decode rbody)captchaResult <- liftIO $ checkCaptcha cfg (userData ^. captchaToken)void . either (const . throwDenied $ AU.AuthError "Captcha check failed, please try again.") pure $ captchaResultnow <- liftIO C.getCurrentTime - replacement in server/Aftok/Snaplet/Users.hs at line 93
userId <- createUser((userData ^. cuser) & userAddress .~ ((BTC, ) <$> addr))void $ traverse (acceptInvitation userId t) (userData ^. invitationCodes)return userIduserId <- createUser (userData ^. cuser)void $ traverse (acceptInvitation userId now) (userData ^. invitationCodes)pure userId - replacement in server/Aftok/Snaplet/Users.hs at line 102
t <- liftIO C.getCurrentTimenow <- liftIO C.getCurrentTime - replacement in server/Aftok/Snaplet/Users.hs at line 111
(\cx -> void . snapEval $ traverse (acceptInvitation uid t) cx)(\cx -> void . snapEval $ traverse (acceptInvitation uid now) cx) - replacement in server/Aftok/Snaplet/Users.hs at line 136
CaptchaResponse <$> v .: "success"<*> (fmap toError . join . toList <$> v .:? "error-codes")wheretoError = \case"missing-input-secret" -> MissingInputSecret"invalid-input-secret" -> InvalidInputSecret"missing-input-response" -> MissingInputResponse"invalid-input-response" -> InvalidInputResponse"bad-request" -> BadRequest"timeout-or-duplicate" -> TimeoutOrDuplicateother -> CaptchaError $ "Unexpected error code: " <> otherparseJSON _ =fail "Captcha response body was not a valid JSON object."CaptchaResponse<$> v.: "success"<*> (fmap toError . join . toList <$> v .:? "error-codes")wheretoError = \case"missing-input-secret" -> MissingInputSecret"invalid-input-secret" -> InvalidInputSecret"missing-input-response" -> MissingInputResponse"invalid-input-response" -> InvalidInputResponse"bad-request" -> BadRequest"timeout-or-duplicate" -> TimeoutOrDuplicateother -> CaptchaError $ "Unexpected error code: " <> otherparseJSON _ = fail "Captcha response body was not a valid JSON object." - replacement in server/Aftok/Snaplet/Users.hs at line 153
request <- parseRequest "https://www.google.com/recaptcha/api/siteverify"reqWithBody <- formDataBody [partBS "secret" (T.encodeUtf8 $ secretKey cfg), partBS "response" (T.encodeUtf8 token)] requestmanager <- newTlsManagerrequest <- parseRequest "https://www.google.com/recaptcha/api/siteverify"reqWithBody <- formDataBody[ partBS "secret" (T.encodeUtf8 $ secretKey cfg), partBS "response" (T.encodeUtf8 token)]requestmanager <- newTlsManager - replacement in server/Aftok/Snaplet/Users.hs at line 162
200 ->case A.eitherDecode (responseBody response) ofLeft err -> Left [CaptchaError $ "Failed to decode JSON response: " <> T.pack err]Right cr -> if success cr then Right () else Left (errorCodes cr)200 -> case A.eitherDecode (responseBody response) ofLeft err ->Left [CaptchaError $ "Failed to decode JSON response: " <> T.pack err]Right cr -> if success cr then Right () else Left (errorCodes cr) - replacement in server/Aftok/Snaplet/Users.hs at line 167
Left $ [CaptchaError $ "Unexpected status code: " <> T.pack (show errCode)][2.12433]Left$ [CaptchaError $ "Unexpected status code: " <> T.pack (show errCode)] - replacement in server/Main.hs at line 67
registerRoute = void $ method POST registerHandlerregisterRoute = void $ method POST (registerHandler $ cfg ^. recaptchaSecret)