packages feed

yesod-recaptcha (empty) → 0.1

raw patch · 4 files changed

+304/−0 lines, 4 filesdep +basedep +bytestringdep +conduitsetup-changed

Dependencies added: base, bytestring, conduit, data-default, http-conduit, http-types, lifted-base, network, network-info, text, wai, yesod-auth, yesod-core, yesod-form

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2012, Felipe Lessa++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Felipe Lessa nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Yesod/ReCAPTCHA.hs view
@@ -0,0 +1,229 @@+module Yesod.ReCAPTCHA+    ( YesodReCAPTCHA(..)+    , recaptchaAForm+    , recaptchaMForm+    , recaptchaOptions+    , RecaptchaOptions(..)+    ) where++import Control.Applicative+import Data.Typeable (Typeable)+import Yesod.Widget (whamlet)+import qualified Control.Exception.Lifted as E+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString.Lazy.Char8 as L8+import qualified Data.Conduit as C+import qualified Data.Default as D+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Data.Text.Encoding.Error as TEE+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TLE+import qualified Network.HTTP.Conduit as H+import qualified Network.HTTP.Types as HT+import qualified Network.Info as NI+import qualified Network.Socket as HS+import qualified Network.Wai as W+import qualified Yesod.Auth as YA+import qualified Yesod.Core as YC+import qualified Yesod.Form.Fields as YF+import qualified Yesod.Form.Functions as YF+import qualified Yesod.Form.Types as YF+++-- | Class used by @yesod-recaptcha@'s fields.  It should be+-- fairly easy to implement a barebones instance of this class+-- for you foundation data type:+--+-- > instance YesodReCAPTCHA MyType where+-- >   recaptchaPublicKey  = return "[your public key]"+-- >   recaptchaPrivateKey = return "[your private key]"+--+-- You may also write a more sophisticated instance.  For+-- example, you may get these values from your @settings.yml@+-- instead of hardcoding them. Or you may give different keys+-- depending on the request (maybe you're serving to two+-- different domains in the same application).+--+-- The 'YA.YesodAuth' superclass is used only for the HTTP+-- request.  Please fill a bug report if you think that this+-- @YesodReCAPTCHA@ may be useful without @YesodAuth@.+class YA.YesodAuth master => YesodReCAPTCHA master where+    recaptchaPublicKey  :: YC.GHandler sub master T.Text+    recaptchaPrivateKey :: YC.GHandler sub master T.Text+++-- | A reCAPTCHA field.  This 'YF.AForm' returns @()@ because+-- CAPTCHAs give no useful information besides having being typed+-- correctly or not.  When the user does not type the CAPTCHA+-- correctly, this 'YF.AForm' will automatically fail in the same+-- way as any other @yesod-form@ widget fails, so you may just+-- ignore the @()@ value.+recaptchaAForm :: YesodReCAPTCHA master => YF.AForm sub master ()+recaptchaAForm = YF.formToAForm recaptchaMForm+++-- | Same as 'recaptchaAForm', but instead of being an+-- 'YF.AForm', it's an 'YF.MForm'.+recaptchaMForm :: YesodReCAPTCHA master =>+                  YF.MForm sub master ( YF.FormResult ()+                                      , [YF.FieldView sub master] )+recaptchaMForm = do+  challengeField <- fakeField "recaptcha_challenge_field"+  responseField  <- fakeField "recaptcha_response_field"+  ret <- maybe (return Nothing)+               (YC.lift . fmap Just . uncurry check)+               ((,) <$> challengeField <*> responseField)+  let view = recaptchaWidget $ case ret of+                                 Just (Error err) -> Just err+                                 _                -> Nothing+      formRet = case ret of+                  Nothing        -> YF.FormMissing+                  Just Ok        -> YF.FormSuccess ()+                  Just (Error _) -> YF.FormFailure []+      formView = YF.FieldView+                   { YF.fvLabel    = ""+                   , YF.fvTooltip  = Nothing+                   , YF.fvId       = "recaptcha_challenge_field"+                   , YF.fvInput    = view+                   , YF.fvErrors   = Nothing+                   , YF.fvRequired = True+                   }+  return (formRet, [formView])+++-- | Widget with reCAPTCHA's HTML.+recaptchaWidget :: YesodReCAPTCHA master =>+                   Maybe T.Text -- ^ Error code, if any.+                -> YC.GWidget sub master ()+recaptchaWidget merr = do+  publicKey <- YC.lift recaptchaPublicKey+  isSecure  <- W.isSecure <$> YC.lift YC.waiRequest+  let proto | isSecure  = "https"+            | otherwise = "http" :: T.Text+      err = maybe "" (T.append "&error=") merr+  [whamlet|+    <script src="#{proto}://www.google.com/recaptcha/api/challenge?k=#{publicKey}#{err}">+    <noscript>+       <iframe src="#{proto}://www.google.com/recaptcha/api/noscript?k=#{publicKey}#{err}"+           height="300" width="500" frameborder="0">+       <br>+       <textarea name="recaptcha_challenge_field" rows="3" cols="40">+       <input type="hidden" name="recaptcha_response_field" value="manual_challenge">+  |]+++-- | Contact reCAPTCHA servers and find out if the user correctly+-- guessed the CAPTCHA.  Unfortunately, reCAPTCHA doesn't seem to+-- provide an HTTPS endpoint for this API even though we need to+-- send our private key.+check :: YesodReCAPTCHA master =>+         T.Text -- ^ @recaptcha_challenge_field@+      -> T.Text -- ^ @recaptcha_response_field@+      -> YC.GHandler sub master CheckRet+check "" _ = return $ Error "invalid-request-cookie"+check _ "" = return $ Error "incorrect-captcha-sol"+check challenge response = do+  manager    <- YA.authHttpManager <$> YC.getYesod+  privateKey <- recaptchaPrivateKey+  sockaddr   <- W.remoteHost <$> YC.waiRequest+  case sockaddr of+    HS.SockAddrUnix _ -> do+      $(YC.logError) $ "Yesod.ReCAPTCHA: Couldn't find out remote IP, \+                       \are you using a reverse proxy?  If yes, then \+                       \please file a bug report at \+                       \<https://github.com/meteficha/yesod-recaptcha>."+      fail "Could not find remote IP address for reCAPTCHA."+    _ -> do+      let remoteip = case sockaddr of+                       HS.SockAddrInet _ hostAddr ->+                         show $ NI.IPv4 hostAddr+                       HS.SockAddrInet6 _ _ (w1, w2, w3, w4) _ ->+                         show $ NI.IPv6 w1 w2 w3 w4+                       HS.SockAddrUnix _ -> error "ReCAPTCHA.check"+          req = H.def+                  { H.method      = HT.methodPost+                  , H.host        = "www.google.com"+                  , H.path        = "/recaptcha/api/verify"+                  , H.queryString = HT.renderSimpleQuery False query+                  }+          query = [ ("privatekey", TE.encodeUtf8 privateKey)+                  , ("remoteip",   B8.pack       remoteip)+                  , ("challenge",  TE.encodeUtf8 challenge)+                  , ("response",   TE.encodeUtf8 response)+                  ]+      eresp <- E.try $ C.runResourceT $ H.httpLbs req manager+      case (L8.lines . H.responseBody) <$> eresp of+        Right ("true":_)      -> return Ok+        Right ("false":why:_) -> return . Error . TL.toStrict $+                                 TLE.decodeUtf8With TEE.lenientDecode why+        Right other -> do+          $(YC.logError) $ T.concat [ "Yesod.ReCAPTCHA: could not parse "+                                    , T.pack (show other) ]+          return (Error "recaptcha-not-reachable")+        Left exc -> do+          $(YC.logError) $ T.concat [ "Yesod.ReCAPTCHA: could not contact server ("+                                    , T.pack (show (exc :: E.SomeException))+                                    , ")" ]+          return (Error "recaptcha-not-reachable")+++-- | See 'check'.+data CheckRet = Ok | Error T.Text+++-- | A fake field.  Just returns the value of a field.+fakeField :: (YC.RenderMessage master YF.FormMessage) =>+             T.Text -- ^ Field id.+          -> YF.MForm sub master (Maybe T.Text)+fakeField fid = YC.lift $ do mt1 <- YC.lookupGetParam fid+                             case mt1 of+                               Nothing -> YC.lookupPostParam fid+                               Just _  -> return mt1+++-- | Define the given 'RecaptchaOptions' for all forms declared+-- after this widget.  This widget may be used anywhere, on the+-- @<head>@ or on the @<body>@.+--+-- Note that this is /not/ required to use 'recaptchaAForm' or+-- 'recaptchaMForm'.+recaptchaOptions :: YC.Yesod master =>+                    RecaptchaOptions+                 -> YC.GWidget sub master ()+recaptchaOptions s | s == D.def = return ()+recaptchaOptions s =+  [whamlet|+    <script>+      var RecaptchaOptions = {+      $maybe t <- theme s+        theme : '#{t}',+      $maybe l <- lang s+        lang : '#{l}',+      x : 'x'+      };+    </script>+  |]+++-- | Options that may be given to reCAPTCHA.  In order to use+-- them on your site, use `recaptchaOptions` anywhere before the+-- form that contains the `recaptchaField`.+--+-- Note that there's an instance for 'D.Default', so you may use+-- 'D.def'.+data RecaptchaOptions =+  RecaptchaOptions {+      -- | Theme of the reCAPTCHA field.  Currently may be+      -- @\"red\"@, @\"white\"@, @\"blackglass\"@ or @\"clean\"@.+      -- A value of @Nothing@ uses the default.+      theme :: Maybe T.Text++      -- | Language.+    , lang :: Maybe T.Text+    }+  deriving (Eq, Ord, Show, Typeable)++-- | Allows you to use 'D.def' and get sane default values.+instance D.Default RecaptchaOptions where+    def = RecaptchaOptions Nothing Nothing
+ yesod-recaptcha.cabal view
@@ -0,0 +1,43 @@+Name:                yesod-recaptcha+Version:             0.1+Synopsis:            Dead simple support for reCAPTCHA on Yesod applications.+Homepage:            https://github.com/meteficha/yesod-recaptcha+License:             BSD3+License-file:        LICENSE+Author:              Felipe Lessa+Maintainer:          felipe.lessa@gmail.com+Category:            Web+Build-type:          Simple+Cabal-version:       >=1.8+-- Copyright:+-- Extra-source-files:++Description:+  This package provides support for reCAPTCHA+  (<http://www.google.com/recaptcha>) on Yesod applications.++Source-repository head+  type:     git+  location: git://github.com/meteficha/yesod-recaptcha.git++Library+  Hs-source-dirs: src+  GHC-options: -Wall+  Exposed-modules: Yesod.ReCAPTCHA+  Extensions: DeriveDataTypeable FlexibleContexts TemplateHaskell+              QuasiQuotes OverloadedStrings+  Build-depends:+      base           == 4.*+    , bytestring+    , text+    , data-default   == 0.3.*+    , lifted-base    == 0.1.*+    , yesod-core     == 0.10.*+    , yesod-auth     == 0.8.*+    , yesod-form     == 0.4.*+    , wai+    , network+    , network-info   == 0.2.*+    , http-conduit   == 1.2.*+    , http-types+    , conduit        >= 0.1   && < 0.3