auth works now

This commit is contained in:
nek0 2022-04-17 16:38:48 +02:00
parent 818ee7421c
commit 48aba74b6d
6 changed files with 77 additions and 35 deletions

View file

@ -72,11 +72,11 @@ authManageNewAuth (Just (uid, method)) (AuthSubmit asmethod ascomment aspayload)
if method == PrimaryPass if method == PrimaryPass
then do then do
salt <- liftIO randomString salt <- liftIO randomString
let mhashstring = decodeUtf8 <$> hash defaultOptions (encodeUtf8 aspayload) salt 64 let mhashstring = hash defaultOptions (encodeUtf8 aspayload) salt 64
case mhashstring of case mhashstring of
CryptoPassed hashstring -> do CryptoPassed hashstring -> do
conn <- asks rsConnection conn <- asks rsConnection
putUserAuthInfo uid asmethod ascomment hashstring conn putUserAuthInfo uid asmethod ascomment salt hashstring conn
CryptoFailed err -> do CryptoFailed err -> do
throwError $ err500 throwError $ err500
{ errBody = "Crypto Error: " <> fromString (show err) { errBody = "Crypto Error: " <> fromString (show err)

View file

@ -9,6 +9,12 @@ import Control.Monad.Reader (asks)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Crypto.Error
import Data.String (fromString)
import Data.Text.Encoding
import Data.Time (getCurrentTime, utctDay) import Data.Time (getCurrentTime, utctDay)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
@ -22,15 +28,25 @@ import Text.Printf (printf)
import Types import Types
import Model import Model
import Util import Util
import Util.Crypto
userNew userNew
:: UserSubmit :: UserSubmit
-> MateHandler Int -> MateHandler Int
userNew (UserSubmit ident email passhash) = do userNew (UserSubmit ident email pass) = do
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
conn <- asks rsConnection conn <- asks rsConnection
uid <- insertUser ident email (utctDay now) conn uid <- insertUser ident email (utctDay now) conn
void $ putUserAuthInfo uid PrimaryPass "Initial password" passhash conn salt <- liftIO randomString
let mpasshash = argon2 salt (encodeUtf8 pass)
case mpasshash of
CryptoPassed passhash -> do
liftIO $ print passhash
void $ putUserAuthInfo uid PrimaryPass "Initial password" salt passhash conn
CryptoFailed err ->
throwError $ err500
{ errBody = "Crypto error: " <> fromString (show err)
}
baseRoleId <- queryRoleIdByCapabilities baseRoleId <- queryRoleIdByCapabilities
(False, False, False, False, False, False, False, False, False, False) (False, False, False, False, False, False, False, False, False, False)
conn conn

View file

@ -2,25 +2,31 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Arrows #-} {-# LANGUAGE Arrows #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE FlexibleContexts #-}
module Model.Auth where module Model.Auth where
import Servant import Servant
import Control.Arrow import Control.Arrow
import Control.Monad (void) import Control.Monad (void, foldM)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (asks) import Control.Monad.Reader (asks)
import Control.Concurrent (threadDelay) import Control.Concurrent (threadDelay)
import Control.Concurrent.STM import Control.Concurrent.STM
import Data.Profunctor.Product (p4, p5) import Crypto.Error
import Data.Profunctor.Product (p4, p6)
import qualified Database.PostgreSQL.Simple as PGS import qualified Database.PostgreSQL.Simple as PGS
import Data.Int (Int64) import Data.Int (Int64)
import Data.String (fromString)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding import Data.Text.Encoding
@ -29,6 +35,7 @@ import qualified Data.Set as S
import Data.Time.Clock import Data.Time.Clock
import Data.ByteString as B (ByteString) import Data.ByteString as B (ByteString)
import Data.ByteString.Base64 (encode)
import Opaleye hiding (null) import Opaleye hiding (null)
@ -77,7 +84,8 @@ initAuthData = mconcat
, "auth_data_user INTEGER NOT NULL REFERENCES \"user\"(\"user_id\") ON DELETE CASCADE," , "auth_data_user INTEGER NOT NULL REFERENCES \"user\"(\"user_id\") ON DELETE CASCADE,"
, "auth_data_method INTEGER NOT NULL," , "auth_data_method INTEGER NOT NULL,"
, "auth_data_comment TEXT NOT NULL," , "auth_data_comment TEXT NOT NULL,"
, "auth_data_payload BYTEA NOT NULL" , "auth_data_payload BYTEA NOT NULL,"
, "auth_data_salt BYTEA NOT NULL"
, ")" , ")"
] ]
@ -87,20 +95,23 @@ authDataTable :: Table
, Field SqlInt4 , Field SqlInt4
, Field SqlText , Field SqlText
, Field SqlBytea , Field SqlBytea
, Field SqlBytea
) )
( Field SqlInt4 ( Field SqlInt4
, Field SqlInt4 , Field SqlInt4
, Field SqlInt4 , Field SqlInt4
, Field SqlText , Field SqlText
, Field SqlBytea , Field SqlBytea
, Field SqlBytea
) )
authDataTable = table "auth_data" ( authDataTable = table "auth_data" (
p5 p6
( tableField "auth_data_id" ( tableField "auth_data_id"
, tableField "auth_data_user" , tableField "auth_data_user"
, tableField "auth_data_method" , tableField "auth_data_method"
, tableField "auth_data_comment" , tableField "auth_data_comment"
, tableField "auth_data_payload" , tableField "auth_data_payload"
, tableField "auth_data_salt"
) )
) )
@ -115,7 +126,7 @@ selectAuthOverviews
-> MateHandler [AuthOverview] -> MateHandler [AuthOverview]
selectAuthOverviews uid conn = do selectAuthOverviews uid conn = do
liftIO $ map fromDatabase <$> runSelect conn (proc () -> do liftIO $ map fromDatabase <$> runSelect conn (proc () -> do
(adid, aduid, admethod, adcomment, _) <- (adid, aduid, admethod, adcomment, _, _) <-
selectTable authDataTable -< () selectTable authDataTable -< ()
restrict -< aduid .== toFields uid restrict -< aduid .== toFields uid
returnA -< (adid, aduid, adcomment, admethod) returnA -< (adid, aduid, adcomment, admethod)
@ -128,7 +139,7 @@ selectAuthOverviewById
-> MateHandler AuthOverview -> MateHandler AuthOverview
selectAuthOverviewById aid conn = do selectAuthOverviewById aid conn = do
liftIO $ fromDatabase . head <$> runSelect conn (limit 1 $ proc () -> do liftIO $ fromDatabase . head <$> runSelect conn (limit 1 $ proc () -> do
(adid, aduid, admethod, adcomment, _) <- (adid, aduid, admethod, adcomment, _, _) <-
selectTable authDataTable -< () selectTable authDataTable -< ()
restrict -< adid .== toFields aid restrict -< adid .== toFields aid
returnA -< (adid, aduid, adcomment, admethod) returnA -< (adid, aduid, adcomment, admethod)
@ -145,11 +156,11 @@ getUserAuthInfo uid method conn = do
liftIO $ do liftIO $ do
void $ threadDelay delayTime void $ threadDelay delayTime
map fromDatabase <$> runSelect conn (proc () -> do map fromDatabase <$> runSelect conn (proc () -> do
(aid, auid, amethod, acomment, apayload) <- (aid, auid, amethod, acomment, apayload, asalt) <-
selectTable authDataTable -< () selectTable authDataTable -< ()
restrict -< restrict -<
auid .== toFields uid .&& amethod .== toFields (fromEnum method) auid .== toFields uid .&& amethod .== toFields (fromEnum method)
returnA -< (aid, auid, amethod, acomment, apayload) returnA -< (aid, auid, amethod, acomment, apayload, asalt)
) :: IO [AuthData] ) :: IO [AuthData]
if null authdata if null authdata
then then
@ -168,10 +179,11 @@ putUserAuthInfo
:: Int :: Int
-> AuthMethod -> AuthMethod
-> T.Text -> T.Text
-> T.Text -> B.ByteString
-> B.ByteString
-> PGS.Connection -> PGS.Connection
-> MateHandler Int -> MateHandler Int
putUserAuthInfo uid method comment payload conn = putUserAuthInfo uid method comment salt payload conn =
fmap head $ liftIO $ runInsert_ conn $ Insert fmap head $ liftIO $ runInsert_ conn $ Insert
{ iTable = authDataTable { iTable = authDataTable
, iRows = , iRows =
@ -180,10 +192,11 @@ putUserAuthInfo uid method comment payload conn =
, toFields uid , toFields uid
, toFields (fromEnum method) , toFields (fromEnum method)
, toFields comment , toFields comment
, toFields (encodeUtf8 payload) , toFields payload
, toFields salt
) )
] ]
, iReturning = rReturning (\(adid, _, _, _, _) -> adid) , iReturning = rReturning (\(adid, _, _, _, _, _) -> adid)
, iOnConflict = Nothing , iOnConflict = Nothing
} }
@ -194,7 +207,7 @@ deleteAuthDataById
-> MateHandler Int64 -> MateHandler Int64
deleteAuthDataById adid conn = liftIO $ runDelete_ conn $ Delete deleteAuthDataById adid conn = liftIO $ runDelete_ conn $ Delete
{ dTable = authDataTable { dTable = authDataTable
, dWhere = \(aid, _, _, _, _) -> aid .== toFields adid , dWhere = \(aid, _, _, _, _, _) -> aid .== toFields adid
, dReturning = rCount , dReturning = rCount
} }
@ -236,16 +249,16 @@ generateToken
generateToken (Ticket _ tuid _ (method, _)) (AuthResponse response) conn = do generateToken (Ticket _ tuid _ (method, _)) (AuthResponse response) conn = do
authData <- liftIO $ map fromDatabase <$> runSelect conn ( authData <- liftIO $ map fromDatabase <$> runSelect conn (
proc () -> do proc () -> do
stuff@(_, auid, amethod, _, _) <- selectTable authDataTable -< () stuff@(_, auid, amethod, _, _, _) <- selectTable authDataTable -< ()
restrict -< (auid .== toFields tuid .&& amethod .== toFields (fromEnum method)) restrict -< (auid .== toFields tuid .&& amethod .== toFields (fromEnum method))
returnA -< stuff returnA -< stuff
) :: MateHandler [AuthData] ) :: MateHandler [AuthData]
let userPayloads = map let userPayloads = map
authDataPayload authDataPayload
authData authData
authResult = case method of authResult <- case method of
PrimaryPass -> validatePass response userPayloads PrimaryPass -> validatePass response authData
SecondaryPass -> validatePass response userPayloads SecondaryPass -> validatePass response authData
ChallengeResponse -> validateChallengeResponse response userPayloads ChallengeResponse -> validateChallengeResponse response userPayloads
-- liftIO $ print (response : userPayloads) -- liftIO $ print (response : userPayloads)
if authResult if authResult
@ -260,8 +273,19 @@ generateToken (Ticket _ tuid _ (method, _)) (AuthResponse response) conn = do
else else
return Denied return Denied
where where
validatePass = validatePass resp =
elem foldM
(\acc AuthData{..} ->
let mresponseHash = argon2 (encodeUtf8 authDataSalt) (encodeUtf8 resp)
in case mresponseHash of
CryptoPassed responseHash ->
return $ acc || responseHash == encodeUtf8 authDataPayload
CryptoFailed err ->
throwError $ err500
{ errBody = "Crypto error: " <> fromString (show err)
}
)
False
validateChallengeResponse _ _ = validateChallengeResponse _ _ =
error "Validation of challenge response authentication not yet implemented" error "Validation of challenge response authentication not yet implemented"
@ -343,7 +367,7 @@ processAuthRequest
-> S.Set Ticket -> S.Set Ticket
-> PGS.Connection -> PGS.Connection
-> MateHandler AuthResult -> MateHandler AuthResult
processAuthRequest (AuthRequest aticket hash) store conn = do processAuthRequest (AuthRequest aticket pass) store conn = do
let mticket = S.filter (\st -> ticketId st == aticket) store let mticket = S.filter (\st -> ticketId st == aticket) store
case S.toList mticket of case S.toList mticket of
[ticket] -> do [ticket] -> do
@ -355,7 +379,7 @@ processAuthRequest (AuthRequest aticket hash) store conn = do
return Denied return Denied
else else
-- liftIO $ putStrLn "...and it is valid" -- liftIO $ putStrLn "...and it is valid"
generateToken ticket hash conn generateToken ticket pass conn
_ -> do _ -> do
liftIO $ threadDelay delayTime liftIO $ threadDelay delayTime
return Denied return Denied

