(#18) Creating tokens
This commit is contained in:
parent
0037d4691e
commit
526a2e7ebc
@ -73,6 +73,7 @@ executable ebook-manager
|
|||||||
, servant-server
|
, servant-server
|
||||||
, text
|
, text
|
||||||
, transformers
|
, transformers
|
||||||
|
, uuid
|
||||||
, wai
|
, wai
|
||||||
, warp
|
, warp
|
||||||
, x509
|
, x509
|
||||||
|
1
migrations/V1.2__User_token.sql
Normal file
1
migrations/V1.2__User_token.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
alter table users add column token text null;
|
@ -5,6 +5,8 @@
|
|||||||
{-# Language TypeOperators #-}
|
{-# Language TypeOperators #-}
|
||||||
{-# Language DuplicateRecordFields #-}
|
{-# Language DuplicateRecordFields #-}
|
||||||
{-# Language TypeApplications #-}
|
{-# Language TypeApplications #-}
|
||||||
|
{-# Language GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# Language NamedFieldPuns #-}
|
||||||
module API.Users where
|
module API.Users where
|
||||||
|
|
||||||
import Servant
|
import Servant
|
||||||
@ -40,12 +42,16 @@ instance FromJSON RegisterStatus
|
|||||||
instance FromForm RegisterForm
|
instance FromForm RegisterForm
|
||||||
instance ToForm RegisterForm
|
instance ToForm RegisterForm
|
||||||
|
|
||||||
|
|
||||||
type API = Auth '[SA.BasicAuth, SA.JWT] SafeUser :> "login" :> Get '[JSON] LoginStatus
|
type API = Auth '[SA.BasicAuth, SA.JWT] SafeUser :> "login" :> Get '[JSON] LoginStatus
|
||||||
:<|> "register" :> ReqBody '[JSON, FormUrlEncoded] RegisterForm :> Post '[JSON] RegisterStatus
|
:<|> "register" :> ReqBody '[JSON, FormUrlEncoded] RegisterForm :> Post '[JSON] RegisterStatus
|
||||||
|
:<|> Auth '[SA.BasicAuth] SafeUser :> "token" :> Post '[JSON, PlainText] Token
|
||||||
|
|
||||||
handler :: ServerT API AppM
|
handler :: ServerT API AppM
|
||||||
handler = loginHandler :<|> registerHandler
|
handler = loginHandler :<|> registerHandler :<|> newTokenHandler
|
||||||
|
|
||||||
|
newTokenHandler :: AuthResult SafeUser -> AppM Token
|
||||||
|
newTokenHandler = requireLoggedIn $ \SafeUser{username} ->
|
||||||
|
runDB (createToken username)
|
||||||
|
|
||||||
loginHandler :: AuthResult SafeUser -> AppM LoginStatus
|
loginHandler :: AuthResult SafeUser -> AppM LoginStatus
|
||||||
loginHandler (Authenticated u) = return (LoginStatus (Just u))
|
loginHandler (Authenticated u) = return (LoginStatus (Just u))
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
{-# Language OverloadedStrings #-}
|
{-# Language OverloadedStrings #-}
|
||||||
{-# Language DuplicateRecordFields #-}
|
{-# Language DuplicateRecordFields #-}
|
||||||
{-# Language GeneralizedNewtypeDeriving #-}
|
{-# Language GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# Language MultiParamTypeClasses #-}
|
||||||
module Database.Schema where
|
module Database.Schema where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
@ -13,6 +14,11 @@ import Database.Selda.Backend
|
|||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Web.HttpApiData
|
import Web.HttpApiData
|
||||||
|
|
||||||
|
import Data.UUID (UUID)
|
||||||
|
import qualified Data.UUID as UUID
|
||||||
|
|
||||||
|
import Servant (MimeRender(..), PlainText)
|
||||||
|
|
||||||
-- | User type
|
-- | User type
|
||||||
newtype PlainPassword = PlainPassword Text deriving (Show, ToJSON, FromJSON, ToHttpApiData, FromHttpApiData, Eq)
|
newtype PlainPassword = PlainPassword Text deriving (Show, ToJSON, FromJSON, ToHttpApiData, FromHttpApiData, Eq)
|
||||||
newtype HashedPassword = HashedPassword {unHashed :: ByteString}
|
newtype HashedPassword = HashedPassword {unHashed :: ByteString}
|
||||||
@ -70,11 +76,24 @@ instance SqlType TagID where
|
|||||||
fromSql _ = error "fromSql: Bad tagid"
|
fromSql _ = error "fromSql: Bad tagid"
|
||||||
defaultValue = mkLit (TagID (-1))
|
defaultValue = mkLit (TagID (-1))
|
||||||
|
|
||||||
|
newtype Token = Token { unToken :: UUID } deriving (Show, ToJSON)
|
||||||
|
|
||||||
|
instance MimeRender PlainText Token where
|
||||||
|
mimeRender _ = UUID.toLazyASCIIBytes . unToken
|
||||||
|
|
||||||
|
instance SqlType Token where
|
||||||
|
mkLit = LCustom . LText . UUID.toText . unToken
|
||||||
|
fromSql (SqlString x) = maybe (error "fromSql: Could not parse token") Token . UUID.fromText $ x
|
||||||
|
fromSql _ = error "fromSql: Could not parse token"
|
||||||
|
defaultValue = mkLit (Token UUID.nil)
|
||||||
|
|
||||||
data User pass = User { identifier :: UserID
|
data User pass = User { identifier :: UserID
|
||||||
, email :: Email
|
, email :: Email
|
||||||
, username :: Username
|
, username :: Username
|
||||||
, role :: Role
|
, role :: Role
|
||||||
, password :: pass }
|
, token :: Maybe Token
|
||||||
|
, password :: pass
|
||||||
|
}
|
||||||
deriving (Show, Generic)
|
deriving (Show, Generic)
|
||||||
|
|
||||||
data Role = UserRole | AdminRole deriving (Show, Read, Enum, Bounded, Typeable, Generic)
|
data Role = UserRole | AdminRole deriving (Show, Read, Enum, Bounded, Typeable, Generic)
|
||||||
|
@ -2,7 +2,15 @@
|
|||||||
{-# Language TypeApplications #-}
|
{-# Language TypeApplications #-}
|
||||||
{-# Language DataKinds #-}
|
{-# Language DataKinds #-}
|
||||||
{-# Language TemplateHaskell #-}
|
{-# Language TemplateHaskell #-}
|
||||||
module Database.User where
|
{-# Language FlexibleContexts #-}
|
||||||
|
module Database.User
|
||||||
|
( Token
|
||||||
|
, insertUser
|
||||||
|
, getUser
|
||||||
|
, validateUser
|
||||||
|
, createToken
|
||||||
|
, invalidateToken )
|
||||||
|
where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Database
|
import Database
|
||||||
@ -14,10 +22,12 @@ import Crypto.KDF.BCrypt
|
|||||||
import Crypto.Random.Types (MonadRandom)
|
import Crypto.Random.Types (MonadRandom)
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Logger
|
||||||
import Control.Monad (mfilter)
|
import Control.Monad (mfilter)
|
||||||
|
import qualified Data.UUID.V4 as UUID
|
||||||
|
|
||||||
data UserExistsError = UserExistsError
|
data UserExistsError = UserExistsError
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
insertUser :: (MonadLogger m, MonadIO m, MonadMask m, MonadRandom m) => Username -> Email -> PlainPassword -> SeldaT m (Either UserExistsError (User NoPassword))
|
insertUser :: (MonadLogger m, MonadIO m, MonadMask m, MonadRandom m) => Username -> Email -> PlainPassword -> SeldaT m (Either UserExistsError (User NoPassword))
|
||||||
insertUser username email (PlainPassword password) =
|
insertUser username email (PlainPassword password) =
|
||||||
getUser' username >>= maybe insert' (const (return $ Left UserExistsError))
|
getUser' username >>= maybe insert' (const (return $ Left UserExistsError))
|
||||||
@ -26,7 +36,7 @@ insertUser username email (PlainPassword password) =
|
|||||||
insertAs role = do
|
insertAs role = do
|
||||||
lift $ $logInfo $ "Inserting new user as " <> pack (show role)
|
lift $ $logInfo $ "Inserting new user as " <> pack (show role)
|
||||||
let bytePass = encodeUtf8 password
|
let bytePass = encodeUtf8 password
|
||||||
user <- User def email username role . HashedPassword <$> lift (hashPassword 12 bytePass)
|
user <- User def email username role Nothing . HashedPassword <$> lift (hashPassword 12 bytePass)
|
||||||
insert_ (gen users) [toRel user] >> return (over (field @"password") (const NoPassword) user)
|
insert_ (gen users) [toRel user] >> return (over (field @"password") (const NoPassword) user)
|
||||||
|
|
||||||
adminExists :: (MonadMask m, MonadLogger m, MonadIO m) => SeldaT m Bool
|
adminExists :: (MonadMask m, MonadLogger m, MonadIO m) => SeldaT m Bool
|
||||||
@ -58,3 +68,21 @@ getUser' name = listToMaybe . fmap fromRel <$> query q
|
|||||||
u@(_ :*: _ :*: username :*: _ ) <- select (gen users)
|
u@(_ :*: _ :*: username :*: _ ) <- select (gen users)
|
||||||
restrict (username .== literal name)
|
restrict (username .== literal name)
|
||||||
return u
|
return u
|
||||||
|
|
||||||
|
createToken :: (MonadMask m, MonadIO m, MonadSelda m) => Username -> m Token
|
||||||
|
createToken username = do
|
||||||
|
token <- Token <$> liftIO UUID.nextRandom
|
||||||
|
void $ update (gen users) predicate (updateToken token)
|
||||||
|
return token
|
||||||
|
where
|
||||||
|
_ :*: _ :*: pUsername :*: _ :*: pToken :*: _ = selectors (gen users)
|
||||||
|
predicate user = user ! pUsername .== literal username
|
||||||
|
updateToken token user= user `with` [pToken := literal (Just token)]
|
||||||
|
|
||||||
|
invalidateToken :: (MonadMask m, MonadIO m, MonadSelda m) => Username -> m ()
|
||||||
|
invalidateToken username = do
|
||||||
|
void $ update (gen users) predicate updateToken
|
||||||
|
where
|
||||||
|
_ :*: _ :*: pUsername :*: _ :*: pToken :*: _ = selectors (gen users)
|
||||||
|
predicate user = user ! pUsername .== literal username
|
||||||
|
updateToken user= user `with` [pToken := literal Nothing]
|
||||||
|
Loading…
Reference in New Issue
Block a user