diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Revision history for haveibeenpwned
+
+## 0.2.0.0
+
+* Breaking change in order to make the API and the implementation more secure.
+  * There is a new HaveIBeenPwnedResult_Secure constructor which signals that the given password was not found in any database.
+  * The `HaveIBeenPwnedResult_Disclosed` constructor has been renamed to `HaveIBeenPwnedResult_Pwned`, as its behaviour changed. (Valid passwords are no longer signalled by this constructor.)
+* Also internally, a "not found in database" is no longer represented as a disclosed count of zero. This improves security in the case of an incorrect database entry, having a disclosed count of 0, which would make this library report that password as "secure", although it actually has been leaked.
+
+## 0.1.0.0
+
+* Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2019, Obsidian Systems
+
+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 Obsidian Systems 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/README.lhs b/README.lhs
new file mode 100644
--- /dev/null
+++ b/README.lhs
@@ -0,0 +1,68 @@
+haveibeenpwned
+<img align="right" src="https://obsidian.systems/static/images/ObsidianSystemsLogo.svg">
+======================
+[![Haskell](https://img.shields.io/badge/language-Haskell-orange.svg)](https://haskell.org) [![Hackage](https://img.shields.io/hackage/v/haveibeenpwned.svg)](https://hackage.haskell.org/package/haveibeenpwned) [![Hackage CI](https://matrix.hackage.haskell.org/api/v2/packages/haveibeenpwned/badge)](https://matrix.hackage.haskell.org/#/package/haveibeenpwned)   [![Github CI](https://github.com/obsidiansystems/haveibeenpwned/workflows/github-action/badge.svg)](https://github.com/obsidiansystems/haveibeenpwned/actions) [![travis-ci](https://api.travis-ci.org/obsidiansystems/haveibeenpwned.svg?branch=develop)](https://travis-ci.org/obsidiansystems/haveibeenpwned) [![BSD3 License](https://img.shields.io/badge/license-BSD3-blue.svg)](https://github.com/obsidiansystems/haveibeenpwned/blob/master/LICENSE)
+
+A [haskell](https://haskell.org) library for checking passwords against the
+[haveibeenpwned.com](https://haveibeenpwned.com) database.
+
+By means of this library you can do some basic strength check on new user
+passwords. Common weak passwords like many plain English words or also many
+stronger passwords which happen to have been leaked will likely be found in the
+database and can thus be rejected.
+
+Example
+-------
+
+The example below can be built and run using `cabal build exe:readme` or `cabal
+repl exe:readme`.
+
+```haskell 
+
+> {-# LANGUAGE OverloadedStrings #-}
+>
+> import Control.Monad.IO.Class (liftIO)
+> import Control.Monad.Logger (runStdoutLoggingT)
+> import Control.Exception (bracket_)
+> import Data.Text as T (pack)
+> import Network.HTTP.Client (newManager)
+> import Network.HTTP.Client.TLS (tlsManagerSettings)
+> import System.IO (hFlush, stdout, hGetEcho, stdin, hSetEcho)
+>
+> import HaveIBeenPwned
+>
+> -- | A really simple demo of the hibp functionality. Asks the user to enter
+> -- a password and then uses the hibp api to check whether that password has
+> -- been pwned.
+> consoleHaveIBeenPwned :: IO ()
+> consoleHaveIBeenPwned = do
+>   runStdoutLoggingT $ do
+>     mgr <- liftIO $ newManager tlsManagerSettings
+>     p <- liftIO $ getPassword
+>     let hibpEnv = HaveIBeenPwnedConfig mgr "https://api.pwnedpasswords.com/range"
+>     p' <- flip runPwnedT hibpEnv $ haveIBeenPwned $ T.pack p
+>     liftIO $ case p' of
+>       HaveIBeenPwnedResult_Secure ->
+>         putStrLn "Your password does not appear in any known breaches.  Practice good password hygene."
+>       HaveIBeenPwnedResult_Pwned p'' ->
+>         putStrLn $ "You have been pwned! Your password has appeared in breaches " ++ show p'' ++ " times."
+>       HaveIBeenPwnedResult_Error ->
+>         putStrLn "Network Error, try again later"
+>
+> getPassword :: IO String
+> getPassword = do
+>   putStr "Password: "
+>   hFlush stdout
+>   password <- withEcho False getLine
+>   putChar '\n'
+>   return password
+>
+> withEcho :: Bool -> IO a -> IO a
+> withEcho echo action = do
+>   old <- hGetEcho stdin
+>   bracket_ (hSetEcho stdin echo) (hSetEcho stdin old) action
+>
+> main :: IO ()
+> main = consoleHaveIBeenPwned
+
+```
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/haveibeenpwned.cabal b/haveibeenpwned.cabal
new file mode 100644
--- /dev/null
+++ b/haveibeenpwned.cabal
@@ -0,0 +1,51 @@
+cabal-version:      2.2
+name:               haveibeenpwned
+version:            0.2.0.0
+synopsis:           Library for checking for weak/compromised passwords.
+description:
+  This library uses the haveibeenpwned database to check for weak or compromised passwords.
+
+bug-reports:        https://github.com/obsidiansystems/haveibeenpwned/issues
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             Obsidian Systems LLC
+maintainer:         maintainer@obsidian.systems
+copyright:          2019 Obsidian Systems LLC
+category:           Web
+extra-source-files: CHANGELOG.md
+tested-with:        GHC ==8.6.5 || ==8.8.4
+
+library
+  exposed-modules:  HaveIBeenPwned
+  build-depends:
+    , base             >=4.11.0   && <4.15
+    , bytestring       ^>=0.10.8
+    , cryptonite       >=0.24     && <0.28
+    , data-default     ^>=0.7.1
+    , http-client      >=0.5.13.1 && <0.7
+    , http-types       ^>=0.12.1
+    , monad-logger     ^>=0.3.29
+    , mtl              ^>=2.2.2
+    , safe             ^>=0.3.17
+    , text             ^>=1.2.3
+
+  ghc-options:      -Wall
+  hs-source-dirs:   src
+  default-language: Haskell2010
+
+executable readme
+  build-depends:
+    , base
+    , haveibeenpwned
+    , http-client
+    , http-client-tls  ^>=0.3.5
+    , monad-logger
+    , text
+
+  default-language: Haskell2010
+  main-is:          README.lhs
+  ghc-options:      -Wall -optL -q
+
+source-repository head
+  type:     git
+  location: https://github.com/obsidiansystems/haveibeenpwned
diff --git a/src/HaveIBeenPwned.hs b/src/HaveIBeenPwned.hs
new file mode 100644
--- /dev/null
+++ b/src/HaveIBeenPwned.hs
@@ -0,0 +1,120 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PackageImports #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+-- | Query haveibeenpwned database to check basic password strength in a secure way.
+--
+--   By checking new user passwords against a database of leaked passwords you
+--   get some means for rejecting very weak or just leaked passwords.
+module HaveIBeenPwned where
+
+import "cryptonite" Crypto.Hash
+import Control.Exception
+import Control.Monad.Logger
+import Control.Monad.Reader
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Lazy as LBS
+import Data.Text (Text)
+import qualified Data.Text as T
+import Data.Text.Encoding (encodeUtf8)
+import qualified Data.Text.Lazy as LT
+import qualified Data.Text.Lazy.Encoding
+import Network.HTTP.Client
+import Network.HTTP.Types.Status (Status(..))
+import Safe (readMay)
+
+data HaveIBeenPwnedConfig = HaveIBeenPwnedConfig
+  { _haveIBeenPwnedConfig_manager :: Manager
+  , _haveIBeenPwnedConfig_apihost :: Text
+  }
+
+-- | Result of a password check.
+--
+--   It is either considered secure, insecure or we can't say because of an
+--   error.
+data HaveIBeenPwnedResult =
+    HaveIBeenPwnedResult_Secure
+    -- ^ We could not find the password in any database, thus it is considered
+    -- "secure" as far as this library is concerned.
+  | HaveIBeenPwnedResult_Pwned Int
+    -- ^ How many times the password was found in public places. Usually this
+    -- will be a value greater than 0, but in any case if you hit this
+    -- constructor you must assume tha password has been leaked.
+  | HaveIBeenPwnedResult_Error
+    -- ^ The check failed for some reason. We can't say anything about the
+    -- password quality.
+  deriving (Eq, Ord, Show)
+
+class Monad m => MonadPwned m where
+  -- | Returns the number of disclosures the supplied password has been seen in.
+  --
+  -- If this is not zero, do not use the supplied password, it is known to hackers.
+  -- If it *is* zero, it might still not be safe, only that if it is
+  -- compromised, that is not yet known.
+  --
+  -- https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange
+  haveIBeenPwned :: Text -> m HaveIBeenPwnedResult
+
+newtype PwnedT m a = PwnedT { unPwnedT :: ReaderT HaveIBeenPwnedConfig m a }
+  deriving (Functor, Applicative, Monad , MonadIO, MonadLogger
+    , MonadTrans
+    )
+
+runPwnedT :: PwnedT m a -> HaveIBeenPwnedConfig -> m a
+runPwnedT (PwnedT (ReaderT f)) = f
+
+mapPwnedT :: (m a -> n b) -> PwnedT m a -> PwnedT n b
+mapPwnedT f = PwnedT . mapReaderT f . unPwnedT
+
+instance MonadReader r m => MonadReader r (PwnedT m) where
+  ask = lift ask
+  local = mapPwnedT . local
+  reader = lift . reader
+
+instance (MonadLogger m, MonadIO m) => MonadPwned (PwnedT m) where
+ haveIBeenPwned password = do
+  let (pfx, rest) = passwdDigest password
+  cfg <- PwnedT ask
+  let request = parseRequest_ $ T.unpack $ T.concat [_haveIBeenPwnedConfig_apihost cfg, "/", pfx]
+  result' <- liftIO $ try $ httpLbs request (_haveIBeenPwnedConfig_manager cfg)
+  case result' of
+    Left err -> do
+      $(logError) $ T.pack $ show @ HttpException $ err
+      return HaveIBeenPwnedResult_Error
+    Right result -> case responseStatus result of
+      Status 200 _ -> do
+        let r = parseHIBPResponse (responseBody result) rest
+        case r of
+          HaveIBeenPwnedResult_Error ->
+            $(logError) $ "Parsing number of occurrences failed. (Not an Int)."
+          _ -> pure ()
+        pure r
+      Status code phrase -> do
+        $(logError) $ T.pack $ show $ Status code phrase
+        return HaveIBeenPwnedResult_Error
+
+
+-- | Get the sha1 digest for the supplied password, split into two parts, to agree with the
+--   hibp api.
+passwdDigest :: Text -> (Text, Text)
+passwdDigest passwd = (T.take 5 digest, T.drop 5 digest)
+  where digest = T.toUpper $ T.pack $ show $ sha1 $ encodeUtf8 passwd
+        sha1 :: ByteString -> Digest SHA1
+        sha1 = hash
+
+-- | The hibp response is a line separated list of colon separated hash
+-- *suffixes* and a number indicating the number of times that password(hash)
+-- has been seen in known publicly disclosed leaks
+parseHIBPResponse :: LBS.ByteString -> Text -> HaveIBeenPwnedResult
+parseHIBPResponse response suffix =
+  let
+    digests :: [(LT.Text, Maybe Int)]
+    digests = fmap (fmap (readMay . LT.unpack . LT.drop 1) . LT.breakOn ":") $ LT.lines $ Data.Text.Lazy.Encoding.decodeUtf8 response
+  in case filter ((LT.fromStrict suffix ==) . fst) digests of
+    ((_,n):_) -> maybe HaveIBeenPwnedResult_Error HaveIBeenPwnedResult_Pwned n
+    [] -> HaveIBeenPwnedResult_Secure
