Password can now be recovered!

beta
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. -- | TODO: token validation.
module App.AuthenticationInterface where 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.ArrayBuffer.Types (ArrayBuffer)
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.Tuple (Tuple(..)) import Data.Tuple (Tuple(..))
@ -21,6 +22,10 @@ import App.Email as Email
import App.LogMessage import App.LogMessage
import App.Messages.AuthenticationDaemon as AuthD 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, -- | 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 -- | and share both the uid and token. The token is useful to authenticate the user to the
-- | dnsmanager daemon. -- | dnsmanager daemon.
@ -30,8 +35,9 @@ import App.Messages.AuthenticationDaemon as AuthD
-- | TODO: authentication is performed in `App.Container`. -- | TODO: authentication is performed in `App.Container`.
data Output data Output
= MessageToSend ArrayBuffer = MessageToSend ArrayBuffer
| AuthenticateToAuthd (Tuple String String) -- Login Password | AuthenticateToAuthd (Tuple Login Password)
| Log LogMessage | Log LogMessage
| PasswordRecovery Login PasswordRecoveryToken Password
-- | The component's parent provides received messages. -- | The component's parent provides received messages.
-- | -- |
@ -53,19 +59,29 @@ data PasswordRecoveryInput
= PASSR_INP_login String = PASSR_INP_login String
| PASSR_INP_email 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 data Action
= HandleAuthenticationInput AuthenticationInput = HandleAuthenticationInput AuthenticationInput
| HandlePasswordRecovery PasswordRecoveryInput | HandlePasswordRecovery PasswordRecoveryInput
| HandleNewPassword NewPasswordInput
-- --
| AuthenticationAttempt Event | AuthenticationAttempt Event
| PasswordRecoveryAttempt Event | PasswordRecoveryAttempt Event
| NewPasswordAttempt Event
type StateAuthenticationForm = { login :: String, pass :: String } type StateAuthenticationForm = { login :: String, pass :: String }
type StatePasswordRecoveryForm = { login :: String, email :: String } type StatePasswordRecoveryForm = { login :: String, email :: String }
type StateNewPasswordForm = { login :: String, token :: String, password :: String, confirmation :: String }
type State = type State =
{ authenticationForm :: StateAuthenticationForm { authenticationForm :: StateAuthenticationForm
, passwordRecoveryForm :: StatePasswordRecoveryForm , passwordRecoveryForm :: StatePasswordRecoveryForm
, newPasswordForm :: StateNewPasswordForm
, wsUp :: Boolean , wsUp :: Boolean
} }
@ -84,23 +100,24 @@ initialState :: Input -> State
initialState _ = initialState _ =
{ authenticationForm: { login: "", pass: "" } { authenticationForm: { login: "", pass: "" }
, passwordRecoveryForm: { login: "", email: "" } , passwordRecoveryForm: { login: "", email: "" }
, newPasswordForm: { login: "", token: "", password: "", confirmation: "" }
, wsUp: true , wsUp: true
} }
render :: forall m. State -> H.ComponentHTML Action () m render :: forall m. State -> H.ComponentHTML Action () m
render { wsUp, authenticationForm, passwordRecoveryForm} render { wsUp, authenticationForm, passwordRecoveryForm, newPasswordForm} =
= Bulma.section_small Bulma.section_small
[ case wsUp of [ case wsUp of
false -> Bulma.p "You are disconnected." false -> Bulma.p "You are disconnected."
true -> Bulma.columns_ [ b auth_form, b passrecovery_form ] true -> Bulma.columns_ [ b auth_form, b passrecovery_form, b newpass_form ]
] ]
where where
b e = Bulma.column_ [ Bulma.box e ] b e = Bulma.column_ [ Bulma.box e ]
auth_form = [ Bulma.h3 "Authentication" , render_auth_form ] auth_form = [ Bulma.h3 "Authentication" , render_auth_form ]
passrecovery_form = [ Bulma.h3 "Password Recovery", render_password_recovery_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)) 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" ] [ 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 :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit
handleAction = case _ of handleAction = case _ of
HandleAuthenticationInput authinp -> do HandleAuthenticationInput authinp -> do
@ -147,11 +191,18 @@ handleAction = case _ of
AUTH_INP_login v -> H.modify_ _ { authenticationForm { login = v } } AUTH_INP_login v -> H.modify_ _ { authenticationForm { login = v } }
AUTH_INP_pass v -> H.modify_ _ { authenticationForm { pass = v } } AUTH_INP_pass v -> H.modify_ _ { authenticationForm { pass = v } }
HandlePasswordRecovery authinp -> do HandlePasswordRecovery passrecovinp -> do
case authinp of case passrecovinp of
PASSR_INP_login v -> H.modify_ _ { passwordRecoveryForm { login = v } } PASSR_INP_login v -> H.modify_ _ { passwordRecoveryForm { login = v } }
PASSR_INP_email v -> H.modify_ _ { passwordRecoveryForm { email = 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 AuthenticationAttempt ev -> do
H.liftEffect $ Event.preventDefault ev H.liftEffect $ Event.preventDefault ev
@ -168,6 +219,19 @@ handleAction = case _ of
H.raise $ AuthenticateToAuthd (Tuple login pass) H.raise $ AuthenticateToAuthd (Tuple login pass)
H.raise $ Log $ SystemLog $ "authenticate (login: " <> login <> ")" 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 PasswordRecoveryAttempt ev -> do
H.liftEffect $ Event.preventDefault ev H.liftEffect $ Event.preventDefault ev

View File

@ -341,6 +341,13 @@ handleAction = case _ of
AuthenticationInterfaceEvent ev -> case ev of AuthenticationInterfaceEvent ev -> case ev of
AI.MessageToSend message -> H.tell _ws_auth unit (WS.ToSend message) 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.AuthenticateToAuthd v -> handleAction $ AuthenticateToAuthd (Right v)
AI.Log message -> H.tell _log unit (AppLog.Log message) 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) MVI.Log message -> H.tell _log unit (AppLog.Log message)
SetupInterfaceEvent ev -> case ev of 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) SetupInterface.Log message -> H.tell _log unit (AppLog.Log message)
AdministrationEvent ev -> case ev of AdministrationEvent ev -> case ev of
@ -432,8 +448,8 @@ handleAction = case _ of
""" """
handleAction $ Routing MailValidation handleAction $ Routing MailValidation
_ -> handleAction $ DispatchAuthDaemonMessage m _ -> handleAction $ DispatchAuthDaemonMessage m
(AuthD.GotUserEdited _) -> do (AuthD.GotUserEdited u) -> do
handleAction $ Log $ ErrorLog "TODO: received a GotUserEdited message." handleAction $ Log $ SuccessLog $ "User (" <> show u.uid <> ") was modified!"
(AuthD.GotUserValidated _) -> do (AuthD.GotUserValidated _) -> do
handleAction $ Log $ SuccessLog "User got validated! You can now log in!" handleAction $ Log $ SuccessLog "User got validated! You can now log in!"
handleAction $ Routing Authentication handleAction $ Routing Authentication
@ -444,7 +460,7 @@ handleAction = case _ of
(AuthD.GotPermissionSet _) -> do (AuthD.GotPermissionSet _) -> do
handleAction $ Log $ ErrorLog "Received a GotPermissionSet message." handleAction $ Log $ ErrorLog "Received a GotPermissionSet message."
(AuthD.GotPasswordRecovered _) -> do (AuthD.GotPasswordRecovered _) -> do
handleAction $ Log $ ErrorLog "TODO: received a GotPasswordRecovered message." handleAction $ Log $ SuccessLog "your new password is now valid!"
m@(AuthD.GotMatchingUsers _) -> do m@(AuthD.GotMatchingUsers _) -> do
{ current_page } <- H.get { current_page } <- H.get
case current_page of case current_page of

