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"}
|