(#18) Creating tokens
This commit is contained in:
		@@ -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]
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user