diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Yesod/ReCAPTCHA.hs b/src/Yesod/ReCAPTCHA.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/ReCAPTCHA.hs
@@ -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
diff --git a/yesod-recaptcha.cabal b/yesod-recaptcha.cabal
new file mode 100644
--- /dev/null
+++ b/yesod-recaptcha.cabal
@@ -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
