{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TupleSections #-} module Yesod.ReCaptcha2 ( YesodReCaptcha(..) -- * ReCaptcha V2 , reCaptcha , mReCaptcha -- * Invisible ReCaptcha -- $invisibleReCaptcha , reCaptchaInvisible , mReCaptchaInvisible , reCaptchaInvisibleForm ) where import Control.Monad (when) import Data.Maybe (isNothing) import Data.String.Transform (ToByteStringStrict (toByteStringStrict)) import Data.Text (Text) import GHC.Generics (Generic) import Network.HTTP.Simple (getResponseBody, httpJSON, parseRequest, setRequestBodyURLEncoded) import Yesod.Core (FromJSON, HandlerFor, MonadIO (liftIO), MonadTrans (lift), ToJSON, ToWidgetHead (toWidgetHead), WidgetFor, addScriptRemote, hamlet, handlerToWidget, lookupPostParam, newIdent, whamlet) import Yesod.Form.Functions (formToAForm) import Yesod.Form.Types (AForm, FieldView (..), FormResult (..), MForm) -- | default key is testing. you should impl reCaptchaSiteKey and reCaptchaSecretKey class YesodReCaptcha site where reCaptchaSiteKey :: HandlerFor site Text reCaptchaSiteKey = pure "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" reCaptchaSecretKey :: HandlerFor site Text reCaptchaSecretKey = pure "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe" -- | with specific language from -- -- -- > reCaptchaLanguage = pure (Just "ru") reCaptchaLanguage :: HandlerFor site (Maybe Text) reCaptchaLanguage = pure Nothing newtype SiteverifyResponse = SiteverifyResponse { success :: Bool } deriving (Eq, Ord, Read, Show, Generic, FromJSON, ToJSON) -- | for Applicative style form reCaptcha :: YesodReCaptcha site => AForm (HandlerFor site) () reCaptcha = formToAForm mReCaptcha -- | for Monadic style form mReCaptcha :: YesodReCaptcha site => MForm (HandlerFor site) (FormResult (), [FieldView site]) mReCaptcha = do result <- lift formResult return (result, [fieldViewSite]) where formResult = do postParam <- lookupPostParam "g-recaptcha-response" case postParam of Nothing -> return FormMissing Just response -> do secret <- reCaptchaSecretKey SiteverifyResponse { success } <- liftIO $ do req <- parseRequest "POST https://www.google.com/recaptcha/api/siteverify" res <- httpJSON $ setRequestBodyURLEncoded [("secret", toByteStringStrict secret), ("response", toByteStringStrict response)] req return $ getResponseBody res return $ if success then FormSuccess () else FormFailure ["reCaptcha error"] fieldViewSite = FieldView { fvLabel = mempty , fvTooltip = Nothing , fvId = "" , fvInput = do mReCaptchaLanguage <- handlerToWidget reCaptchaLanguage case mReCaptchaLanguage of Nothing -> addScriptRemote "https://www.google.com/recaptcha/api.js" Just hl -> addScriptRemote $ "https://www.google.com/recaptcha/api.js?hl=" <> hl siteKey <- handlerToWidget reCaptchaSiteKey [whamlet|
|] , fvErrors = Nothing , fvRequired = True } -- $invisibleReCaptcha -- -- The Invisible ReCaptcha is not as easy as the V2. -- -- 1. Function to check the response: 'reCaptchaInvisible' or 'mReCaptchaInvisible'. -- -- 2. Add the following to the code which creates the form: -- -- > (reCaptchaFormId, reCaptchaWidget, reCaptchaButtonAttributes) <- -- > reCaptchaInvisibleForm Nothing -- -- 3. Add the id to the form, class and attributes to the button and the widget somewhere. -- Example: -- -- @ -- \
-- ^{widget} -- ^{reCaptchaWidget} -- -- \