View File

@ -24,6 +24,7 @@ import App.Messages.AuthenticationDaemon as AuthD
data Output data Output
= Log LogMessage = Log LogMessage
| ChangePassword String | ChangePassword String
| DeleteUserAccount
-- | The component's parent provides received messages. -- | The component's parent provides received messages.
-- | -- |
@ -48,13 +49,21 @@ data NewPasswordInput
data Action data Action
= HandleNewPassword NewPasswordInput = HandleNewPassword NewPasswordInput
| ChangePasswordAttempt Event | ChangePasswordAttempt Event
| CancelModal
| DeleteAccountPopup
| DeleteAccount
type StateNewPasswordForm = { password :: String, confirmation :: String } type StateNewPasswordForm = { password :: String, confirmation :: String }
data Modal
= NoModal
| DeleteAccountModal
type State = type State =
{ newPasswordForm :: StateNewPasswordForm { newPasswordForm :: StateNewPasswordForm
, token :: String , token :: String
, wsUp :: Boolean , wsUp :: Boolean
, modal :: Modal
} }
component :: forall m. MonadAff m => H.Component Query Input Output m component :: forall m. MonadAff m => H.Component Query Input Output m
@ -72,15 +81,23 @@ initialState :: Input -> State
initialState token = initialState token =
{ newPasswordForm: { password: "", confirmation: "" } { newPasswordForm: { password: "", confirmation: "" }
, token , token
, modal: NoModal
, wsUp: true , wsUp: true
} }
render :: forall m. State -> H.ComponentHTML Action () m render :: forall m. State -> H.ComponentHTML Action () m
render { wsUp, newPasswordForm } render { modal, wsUp, newPasswordForm } =
= render_new_password_form 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 where
b e = Bulma.column_ [ Bulma.box e ]
should_be_disabled = (if wsUp then (HP.enabled true) else (HP.disabled true)) 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 render_new_password_form = HH.form
[ HE.onSubmit ChangePasswordAttempt ] [ HE.onSubmit ChangePasswordAttempt ]
[ Bulma.box_input "passwordNEWPASS" "Password" "password" [ Bulma.box_input "passwordNEWPASS" "Password" "password"
@ -99,6 +116,15 @@ render { wsUp, newPasswordForm }
[ HH.text "Send Message to Server" ] [ 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 :: forall m. MonadAff m => Action -> H.HalogenM State Action () Output m Unit
handleAction = case _ of handleAction = case _ of
HandleNewPassword authinp -> do HandleNewPassword authinp -> do
@ -106,6 +132,14 @@ handleAction = case _ of
NEWPASS_INP_password v -> H.modify_ _ { newPasswordForm { password = v } } NEWPASS_INP_password v -> H.modify_ _ { newPasswordForm { password = v } }
NEWPASS_INP_confirmation v -> H.modify_ _ { newPasswordForm { confirmation = 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 ChangePasswordAttempt ev -> do
H.liftEffect $ Event.preventDefault ev 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 :: forall w i. String -> Array (HH.HTML w i) -> Array (HH.HTML w i) -> HH.HTML w i
modal title body foot = modal title body foot =
modal_ modal_