summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNCrashed <>2018-09-14 12:10:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2018-09-14 12:10:00 (GMT)
commitfdac40f233d8a7ccadff433fb42956a69798bbd9 (patch)
treeece948d69ff66b82ea6c61675970508f6ad435e2
parent0d1b0a10303aa793dbbeb089dcec91bf0fa3c157 (diff)
version 0.5.5.00.5.5.0
-rw-r--r--CHANGELOG.md5
-rw-r--r--servant-auth-token.cabal3
-rw-r--r--src/Servant/Server/Auth/Token.hs42
-rw-r--r--stack.yaml2
4 files changed, 48 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 632c717..461a785 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+0.5.5.0
+=======
+
+* Allow passing client side hashed password to signin endpoint.
+
0.5.4.0
=======
diff --git a/servant-auth-token.cabal b/servant-auth-token.cabal
index 3a0a898..7d67476 100644
--- a/servant-auth-token.cabal
+++ b/servant-auth-token.cabal
@@ -1,5 +1,5 @@
name: servant-auth-token
-version: 0.5.4.0
+version: 0.5.5.0
synopsis: Servant based API and server for token based authorisation
description: Please see README.md
homepage: https://github.com/ncrashed/servant-auth-token#readme
@@ -62,6 +62,7 @@ library
build-depends:
base >= 4.8 && < 5
, aeson-injector >= 1.1 && < 1.2
+ , byteable >= 0.1 && < 0.2
, bytestring >= 0.10 && < 0.11
, containers >= 0.5 && < 0.6
, http-api-data >= 0.3.5 && < 0.4
diff --git a/src/Servant/Server/Auth/Token.hs b/src/Servant/Server/Auth/Token.hs
index c093b12..40f511c 100644
--- a/src/Servant/Server/Auth/Token.hs
+++ b/src/Servant/Server/Auth/Token.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE UndecidableInstances #-}
{-|
Module : Servant.Server.Auth.Token
@@ -78,6 +79,7 @@ import Control.Monad.Except
import Crypto.PasswordStore
import Data.Aeson.Unit
import Data.Aeson.WithField
+import Data.Byteable (Byteable, toBytes, constEqBytes)
import Data.Maybe
import Data.Monoid
import Data.Text (Text)
@@ -98,6 +100,7 @@ import Servant.Server.Auth.Token.Pagination
import Servant.Server.Auth.Token.Restore
import Servant.Server.Auth.Token.SingleUse
+import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BS
-- | Implementation of AuthAPI
@@ -128,7 +131,18 @@ authServer =
:<|> authGetUserIdMethod
:<|> authFindUserByLogin
--- | Implementation of "signin" method
+-- | Implementation of "signin" method.
+--
+-- You can pass hashed password in format of `pwstore`. The library will
+-- strengthen the hash and compare with hash in DB in this case. The feature
+-- allow you to previously hash on client side to not pass the password as plain
+-- text to server. Note that you should have the same salt in the passwords.
+--
+-- Also avoid having strength of hashed password less that is passed from client side.
+--
+-- Format of hashed password: "sha256|strength|salt|hash", where strength is an unsigned int, salt
+-- is a base64-encoded 16-byte random number, and hash is a base64-encoded hash
+-- value.
authSignin :: AuthHandler m
=> Maybe Login -- ^ Login query parameter
-> Maybe Password -- ^ Password query parameter
@@ -140,15 +154,39 @@ authSignin mlogin mpass mexpire = do
WithField uid UserImpl{..} <- guardLogin login pass
OnlyField <$> getAuthToken uid mexpire
where
+ checkPassword pass uimpl@UserImpl{..} = case readPwHash pass of
+ Nothing -> pass `verifyPassword` passToByteString userImplPassword
+ Just (passedStrength, passedSalt, passedHash) -> case readPwHash $ passToByteString userImplPassword of
+ Nothing -> False
+ Just (storedStrength, storedSalt, storedHash) -> if
+ | not (passedSalt `constEqBytes` storedSalt) -> False
+ | passedStrength == storedStrength -> passedHash `constEqBytes` storedHash
+ | passedStrength < storedStrength -> let
+ newPass = strengthenPassword pass storedStrength
+ in checkPassword newPass uimpl
+ | otherwise -> let
+ newUserPass = strengthenPassword (passToByteString userImplPassword) passedStrength
+ in checkPassword pass uimpl { userImplPassword = byteStringToPass newUserPass }
guardLogin login pass = do -- check login and password, return passed user
muser <- getUserImplByLogin login
let err = throw401 "Cannot find user with given combination of login and pass"
case muser of
Nothing -> err
- Just user@(WithField _ UserImpl{..}) -> if passToByteString pass `verifyPassword` passToByteString userImplPassword
+ Just user@(WithField _ uimpl) -> if checkPassword (passToByteString pass) uimpl
then return user
else err
+-- | Try to parse a password hash.
+readPwHash :: BC.ByteString -> Maybe (Int, BC.ByteString, BC.ByteString)
+readPwHash pw | length broken /= 4
+ || algorithm /= "sha256"
+ || BC.length hash /= 44 = Nothing
+ | otherwise = case BC.readInt strBS of
+ Just (strength, _) -> Just (strength, salt, hash)
+ Nothing -> Nothing
+ where broken = BC.split '|' pw
+ [algorithm, strBS, salt, hash] = broken
+
-- | Implementation of "signin" method
authSigninPost :: AuthHandler m
=> AuthSigninPostBody -- ^ Holds login, password and token lifetime
diff --git a/stack.yaml b/stack.yaml
index 299deac..8c8db0b 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -43,7 +43,7 @@ packages:
#- 'servant-auth-token-rocksdb'
- 'example/acid'
- 'example/persistent'
-#- 'example/leveldb'
+- 'example/leveldb'
#- '../servant-auth-token-api'
# - location:
# git: https://github.com/NCrashed/servant-auth-token-api.git