diff --git a/src/App/AuthenticationInterface.purs b/src/App/AuthenticationInterface.purs index 1ab3e12..c50dcae 100644 --- a/src/App/AuthenticationInterface.purs +++ b/src/App/AuthenticationInterface.purs @@ -2,11 +2,12 @@ -- | TODO: token validation. module App.AuthenticationInterface where -import Prelude (Unit, bind, discard, pure, ($), (<<<), (<>), (>), (==)) +import Prelude (Unit, bind, discard, pure, ($), (<<<), (<>), (>), (==), map, show) import Data.Array as A import Data.ArrayBuffer.Types (ArrayBuffer) -import Data.Maybe (Maybe(..)) +import Data.Maybe (Maybe(..), maybe) +import Data.Either (Either(..)) import Data.Tuple (Tuple(..)) import Effect.Aff.Class (class MonadAff) import Halogen as H @@ -18,14 +19,23 @@ import Web.Event.Event (Event) import Bulma as Bulma -import App.Email as Email import App.LogMessage import App.Messages.AuthenticationDaemon as AuthD +import App.Validation.Login as L +import App.Validation.Email as E +import App.Validation.Password as P + type Login = String +type Email = String type Password = String type PasswordRecoveryToken = String +data Error + = Login (Array L.Error) + | Email (Array E.Error) + | Password (Array P.Error) + -- | 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. @@ -38,6 +48,7 @@ data Output | AuthenticateToAuthd (Tuple Login Password) | Log LogMessage | PasswordRecovery Login PasswordRecoveryToken Password + | AskPasswordRecovery (Either Email Login) -- | The component's parent provides received messages. -- | @@ -82,9 +93,19 @@ type State = { authenticationForm :: StateAuthenticationForm , passwordRecoveryForm :: StatePasswordRecoveryForm , newPasswordForm :: StateNewPasswordForm + , errors :: Array Error , wsUp :: Boolean } +initialState :: Input -> State +initialState _ = + { authenticationForm: { login: "", pass: "" } + , passwordRecoveryForm: { login: "", email: "" } + , newPasswordForm: { login: "", token: "", password: "", confirmation: "" } + , wsUp: true + , errors: [] + } + component :: forall m. MonadAff m => H.Component Query Input Output m component = H.mkComponent @@ -96,25 +117,66 @@ component = } } -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, newPasswordForm} = +render { wsUp, authenticationForm, passwordRecoveryForm, newPasswordForm, errors } = Bulma.section_small [ case wsUp of false -> Bulma.p "You are disconnected." - true -> Bulma.columns_ [ b auth_form, b passrecovery_form, b newpass_form ] + true -> + if A.length errors > 0 + then HH.div_ [ Bulma.box [ HH.text (A.fold $ map show_error errors) ] + , Bulma.columns_ [ b auth_form, b passrecovery_form, b newpass_form ] + ] + else Bulma.columns_ [ b auth_form, b passrecovery_form, b newpass_form ] ] where b e = Bulma.column_ [ Bulma.box e ] + show_error :: Error -> String + show_error = case _ of + Login arr -> "Error with the Login: " <> (A.fold $ map show_error_login arr) + Email arr -> "Error with the Email: " <> (A.fold $ map show_error_email arr) + Password arr -> "Error with the Password: " <> (A.fold $ map show_error_password arr) + + show_error_login :: L.Error -> String + show_error_login = case _ of + L.ParsingError {error, position} -> + "position " <> show position <> " " <> maybe "" string_error_login error + + string_error_login :: L.LoginParsingError -> String + string_error_login = case _ of + L.CannotParse -> "cannot parse the login" + L.CannotEntirelyParse -> "cannot entirely parse the login" + L.Size min max n -> "login size should be between " + <> show min <> " and " <> show max + <> " (currently: " <> show n <> ")" + show_error_email :: E.Error -> String + show_error_email = case _ of + E.ParsingError {error, position} -> + "position " <> show position <> " " <> maybe "" string_error_email error + + string_error_email :: E.EmailParsingError -> String + string_error_email = case _ of + E.CannotParse -> "cannot parse the email" + E.CannotEntirelyParse -> "cannot entirely parse the email" + E.Size min max n -> "email size should be between " + <> show min <> " and " <> show max + <> " (currently: " <> show n <> ")" + + show_error_password :: P.Error -> String + show_error_password = case _ of + P.ParsingError {error, position} -> + "position " <> show position <> " " <> maybe "" string_error_password error + + string_error_password :: P.PasswordParsingError -> String + string_error_password = case _ of + P.CannotParse -> "cannot parse the password" + P.CannotEntirelyParse -> "cannot entirely parse the password" + P.Size min max n -> "password size should be between " + <> show min <> " and " <> show max + <> " (currently: " <> show n <> ")" + 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 ] @@ -207,31 +269,23 @@ handleAction = case _ of H.liftEffect $ Event.preventDefault ev { authenticationForm } <- H.get + let { login, pass } = authenticationForm - case authenticationForm.login, authenticationForm.pass of + case login, pass of "" , _ -> H.raise $ Log $ UnableToSend "Write your login!" _ , "" -> H.raise $ Log $ UnableToSend "Write your password!" - login, pass -> do - H.raise $ AuthenticateToAuthd (Tuple login pass) + _, _ -> do + case L.login login, P.password pass of + Left errors, _ -> H.modify_ _ { errors = [ Login errors ] } + _, Left errors -> H.modify_ _ { errors = [ Password errors ] } + _, _ -> do H.modify_ _ { errors = [] } + 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 @@ -242,10 +296,32 @@ handleAction = case _ of case login, email of "", "" -> H.raise $ Log $ UnableToSend "Write your login or your email!" _, _ -> do - message <- H.liftEffect $ AuthD.serialize $ - AuthD.MkAskPasswordRecovery { user: (Just login), email: Just (Email.Email email) } - H.raise $ MessageToSend message H.raise $ Log $ SystemLog "password recovery" + if email == "" + then case L.login login of + Left errors -> H.modify_ _ { errors = [ Login errors ] } + _ -> do H.modify_ _ { errors = [] } + H.raise $ AskPasswordRecovery (Right login) + else case E.email email of + Left errors -> H.modify_ _ { errors = [ Email errors ] } + _ -> do H.modify_ _ { errors = [] } + H.raise $ AskPasswordRecovery (Left email) + + -- 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 case L.login login of + Left errors -> H.modify_ _ { errors = [ Login errors ] } + Right _ -> do H.modify_ _ { errors = [] } + H.raise $ PasswordRecovery login token password + else H.raise $ Log $ UnableToSend "Confirmation differs from password!" handleQuery :: forall a m. MonadAff m => Query a -> H.HalogenM State Action () Output m (Maybe a) handleQuery = case _ of diff --git a/src/App/Container.purs b/src/App/Container.purs index 6e0f0be..b1684e7 100644 --- a/src/App/Container.purs +++ b/src/App/Container.purs @@ -77,6 +77,8 @@ import Web.HTML (window) as HTML import Web.HTML.Window (sessionStorage) as Window import Web.Storage.Storage as Storage +import App.Email as Email + import App.LogMessage (LogMessage(..)) import App.Pages @@ -341,6 +343,15 @@ handleAction = case _ of AuthenticationInterfaceEvent ev -> case ev of AI.MessageToSend message -> H.tell _ws_auth unit (WS.ToSend message) + AI.AskPasswordRecovery e -> case e of + Left email -> do + message <- H.liftEffect $ AuthD.serialize $ + AuthD.MkAskPasswordRecovery { user: Nothing, email: Just (Email.Email email) } + H.tell _ws_auth unit (WS.ToSend message) + Right login -> do + message <- H.liftEffect $ AuthD.serialize $ + AuthD.MkAskPasswordRecovery { user: (Just login), email: Nothing } + H.tell _ws_auth unit (WS.ToSend message) AI.PasswordRecovery login token pass -> do message <- H.liftEffect $ AuthD.serialize $ AuthD.MkPasswordRecovery { user: login diff --git a/src/App/RegistrationInterface.purs b/src/App/RegistrationInterface.purs index cf6bdb3..28e0e8d 100644 --- a/src/App/RegistrationInterface.purs +++ b/src/App/RegistrationInterface.purs @@ -71,6 +71,13 @@ type State = , wsUp :: Boolean } +initialState :: Input -> State +initialState _ = + { registrationForm: { login: "", email: "", pass: "" } + , errors: [] + , wsUp: true + } + component :: forall m. MonadAff m => H.Component Query Input Output m component = H.mkComponent @@ -82,13 +89,6 @@ component = } } -initialState :: Input -> State -initialState _ = - { registrationForm: { login: "", email: "", pass: "" } - , errors: [] - , wsUp: true - } - render :: forall m. State -> H.ComponentHTML Action () m render { wsUp, registrationForm } = Bulma.section_small diff --git a/src/App/Setup.purs b/src/App/Setup.purs index a37a0da..4c39f5c 100644 --- a/src/App/Setup.purs +++ b/src/App/Setup.purs @@ -117,9 +117,8 @@ render { modal, wsUp, newPasswordForm } = ] 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.p "Your account and domains will be removed." + , Bulma.strong "⚠ You won't be able to recover your data." ] [ Bulma.alert_btn "GO AHEAD LOL" DeleteAccount , Bulma.cancel_button CancelModal diff --git a/src/App/Validation/Password.purs b/src/App/Validation/Password.purs index 19beea2..7659428 100644 --- a/src/App/Validation/Password.purs +++ b/src/App/Validation/Password.purs @@ -21,7 +21,7 @@ data Error = ParsingError (G.Error PasswordParsingError) min_password_size :: Int -min_password_size = 1 +min_password_size = 2 max_password_size :: Int max_password_size = 100