-
Notifications
You must be signed in to change notification settings - Fork 9
Record all signup attempts in DB #418
Changes from all commits
544fe46
e0a0d64
6956441
2b2cf23
f4e1f6a
aca3dc5
ba4dd4d
5cb2e33
2832084
3ec0b93
9c63fdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
{-# OPTIONS_GHC -fno-warn-orphans #-} | ||
|
||
module Data.Csv.Missing where | ||
|
||
import Data.Csv | ||
import Control.Monad (mzero) | ||
|
||
instance ToField Bool where | ||
toField True = "1" | ||
toField False = "0" | ||
|
||
instance FromField Bool where | ||
parseField "1" = pure True | ||
parseField "0" = pure False | ||
parseField _ = mzero | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import Network.Wai (Application) | |
import Servant.API ((:<|>)((:<|>)), (:>), Get, Post, Delete, Capture, ReqBody, JSON) | ||
import Servant.Server (ServerT, Server, serve, enter) | ||
import Servant.API.ResponseHeaders (Headers, addHeader) | ||
import Servant.CSV.Cassava (CSV', DefaultEncodeOpts) | ||
import System.Log.Logger (Priority(INFO)) | ||
|
||
import qualified Servant.Docs as Docs | ||
|
@@ -87,6 +88,7 @@ type ThentosUser = | |
:<|> "activate" :> ReqBody '[JSON] (JsonTop ConfirmationToken) | ||
:> Post '[JSON] (JsonTop ThentosSessionToken) | ||
:<|> "login" :> ReqBody '[JSON] LoginFormData :> Post '[JSON] (JsonTop ThentosSessionToken) | ||
:<|> "registration_attempts" :> Get '[JSON, (CSV', DefaultEncodeOpts)] [SignupAttempt] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the path so different from the name of the handler function? |
||
:<|> Capture "uid" UserId :> Delete '[JSON] () | ||
:<|> Capture "uid" UserId :> "name" :> Get '[JSON] (JsonTop UserName) | ||
:<|> Capture "uid" UserId :> "email" :> Get '[JSON] (JsonTop UserEmail) | ||
|
@@ -100,6 +102,7 @@ thentosUser = | |
:<|> addUnconfirmedUserWithCaptcha | ||
:<|> (JsonTop <$>) . (snd <$>) . confirmNewUser . fromJsonTop | ||
:<|> (\(LoginFormData n p) -> JsonTop . snd <$> startThentosSessionByUserName n p) | ||
:<|> getAllSignupAttempts | ||
:<|> deleteUser | ||
:<|> (JsonTop . ((^. userName) . snd) <$>) . lookupConfirmedUser | ||
:<|> (JsonTop . ((^. userEmail) . snd) <$>) . lookupConfirmedUser | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -618,6 +618,17 @@ garbageCollectCaptchas timeout = void $ execT | |
[sql| DELETE FROM captchas WHERE timestamp < now() - ?::interval; |] (Only timeout) | ||
|
||
|
||
-- | store signup attempt in the db | ||
recordSignupAttempt :: UserName -> UserEmail -> Bool -> ThentosQuery e () | ||
recordSignupAttempt name email captchaCorrect = void $ execT | ||
[sql| INSERT INTO signup_attempts (user_name, email, captcha_correct, timestamp) | ||
VALUES (?, ?, ?, now()) |] (name, email, captchaCorrect) | ||
|
||
getAllSignupAttempts :: ThentosQuery e [SignupAttempt] | ||
getAllSignupAttempts = map (\(n, e, cc, t) -> SignupAttempt n e cc t) <$> | ||
queryT [sql| SELECT user_name, email, captcha_correct, timestamp | ||
FROM signup_attempts |] () | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing empty line. |
||
-- * helpers | ||
|
||
-- | Throw an error from a situation which (we believe) will never arise. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,6 +112,38 @@ spec_user = describe "user" $ do | |
void . runWithoutPrivs sta $ confirmUserEmailChange token | ||
checkEmail uid $ (==) newEmail | ||
|
||
describe "addUnconfirmedUserWithCaptcha" $ do | ||
it "records signup attempts in the database" $ \sta -> do | ||
let ActionState (conns, _, _) = sta | ||
name = UserName "someName" | ||
userData = UserFormData name "secret" (forceUserEmail "me@example.com") | ||
cid = "myCaptchaId" | ||
solution = "theSolution" | ||
captchaSolution = CaptchaSolution cid solution | ||
req = UserCreationRequest userData captchaSolution | ||
Right () <- runVoidedQuery conns $ T.storeCaptcha cid solution | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you don't need to store the captcha solution: validity is not a requirement for this test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant to write a test for each case (with and without valid captcha) to make sure we store the attempt either way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant to write a test for each case (with and without valid captcha) to make sure we store the attempt either way.
ok, that makes sense.
|
||
void . runPrivs ([] :: [Bool]) sta $ addUnconfirmedUserWithCaptcha req | ||
[(name', captchaCorrect)] <- doQuery conns | ||
[sql| SELECT user_name, captcha_correct | ||
FROM signup_attempts |] () | ||
name `shouldBe` name' | ||
captchaCorrect `shouldBe` True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as above: different test? |
||
|
||
it "records signup attempts in the database if the captcha is wrong" $ \sta -> do | ||
let ActionState (conns, _, _) = sta | ||
name = UserName "someName" | ||
userData = UserFormData name "secret" (forceUserEmail "me@example.com") | ||
cid = "myCaptchaId" | ||
captchaSolution = CaptchaSolution cid "wrong answer" | ||
req = UserCreationRequest userData captchaSolution | ||
Right () <- runVoidedQuery conns $ T.storeCaptcha cid "right answer" | ||
Left _ <- runPrivsE ([] :: [Bool]) sta $ addUnconfirmedUserWithCaptcha req | ||
[(name', captchaCorrect)] <- doQuery conns | ||
[sql| SELECT user_name, captcha_correct | ||
FROM signup_attempts |] () | ||
name `shouldBe` name' | ||
captchaCorrect `shouldBe` False | ||
|
||
spec_service :: SpecWith ActionState | ||
spec_service = describe "service" $ do | ||
describe "addService, lookupService, deleteService" $ do | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also: haskell-hvr/cassava#107