Password can now be recovered!

This commit is contained in:
Philippe Pittoli 2024-02-24 00:40:09 +01:00
parent 66820d0dd4
commit 065bc7a716
4 changed files with 135 additions and 17 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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_