View file

@ -103,7 +103,7 @@ instance FromJSON AuthResponse
data AuthRequest = AuthRequest data AuthRequest = AuthRequest
{ authRequestTicket :: AuthTicket { authRequestTicket :: AuthTicket
, authRequestHash :: AuthResponse , authRequestPassword :: AuthResponse
} }
deriving (Show, Generic) deriving (Show, Generic)
@ -178,6 +178,7 @@ data AuthData = AuthData
, authDataMethod :: AuthMethod , authDataMethod :: AuthMethod
, authDataComment :: T.Text , authDataComment :: T.Text
, authDataPayload :: T.Text , authDataPayload :: T.Text
, authDataSalt :: T.Text
} }
deriving (Show) deriving (Show)
@ -190,10 +191,10 @@ data AuthData = AuthData
instance FromDatabase AuthData where instance FromDatabase AuthData where
type OutTuple AuthData = (Int, Int, Int, T.Text, ByteString) type OutTuple AuthData = (Int, Int, Int, T.Text, ByteString, ByteString)
fromDatabase (id_, usr, method, comm, payload) = fromDatabase (id_, usr, method, comm, payload, salt) =
AuthData id_ usr (toEnum method) comm (decodeUtf8 payload) AuthData id_ usr (toEnum method) comm (decodeUtf8 payload) (decodeUtf8 salt)
data AuthOverview = AuthOverview data AuthOverview = AuthOverview

