diff --git a/src/App/AuthenticationInterface.purs b/src/App/AuthenticationInterface.purs index 7a590aa..1ab3e12 100644 --- a/src/App/AuthenticationInterface.purs +++ b/src/App/AuthenticationInterface.purs @@ -2,8 +2,9 @@ -- | TODO: token validation. module App.AuthenticationInterface where -import Prelude (Unit, bind, discard, pure, ($), (<<<), (<>)) +import Prelude (Unit, bind, discard, pure, ($), (<<<), (<>), (>), (==)) +import Data.Array as A import Data.ArrayBuffer.Types (ArrayBuffer) import Data.Maybe (Maybe(..)) import Data.Tuple (Tuple(..)) @@ -21,6 +22,10 @@ import App.Email as Email import App.LogMessage import App.Messages.AuthenticationDaemon as AuthD +type Login = String +type Password = String +type PasswordRecoveryToken = String + -- | The component can inform the parent (`App.Container`) that the authentication is complete, -- | and share both the uid and token. The token is useful to authenticate the user to the -- | dnsmanager daemon. @@ -30,8 +35,9 @@ import App.Messages.AuthenticationDaemon as AuthD -- | TODO: authentication is performed in `App.Container`. data Output = MessageToSend ArrayBuffer - | AuthenticateToAuthd (Tuple String String) -- Login Password + | AuthenticateToAuthd (Tuple Login Password) | Log LogMessage + | PasswordRecovery Login PasswordRecoveryToken Password -- | The component's parent provides received messages. -- | @@ -53,19 +59,29 @@ data PasswordRecoveryInput = PASSR_INP_login String | PASSR_INP_email String +data NewPasswordInput + = NEWPASS_INP_login String + | NEWPASS_INP_token String + | NEWPASS_INP_password String + | NEWPASS_INP_confirmation String + data Action = HandleAuthenticationInput AuthenticationInput | HandlePasswordRecovery PasswordRecoveryInput + | HandleNewPassword NewPasswordInput -- | AuthenticationAttempt Event | PasswordRecoveryAttempt Event + | NewPasswordAttempt Event type StateAuthenticationForm = { login :: String, pass :: String } type StatePasswordRecoveryForm = { login :: String, email :: String } +type StateNewPasswordForm = { login :: String, token :: String, password :: String, confirmation :: String } type State = { authenticationForm :: StateAuthenticationForm , passwordRecoveryForm :: StatePasswordRecoveryForm + , newPasswordForm :: StateNewPasswordForm , wsUp :: Boolean } @@ -84,23 +100,24 @@ initialState :: Input -> State initialState _ = { authenticationForm: { login: "", pass: "" } , passwordRecoveryForm: { login: "", email: "" } - + , newPasswordForm: { login: "", token: "", password: "", confirmation: "" } , wsUp: true } render :: forall m. State -> H.ComponentHTML Action () m -render { wsUp, authenticationForm, passwordRecoveryForm} - = Bulma.section_small - [ case wsUp of - false -> Bulma.p "You are disconnected." - true -> Bulma.columns_ [ b auth_form, b passrecovery_form ] - ] +render { wsUp, authenticationForm, passwordRecoveryForm, newPasswordForm} = + Bulma.section_small + [ case wsUp of + false -> Bulma.p "You are disconnected." + true -> Bulma.columns_ [ b auth_form, b passrecovery_form, b newpass_form ] + ] where b e = Bulma.column_ [ Bulma.box e ] auth_form = [ Bulma.h3 "Authentication" , render_auth_form ] passrecovery_form = [ Bulma.h3 "Password Recovery", render_password_recovery_form ] + newpass_form = [ Bulma.h3 "New password", render_new_password_form ] should_be_disabled = (if wsUp then (HP.enabled true) else (HP.disabled true)) @@ -140,6 +157,33 @@ render { wsUp, authenticationForm, passwordRecoveryForm} [ HH.text "Send Message to Server" ] ] + render_new_password_form = HH.form + [ HE.onSubmit NewPasswordAttempt ] + [ Bulma.box_input "loginNEWPASS" "Login" "login" + (HandleNewPassword <<< NEWPASS_INP_login) + newPasswordForm.login + should_be_disabled + , Bulma.box_input "tokenNEWPASS" "Token" "token" + (HandleNewPassword <<< NEWPASS_INP_token) + newPasswordForm.token + should_be_disabled + , Bulma.box_input "passwordNEWPASS" "Password" "password" + (HandleNewPassword <<< NEWPASS_INP_password) + newPasswordForm.password + should_be_disabled + , Bulma.box_input "confirmationNEWPASS" "Confirmation" "confirmation" + (HandleNewPassword <<< NEWPASS_INP_confirmation) + newPasswordForm.confirmation + should_be_disabled + , HH.button + [ HP.style "padding: 0.5rem 1.25rem;" + , HP.type_ HP.ButtonSubmit + , (if wsUp then (HP.enabled true) else (HP.disabled true)) + ] + [ HH.text "Send Message to Server" ] + ] + + handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit handleAction = case _ of HandleAuthenticationInput authinp -> do @@ -147,11 +191,18 @@ handleAction = case _ of AUTH_INP_login v -> H.modify_ _ { authenticationForm { login = v } } AUTH_INP_pass v -> H.modify_ _ { authenticationForm { pass = v } } - HandlePasswordRecovery authinp -> do - case authinp of + HandlePasswordRecovery passrecovinp -> do + case passrecovinp of PASSR_INP_login v -> H.modify_ _ { passwordRecoveryForm { login = v } } PASSR_INP_email v -> H.modify_ _ { passwordRecoveryForm { email = v } } + HandleNewPassword newpassinp -> do + case newpassinp of + NEWPASS_INP_login v -> H.modify_ _ { newPasswordForm { login = v } } + NEWPASS_INP_token v -> H.modify_ _ { newPasswordForm { token = v } } + NEWPASS_INP_password v -> H.modify_ _ { newPasswordForm { password = v } } + NEWPASS_INP_confirmation v -> H.modify_ _ { newPasswordForm { confirmation = v } } + AuthenticationAttempt ev -> do H.liftEffect $ Event.preventDefault ev @@ -168,6 +219,19 @@ handleAction = case _ of H.raise $ AuthenticateToAuthd (Tuple login pass) H.raise $ Log $ SystemLog $ "authenticate (login: " <> login <> ")" + -- TODO: verify the login? + NewPasswordAttempt ev -> do + H.liftEffect $ Event.preventDefault ev + + { newPasswordForm } <- H.get + let { login, token, password, confirmation} = newPasswordForm + + if A.any (_ == "") [ login, token, password, confirmation ] + then H.raise $ Log $ ErrorLog "All entries are required!" + else if password == confirmation + then H.raise $ PasswordRecovery login token password + else H.raise $ Log $ UnableToSend "Confirmation differs from password!" + PasswordRecoveryAttempt ev -> do H.liftEffect $ Event.preventDefault ev diff --git a/src/App/Container.purs b/src/App/Container.purs index 4f6a0ec..6e0f0be 100644 --- a/src/App/Container.purs +++ b/src/App/Container.purs @@ -341,6 +341,13 @@ handleAction = case _ of AuthenticationInterfaceEvent ev -> case ev of AI.MessageToSend message -> H.tell _ws_auth unit (WS.ToSend message) + AI.PasswordRecovery login token pass -> do + message <- H.liftEffect $ AuthD.serialize $ AuthD.MkPasswordRecovery + { user: login + , password_renew_key: token + , new_password: pass } + H.tell _ws_auth unit (WS.ToSend message) + AI.AuthenticateToAuthd v -> handleAction $ AuthenticateToAuthd (Right v) AI.Log message -> H.tell _log unit (AppLog.Log message) @@ -353,7 +360,16 @@ handleAction = case _ of MVI.Log message -> H.tell _log unit (AppLog.Log message) SetupInterfaceEvent ev -> case ev of - SetupInterface.ChangePassword pass -> handleAction $ Log $ ErrorLog "TODO: change password" + SetupInterface.DeleteUserAccount -> do + handleAction $ Log $ ErrorLog "TODO: delete the user account" + SetupInterface.ChangePassword pass -> do + message <- H.liftEffect $ AuthD.serialize $ AuthD.MkModUser { user: Nothing + , admin: Nothing + , password: Just pass + , email: Nothing + } + + H.tell _ws_auth unit (WS.ToSend message) SetupInterface.Log message -> H.tell _log unit (AppLog.Log message) AdministrationEvent ev -> case ev of @@ -432,8 +448,8 @@ handleAction = case _ of """ handleAction $ Routing MailValidation _ -> handleAction $ DispatchAuthDaemonMessage m - (AuthD.GotUserEdited _) -> do - handleAction $ Log $ ErrorLog "TODO: received a GotUserEdited message." + (AuthD.GotUserEdited u) -> do + handleAction $ Log $ SuccessLog $ "User (" <> show u.uid <> ") was modified!" (AuthD.GotUserValidated _) -> do handleAction $ Log $ SuccessLog "User got validated! You can now log in!" handleAction $ Routing Authentication @@ -444,7 +460,7 @@ handleAction = case _ of (AuthD.GotPermissionSet _) -> do handleAction $ Log $ ErrorLog "Received a GotPermissionSet message." (AuthD.GotPasswordRecovered _) -> do - handleAction $ Log $ ErrorLog "TODO: received a GotPasswordRecovered message." + handleAction $ Log $ SuccessLog "your new password is now valid!" m@(AuthD.GotMatchingUsers _) -> do { current_page } <- H.get case current_page of diff --git a/src/App/Setup.purs b/src/App/Setup.purs index dec043b..a37a0da 100644 --- a/src/App/Setup.purs +++ b/src/App/Setup.purs @@ -24,6 +24,7 @@ import App.Messages.AuthenticationDaemon as AuthD data Output = Log LogMessage | ChangePassword String + | DeleteUserAccount -- | The component's parent provides received messages. -- | @@ -48,13 +49,21 @@ data NewPasswordInput data Action = HandleNewPassword NewPasswordInput | ChangePasswordAttempt Event + | CancelModal + | DeleteAccountPopup + | DeleteAccount type StateNewPasswordForm = { password :: String, confirmation :: String } +data Modal + = NoModal + | DeleteAccountModal + type State = { newPasswordForm :: StateNewPasswordForm , token :: String , wsUp :: Boolean + , modal :: Modal } component :: forall m. MonadAff m => H.Component Query Input Output m @@ -72,15 +81,23 @@ initialState :: Input -> State initialState token = { newPasswordForm: { password: "", confirmation: "" } , token + , modal: NoModal , wsUp: true } render :: forall m. State -> H.ComponentHTML Action () m -render { wsUp, newPasswordForm } - = render_new_password_form +render { modal, wsUp, newPasswordForm } = + case modal of + DeleteAccountModal -> render_delete_account_modal + NoModal -> Bulma.columns_ [ b [ Bulma.h3 "Change password", render_new_password_form ] + , b [ Bulma.h3 "Delete account", render_delete_account ] + ] where + b e = Bulma.column_ [ Bulma.box e ] should_be_disabled = (if wsUp then (HP.enabled true) else (HP.disabled true)) + + render_delete_account = Bulma.alert_btn "Delete my account" DeleteAccountPopup render_new_password_form = HH.form [ HE.onSubmit ChangePasswordAttempt ] [ Bulma.box_input "passwordNEWPASS" "Password" "password" @@ -99,6 +116,15 @@ render { wsUp, newPasswordForm } [ HH.text "Send Message to Server" ] ] + render_delete_account_modal = Bulma.modal "Delete your account" + -- TODO: body content + [ Bulma.p "Wait, this is serious" + , Bulma.strong "⚠ You won't be able to recover." + ] + [ Bulma.alert_btn "GO AHEAD LOL" DeleteAccount + , Bulma.cancel_button CancelModal + ] + handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit handleAction = case _ of HandleNewPassword authinp -> do @@ -106,6 +132,14 @@ handleAction = case _ of NEWPASS_INP_password v -> H.modify_ _ { newPasswordForm { password = v } } NEWPASS_INP_confirmation v -> H.modify_ _ { newPasswordForm { confirmation = v } } + CancelModal -> do + H.modify_ _ { modal = NoModal } + DeleteAccountPopup -> do + H.modify_ _ { modal = DeleteAccountModal } + DeleteAccount -> do + H.raise $ DeleteUserAccount + handleAction $ CancelModal + ChangePasswordAttempt ev -> do H.liftEffect $ Event.preventDefault ev diff --git a/src/Bulma.purs b/src/Bulma.purs index dc3f923..1404701 100644 --- a/src/Bulma.purs +++ b/src/Bulma.purs @@ -418,6 +418,10 @@ input_with_side_text id title placeholder action value sidetext ] ] +-- | `modal`: create a modal by providing a few things: +-- | - a title (a simple String) +-- | - a body (`HTML` content) +-- | - a footer (`HTML` content) modal :: forall w i. String -> Array (HH.HTML w i) -> Array (HH.HTML w i) -> HH.HTML w i modal title body foot = modal_