diff --git a/etc/pages.json b/etc/pages.json index f0d846962..4ff01d6f3 100644 --- a/etc/pages.json +++ b/etc/pages.json @@ -186,6 +186,14 @@ "name": "resetpassword/request", "order": 100, "request_function": "*Signin_Page::reset_request" }, + { + "name": "resetpassword/request/basic", "order": 100, + "signin_function": "*Signin_Page::reset_request_basic" + }, + { + "name": "resetpassword/request/success", "order": 100000, + "signin_function": "*Signin_Page::reset_request_success" + }, [ "resetpassword/head", 1000, "Signin_Page::print_reset_head" ], [ "resetpassword/body", 3000, "*Signin_Page::print_reset_body" ], [ "resetpassword/form/title", 1, "Signin_Page::print_reset_form_title" ], diff --git a/src/pages/p_signin.php b/src/pages/p_signin.php index cbf5167ea..7495d3c82 100644 --- a/src/pages/p_signin.php +++ b/src/pages/p_signin.php @@ -48,36 +48,36 @@ private function problem_status_at($field) { /** @param ComponentSet $cs */ function signin_request(Contact $user, Qrequest $qreq, $cs) { assert($qreq->method() === "POST"); + $conf = $user->conf; if ($qreq->cancel) { $info = ["ok" => false]; foreach ($cs->members("signin/request") as $gj) { - $info = call_user_func($gj->signin_function, $user, $qreq, $info, $gj); + $info = $cs->call_function($gj, $gj->signin_function, $info, $gj); } - $user->conf->redirect(); - } else if ($user->conf->opt("httpAuthLogin")) { + $conf->redirect(); + } else if ($conf->opt("httpAuthLogin")) { LoginHelper::check_http_auth($user, $qreq); - } else if ($qreq->valid_post()) { - if (!$user->is_empty() && strcasecmp($qreq->email, $user->email) === 0) { - $user->conf->redirect(); - } else if (!$qreq->start) { - $info = ["ok" => true]; - foreach ($cs->members("signin/request") as $gj) { - $info = call_user_func($gj->signin_function, $user, $qreq, $info, $gj); - } - if ($info["ok"] || isset($info["redirect"])) { - $user->conf->redirect($info["redirect"] ?? ""); - } else if (($code = self::check_password_as_reset_code($user, $qreq))) { - $user->conf->redirect_hoturl("resetpassword", ["__PATH__" => $code]); - } else { - LoginHelper::login_error($user->conf, $qreq->email, $info, $this->ms()); - } - } - } else { + } else if (!$qreq->valid_post()) { self::bad_post_error($user, $qreq, "signin"); + } else if (!$user->is_empty() + && strcasecmp($qreq->email, $user->email) === 0) { + $conf->redirect(); + } else if (!$qreq->start) { + $info = ["ok" => true]; + foreach ($cs->members("signin/request") as $gj) { + $info = $cs->call_function($gj, $gj->signin_function, $info, $gj); + } + if ($info["ok"] || isset($info["redirect"])) { + $conf->redirect($info["redirect"] ?? ""); + } else if (($code = self::check_password_as_reset_code($user, $qreq))) { + $conf->redirect_hoturl("resetpassword", ["__PATH__" => $code]); + } else { + LoginHelper::login_error($conf, $qreq->email, $info, $this->ms()); + } } } - static function signin_request_basic(Contact $user, Qrequest $qreq, $info) { + static function signin_request_basic(Contact $user, Qrequest $qreq, $cs, $info) { if (!$info["ok"]) { return $info; } else if ($user->conf->external_login()) { @@ -87,7 +87,7 @@ static function signin_request_basic(Contact $user, Qrequest $qreq, $info) { } } - static function signin_request_success(Contact $user, Qrequest $qreq, $info) { + static function signin_request_success(Contact $user, Qrequest $qreq, $cs, $info) { if (!$info["ok"]) { return $info; } else { @@ -99,7 +99,7 @@ static function signin_request_success(Contact $user, Qrequest $qreq, $info) { * @return ?TokenInfo */ static private function _find_reset_token(Conf $conf, $token) { if ($token) { - $is_cdb = str_starts_with($token, "2") /* XXX */ || str_starts_with($token, "hcpw1"); + $is_cdb = str_starts_with($token, "hcpw1"); if (($tok = TokenInfo::find($token, $conf, $is_cdb)) && $tok->is_active() && $tok->capabilityType === TokenInfo::RESETPASSWORD) { @@ -172,7 +172,7 @@ static function print_signin_form(Contact $user, Qrequest $qreq, $cs) { } static function print_signin_form_title(Contact $user, Qrequest $qreq) { - echo '
Enter your email and we’ll send you a link to reset your password.'; @@ -446,22 +446,31 @@ function print_forgot_form_actions() { // Password reset - function reset_request(Contact $user, Qrequest $qreq) { + function reset_request(Contact $user, Qrequest $qreq, $cs) { $conf = $user->conf; + if ($conf->external_login()) { + return; + } + + // exit on cancel if ($qreq->cancel) { + $info = ["ok" => false]; + foreach ($cs->members("reset/request") as $gj) { + $info = $cs->call_function($gj, $gj->reset_function, $info, $gj); + } $conf->redirect(); - } else if ($conf->external_login()) { return; } - if ($qreq->resetcap === null /* [12] == XXX */ - && preg_match('/\A\/(hcpw[01][a-zA-Z]+|[12][-\w]+)(?:\/|\z)/', $qreq->path(), $m)) { + // derive `resetcap` parameter, maybe from URL + if ($qreq->resetcap === null + && preg_match('/\A\/(hcpw[01][a-zA-Z]+)(?:\/|\z)/', $qreq->path(), $m)) { $qreq->resetcap = $m[1]; } - // set $this->_reset_tokstr - $resetcap = trim((string) $qreq->resetcap); /* [12] == XXX */ - if (preg_match('/\A\/?(hcpw[01][a-zA-Z]+|[12][-\w]+)\/?\z/', $resetcap, $m)) { + // find token string + $resetcap = trim((string) $qreq->resetcap); + if (preg_match('/\A\/?(hcpw[01][a-zA-Z]+)\/?\z/', $resetcap, $m)) { $this->_reset_tokstr = $m[1]; } else if (strpos($resetcap, "@") !== false) { if ($qreq->valid_post()) { @@ -474,61 +483,91 @@ function reset_request(Contact $user, Qrequest $qreq) { } } } - - // set $this->_reset_token and $this->_reset_user - if ($this->_reset_tokstr) { - if (($tok = self::_find_reset_token($conf, $this->_reset_tokstr))) { - $this->_reset_token = $tok; - $this->_reset_user = $tok->user(); - $qreq->open_session(); - } else { - $this->ms()->error_at("resetcap", "Unknown or expired password reset code. Please check that you entered the code correctly."); - } + if (!$this->_reset_tokstr) { + return; } - // check passwords - if ($this->_reset_user) { - if ($qreq->valid_post()) { - $this->reset_valid_post_request($user, $qreq); - } else if ($qreq->is_post()) { - self::bad_post_error($user, $qreq, "resetpassword"); - } - } else if ($this->_reset_token) { + // look up token + $token = self::_find_reset_token($conf, $this->_reset_tokstr); + if (!$token) { + $this->ms()->error_at("resetcap", "Unknown or expired password reset code. Please check that you entered the code correctly."); + return; + } + if (!$token->user()) { $this->ms()->error_at("resetcap", "This password reset code refers to a user who no longer exists. Either create a new account or contact the conference administrator."); + return; + } + $this->_reset_token = $token; + $this->_reset_user = $token->user(); + $qreq->open_session(); + + // ensure POST + if (!$qreq->is_post()) { + return; + } + if (!$qreq->valid_post()) { + self::bad_post_error($user, $qreq, "resetpassword"); + return; + } + + // process request + $info = ["ok" => true, "user" => $this->_reset_user]; + foreach ($cs->members("resetpassword/request") as $gj) { + $info = $cs->call_function($gj, $gj->signin_function, $info, $gj); + } + if (isset($info["redirect"])) { + $conf->redirect($info["redirect"]); } } - private function reset_valid_post_request(Contact $user, Qrequest $qreq) { + function reset_request_basic(Contact $user, Qrequest $qreq, $cs, $info) { + if (!$info["ok"]) { + return $info; + } $p1 = (string) $qreq->password; $p2 = (string) $qreq->password2; if ($p1 === "") { if ($p2 !== "" || $qreq->autopassword) { $this->ms()->error_at("password", "Password required."); } + $info["ok"] = false; } else if (trim($p1) !== $p1) { $this->ms()->error_at("password", "Passwords cannot begin or end with spaces."); $this->ms()->error_at("password2"); + $info["ok"] = false; } else if (strlen($p1) <= 5) { $this->ms()->error_at("password", "Passwords must be at least six characters long."); $this->ms()->error_at("password2"); + $info["ok"] = false; } else if (!Contact::valid_password($p1)) { $this->ms()->error_at("password", "Invalid password."); $this->ms()->error_at("password2"); + $info["ok"] = false; } else if ($p1 !== $p2) { $this->ms()->error_at("password", "The passwords you entered did not match."); $this->ms()->error_at("password2"); + $info["ok"] = false; } else { - $accthere = $this->_reset_user->ensure_account_here(); - $accthere->change_password($p1); - $accthere->log_activity("Password reset via " . substr($this->_reset_tokstr, 0, 8) . "..."); - $user->conf->success_msg("<0>Password changed. Use the new password to sign in below."); - $this->_reset_token->delete(); - $qreq->set_csession("password_reset", (object) [ - "time" => Conf::$now, - "email" => $this->_reset_user->email, - "password" => $p1 - ]); - $user->conf->redirect_hoturl("signin"); + $info["newpassword"] = $p1; + } + return $info; + } + function reset_request_success(Contact $user, Qrequest $qreq, $cs, $info) { + if (!$info["ok"] || !isset($info["newpassword"])) { + return $info; } + // actually reset password + $accthere = $this->_reset_user->ensure_account_here(); + $accthere->change_password($info["newpassword"]); + $accthere->log_activity("Password reset via " . substr($this->_reset_tokstr, 0, 8) . "..."); + $user->conf->success_msg("<0>Password changed. Use the new password to sign in below."); + $this->_reset_token->delete(); + $qreq->set_csession("password_reset", (object) [ + "time" => Conf::$now, + "email" => $this->_reset_user->email, + "password" => $info["newpassword"] + ]); + $info["redirect"] = $info["redirect"] ?? $user->conf->hoturl_raw("signin"); + return $info; } static function print_reset_head(Contact $user, Qrequest $qreq, $cs) { $qreq->print_header("Reset password", "resetpassword", ["action_bar" => "", "hide_title" => true, "body_class" => "body-signin"]); @@ -549,7 +588,7 @@ function print_reset_body(Contact $user, Qrequest $qreq, $cs) { Ht::stash_script("hotcrp.focus_within(\$(\"#f-signin\"));window.scroll(0,0)"); } static function print_reset_form_title() { - echo '
Use this form to set a new password. You may want to use the random password we’ve chosen.
'; diff --git a/stylesheets/style.css b/stylesheets/style.css index 6b42625e7..a4d6e4c4c 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -240,7 +240,7 @@ body.paper { background: linear-gradient(to right, #e2e9ec, #e2e9ec 15.5rem, white 15.5rem, white); } body.body-signin { - background: #e8dcdd; + background: #e0d9da; } body.body-error { background: #dfd0d3; @@ -326,6 +326,10 @@ h3, h4 { h1.paptitle { padding: 6px 24px; } +h1.signin { + margin-bottom: 1rem; + font-weight: bold; +} .pnum-sp { padding-right: 0.4em; } diff --git a/test/t_login.php b/test/t_login.php index 94c7f4186..ffa32444c 100644 --- a/test/t_login.php +++ b/test/t_login.php @@ -63,10 +63,11 @@ function test_login() { $qreq->set_req("resetcap", $prep->reset_capability); $qreq->set_req("password", "newuserpassword!"); $qreq->set_req("password2", "newuserpassword!"); - $signinp = new Signin_Page; $result = null; try { - $signinp->reset_request($user, $qreq); + $cs = $this->conf->page_components($user, $qreq); + $signinp = $cs->callable("Signin_Page"); + $signinp->reset_request($user, $qreq, $cs); } catch (Redirection $redir) { $result = $redir; } @@ -137,10 +138,11 @@ function test_login_placeholder() { $qreq->set_req("resetcap", $prep->reset_capability); $qreq->set_req("password", "newuserpassword!"); $qreq->set_req("password2", "newuserpassword!"); - $signinp = new Signin_Page; $result = null; try { - $signinp->reset_request($user, $qreq); + $cs = $this->conf->page_components($user, $qreq); + $signinp = $cs->callable("Signin_Page"); + $signinp->reset_request($user, $qreq, $cs); } catch (Redirection $redir) { $result = $redir; } @@ -190,10 +192,11 @@ function test_login_first_user() { $qreq->set_req("resetcap", $prep->reset_capability); $qreq->set_req("password", "newuserpassword!"); $qreq->set_req("password2", "newuserpassword!"); - $signinp = new Signin_Page; $result = null; try { - $signinp->reset_request($user, $qreq); + $cs = $this->conf->page_components($user, $qreq); + $signinp = $cs->callable("Signin_Page"); + $signinp->reset_request($user, $qreq, $cs); } catch (Redirection $redir) { $result = $redir; }