View file

@ -40,7 +40,7 @@ instance FromDatabase User where
type OutTuple User = (Int, T.Text, Int, Day, Maybe T.Text, Maybe Int) type OutTuple User = (Int, T.Text, Int, Day, Maybe T.Text, Maybe Int)
fromDatabase (id_, ident, bal, ts, email, ava) = fromDatabase (id_, ident, bal, ts, email, ava) =
(User id_ ident bal ts email ava) User id_ ident bal ts email ava
data UserSummary = UserSummary data UserSummary = UserSummary
@ -59,7 +59,7 @@ instance FromJSON UserSummary
data UserSubmit = UserSubmit data UserSubmit = UserSubmit
{ userSubmitIdent :: T.Text { userSubmitIdent :: T.Text
, userSubmitEmail :: Maybe T.Text , userSubmitEmail :: Maybe T.Text
, userSubmitPassHash :: T.Text , userSubmitPassword :: T.Text
} }
deriving (Generic, Show) deriving (Generic, Show)

View file

@ -4,11 +4,12 @@ import Crypto.KDF.Argon2
import Crypto.Error (CryptoFailable) import Crypto.Error (CryptoFailable)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.ByteString.Base64
import System.Random.Stateful (uniformByteStringM, newIOGenM, mkStdGen) import System.Random.Stateful (uniformByteStringM, newIOGenM, mkStdGen)
randomString :: IO BS.ByteString randomString :: IO BS.ByteString
randomString = uniformByteStringM 32 =<< newIOGenM (mkStdGen 23) randomString = encode <$> (uniformByteStringM 32 =<< newIOGenM (mkStdGen 23))
argon2 :: BS.ByteString -> BS.ByteString -> CryptoFailable BS.ByteString argon2 :: BS.ByteString -> BS.ByteString -> CryptoFailable BS.ByteString
argon2 salt payload = hash defaultOptions payload salt 64 argon2 salt payload = encode <$> hash defaultOptions payload (decodeLenient salt) 64