ebook-manager/src/API/Users.hs

91 lines
3.1 KiB
Haskell
Raw Normal View History

2018-08-03 23:36:38 +03:00
{-# Language DataKinds #-}
{-# Language TypeFamilies #-}
{-# Language OverloadedStrings #-}
{-# Language NoImplicitPrelude #-}
{-# Language TypeOperators #-}
{-# Language DuplicateRecordFields #-}
2018-08-04 22:05:41 +03:00
{-# Language TypeApplications #-}
module API.Users where
2018-08-03 23:36:38 +03:00
import Servant
import ClassyPrelude
import Types
import Data.Aeson
import Web.FormUrlEncoded
import Database (runDB)
import Database.User
2018-08-04 22:05:41 +03:00
import Database.Schema
import Control.Lens (view)
import Data.Generics.Product
2018-08-04 23:43:26 +03:00
import Servant.Auth as SA
import Servant.Auth.Server as SAS
2018-08-03 23:36:38 +03:00
2018-08-04 22:05:41 +03:00
data RegisterForm = RegisterForm { username :: Username
, email :: Email
, password :: PlainPassword
, passwordAgain :: PlainPassword }
2018-08-03 23:36:38 +03:00
deriving (Generic, Show)
2018-08-04 22:05:41 +03:00
data LoginStatus = LoginStatus ( Maybe SafeUser ) deriving Generic
2018-08-03 23:36:38 +03:00
data RegisterStatus = RegisterStatus deriving Generic
instance ToJSON LoginStatus
instance FromJSON LoginStatus
instance FromJSON RegisterForm
instance ToJSON RegisterForm
instance ToJSON RegisterStatus
instance FromJSON RegisterStatus
instance FromForm RegisterForm
instance ToForm RegisterForm
2018-08-04 22:05:41 +03:00
-- generic-lens can convert similar types to this
2018-08-04 23:43:26 +03:00
-- I'm trying out servant-auth-server which uses a jwt style login. IIRC anyone
-- can open the jwt token and view what's inside, you just can't modify it.
--
-- Is it a problem that a human readable username and email are visible?
2018-08-04 22:05:41 +03:00
data SafeUser = SafeUser { email :: Email
, username :: Username
, role :: Role }
deriving (Show, Generic)
instance ToJSON SafeUser where
instance FromJSON SafeUser where
2018-08-04 23:43:26 +03:00
instance ToJWT SafeUser where
instance FromJWT SafeUser where
2018-08-04 22:05:41 +03:00
2018-08-04 23:43:26 +03:00
authCheck :: App -> BasicAuthData -> IO (AuthResult SafeUser)
authCheck app (BasicAuthData username password) = flip runReaderT app $
maybe SAS.Indefinite authenticated <$> runDB (validateUser username' password')
where
username' = Username $ decodeUtf8 username
password' = PlainPassword $ decodeUtf8 password
authenticated = SAS.Authenticated . view (super @SafeUser)
type instance BasicAuthCfg = BasicAuthData -> IO (AuthResult SafeUser)
instance FromBasicAuthData SafeUser where
fromBasicAuthData authData authCheckFunction = authCheckFunction authData
type API = Auth '[SA.BasicAuth] SafeUser :> "login" :> Get '[JSON] LoginStatus
2018-08-03 23:36:38 +03:00
:<|> "register" :> ReqBody '[JSON, FormUrlEncoded] RegisterForm :> Post '[JSON] RegisterStatus
handler :: ServerT API AppM
handler = loginHandler :<|> registerHandler
2018-08-04 23:43:26 +03:00
loginHandler :: AuthResult SafeUser -> AppM LoginStatus
loginHandler (Authenticated u) = return (LoginStatus (Just u))
loginHandler _ = return (LoginStatus Nothing)
2018-08-03 23:36:38 +03:00
registerHandler :: RegisterForm -> AppM RegisterStatus
registerHandler RegisterForm{..} =
case () of
() | password /= passwordAgain -> noMatch
| otherwise ->
2018-08-04 22:05:41 +03:00
either (const alreadyExists) (const (pure RegisterStatus)) =<< runDB (insertUser username email password)
2018-08-03 23:36:38 +03:00
where
noMatch = throwM err403{errBody = "passwords don't match"}
alreadyExists = throwM err403{errBody = "User already exists"}