halogen-websocket-ipc-playzone/src/App/Messages/AuthenticationDaemon.purs

206 lines
9.6 KiB
Plaintext

module App.Messages.AuthenticationDaemon where
import Prelude (bind, pure, show, ($))
import Effect (Effect)
import Data.Argonaut.Core as J
import Data.Codec.Argonaut as CA
import Data.Maybe (Maybe)
import Data.Either (Either(..))
import Data.Codec.Argonaut.Record as CAR
import Data.UInt (fromInt, toInt, UInt)
import Data.Tuple (Tuple(..))
import Data.ArrayBuffer.Types (ArrayBuffer)
import App.Email as Email
import App.Phone as Phone
import App.UserPublic as UserPublic
import Effect.Class (liftEffect)
import Data.Argonaut.Parser as JSONParser
import Data.Bifunctor (lmap)
import App.IPC as IPC
{- TODO:
Possible requests:
- 1 type AddUser = { shared_key :: String, login :: String, password :: String, email :: Maybe String, phone :: Maybe Phone.Phone, profile :: Maybe Hash(String, JSON::Any) }
- 2 type ValidateUser = { login :: String, activation_key :: String }
- 3 type GetUser = { user :: Int32 | String }
- 4 type GetUserByCredentials = { login :: String, password :: String }
- 6 type Register = { login :: String, password :: String, email :: Maybe String, phone :: Maybe Phone.Phone , profile :: Maybe Hash(String, JSON::Any) }
- 7 type UpdatePassword = { login :: String, old_password :: String, new_password :: String }
- 8 type ListUsers = { token :: Maybe String, key :: Maybe String }
- 9 type CheckPermission = { shared_key :: Maybe String, token :: Maybe String, user :: Int32 | String, service :: String, resource :: String }
- 10 type SetPermission = { shared_key :: String, user :: Int32 | String, service :: String, resource :: String, permission :: AuthD::User::PermissionLevel }
- 11 type PasswordRecovery = { user :: Int32 | String, password_renew_key :: String, new_password :: String }
- 12 type AskPasswordRecovery = { user :: Int32 | String, email :: String }
- 13 type SearchUser = { user :: String }
- 14 type EditProfile = { token :: String, new_profile :: Hash(String, JSON::Any) }
- 15 type EditProfileContent = { token :: Maybe String, shared_key :: Maybe String, user :: Int32 | String | Nil, new_profile :: Hash(String, JSON::Any) }
- 16 type EditContacts = { token :: String, email :: Maybe String, phone :: Maybe Phone.Phone }
- 17 type Delete = { shared_key :: Maybe String, login :: Maybe String, password :: Maybe String, user :: String | Int32 }
- 18 type GetContacts = { token :: String }
-- Deletion can be triggered by either an admin or the user.
Possible answers:
- 6 type UsersList = { users :: Array(UserPublic.UserPublic) }
- 7 type PermissionCheck = { user :: Int32, service :: String, resource :: String, permission :: AuthD::User::PermissionLevel }
- 8 type PermissionSet = { user :: Int32, service :: String, resource :: String, permission :: AuthD::User::PermissionLevel }
- 11 type MatchingUsers = { users :: Array(UserPublic.UserPublic) }
-}
-- Basic message types.
-- TODO: note to myself: messages seem chaotic. Could be simpler. Should be simpler.
type Error = { reason :: Maybe String }
type Token = { uid :: Int, token :: String }
type PasswordRecoverySent = { user :: UserPublic.UserPublic }
type PasswordRecovered = { user :: UserPublic.UserPublic }
type Contacts = { user :: Int, email :: Maybe Email.Email, phone :: Maybe Phone.Phone }
type User = { user :: UserPublic.UserPublic }
type UserAdded = { user :: UserPublic.UserPublic }
type UserEdited = { uid :: Int }
type UserValidated = { user :: UserPublic.UserPublic }
type Password = String
type GetToken = { login :: String, password :: String }
-- Related JSON codecs.
codecGetToken ∷ CA.JsonCodec GetToken
codecGetToken = CA.object "GetToken" (CAR.record { login: CA.string, password: CA.string })
codecGotError ∷ CA.JsonCodec Error
codecGotError = CA.object "Error" (CAR.record { reason: CAR.optional CA.string })
codecGotToken ∷ CA.JsonCodec Token
codecGotToken = CA.object "Token" (CAR.record { "uid": CA.int, "token": CA.string })
codecGotPasswordRecoverySent ∷ CA.JsonCodec PasswordRecoverySent
codecGotPasswordRecoverySent = CA.object "PasswordRecoverySent" (CAR.record { user: UserPublic.codec })
codecGotUser ∷ CA.JsonCodec User
codecGotUser = CA.object "User" (CAR.record { user: UserPublic.codec })
codecGotUserAdded ∷ CA.JsonCodec UserAdded
codecGotUserAdded = CA.object "UserAdded" (CAR.record { user: UserPublic.codec })
codecGotUserEdited ∷ CA.JsonCodec UserEdited
codecGotUserEdited = CA.object "UserEdited" (CAR.record { "uid": CA.int })
codecGotUserValidated ∷ CA.JsonCodec UserValidated
codecGotUserValidated = CA.object "UserValidated" (CAR.record { user: UserPublic.codec })
codecGotPasswordRecovered ∷ CA.JsonCodec PasswordRecovered
codecGotPasswordRecovered = CA.object "PasswordRecovered" (CAR.record { user: UserPublic.codec })
codecGotContacts ∷ CA.JsonCodec Contacts
codecGotContacts = CA.object "Contacts" (CAR.record { user: CA.int
, email: CAR.optional Email.codec
, phone: CAR.optional Phone.codec })
-- All possible requests.
data RequestMessage
= MkGetToken GetToken -- 0
--| MkAddUser AddUser -- 1
--| MkValidateUser ValidateUser -- 2
--| MkGetUser GetUser -- 3
--| MkGetUserByCredentials GetUserByCredentials -- 4
--| MkRegister Register -- 6
--| MkUpdatePassword UpdatePassword -- 7
--| MkListUsers ListUsers -- 8
--| MkCheckPermission CheckPermission -- 9
--| MkSetPermission SetPermission -- 10
--| MkPasswordRecovery PasswordRecovery -- 11
--| MkAskPasswordRecovery AskPasswordRecovery -- 12
--| MkSearchUser SearchUser -- 13
--| MkEditProfile EditProfile -- 14
--| MkEditProfileContent EditProfileContent -- 15
--| MkEditContacts EditContacts -- 16
--| MkDelete Delete -- 17
--| MkGetContacts GetContacts -- 18
-- All possible answers from the authentication daemon (authd).
data AnswerMessage
= GotError Error -- 0
| GotToken Token -- 1
| GotUser User -- 2
| GotUserAdded UserAdded -- 3
| GotUserEdited UserEdited -- 4
| GotUserValidated UserValidated -- 5
-- | GotUsersList UsersList -- 6
-- | GotPermissionCheck PermissionCheck -- 7
-- | GotPermissionSet PermissionSet -- 8
| GotPasswordRecoverySent PasswordRecoverySent -- 9
| GotPasswordRecovered PasswordRecovered -- 10
-- | GotMatchingUsers MatchingUsers -- 11
| GotContacts Contacts -- 12
encode ∷ RequestMessage -> Tuple UInt String
encode m = case m of
(MkGetToken token) -> Tuple (fromInt 0) (J.stringify $ CA.encode codecGetToken token)
-- 1 AddUser
-- 2 ValidateUser
-- 3 GetUser
-- 4 GetUserByCredentials
-- 6 Register
-- 7 UpdatePassword
-- 8 ListUsers
-- 9 CheckPermission
-- 10 SetPermission
-- 11 PasswordRecovery
-- 12 AskPasswordRecovery
-- 13 SearchUser
-- 14 EditProfile
-- 15 EditProfileContent
-- 16 EditContacts
-- 17 Delete
-- 18 GetContacts
data DecodeError
= JSONERROR String
| UnknownError String
| UnknownNumber
decode :: Int -> String -> Either DecodeError AnswerMessage
decode number string
= case number of
0 -> error_management codecGotError GotError
1 -> error_management codecGotToken GotToken
2 -> error_management codecGotUser GotUser
3 -> error_management codecGotUserAdded GotUserAdded
4 -> error_management codecGotUserEdited GotUserEdited
5 -> error_management codecGotUserValidated GotUserValidated
9 -> error_management codecGotPasswordRecoverySent GotPasswordRecoverySent
10 -> error_management codecGotPasswordRecovered GotPasswordRecovered
12 -> error_management codecGotContacts GotContacts
_ -> Left UnknownNumber
-- 6 type UsersList = { users :: Array(UserPublic.UserPublic) }
-- 7 type PermissionCheck = { user :: Int32, service :: String, resource :: String, permission :: AuthD::User::PermissionLevel }
-- 8 type PermissionSet = { user :: Int32, service :: String, resource :: String, permission :: AuthD::User::PermissionLevel }
-- 11 type MatchingUsers = { users :: Array(UserPublic.UserPublic) }
where
-- Signature is required since the compiler's guess is wrong.
error_management :: forall a. CA.JsonCodec a -> (a -> AnswerMessage) -> Either DecodeError AnswerMessage
error_management codec f
= case (parseDecodeJSON codec string) of
(Left err) -> Left (JSONERROR err)
(Right v) -> Right (f v)
parseDecodeJSON :: forall a. CA.JsonCodec a -> String -> Either String a
parseDecodeJSON codec str = do
json <- JSONParser.jsonParser str
lmap CA.printJsonDecodeError (CA.decode codec json)
serialize :: RequestMessage -> Effect ArrayBuffer
serialize request
= case (encode request) of
(Tuple messageTypeNumber string) -> IPC.toTypedIPC messageTypeNumber string
deserialize :: ArrayBuffer -> Effect (Either DecodeError AnswerMessage)
deserialize arraybuffer
= do
value <- liftEffect $ IPC.fromTypedIPC arraybuffer
pure $ case (value) of
Left err -> Left (UnknownError $ show err)
Right (Tuple messageTypeNumber string) -> case (decode (toInt messageTypeNumber) string) of
Left parsingError -> Left parsingError
Right answerMessage -> Right answerMessage