diff --git a/mt-static/plugins/MFA/src/login.ts b/mt-static/plugins/MFA/src/login.ts index a989760..cfd67a8 100644 --- a/mt-static/plugins/MFA/src/login.ts +++ b/mt-static/plugins/MFA/src/login.ts @@ -52,16 +52,14 @@ function renderMFAForm() { rendered = true; - const fieldSelector = [ - "#username-field, #password-field, #remember-me, #remember-me + div", // login - "#page-title, #name-field, #name-field + div", // new_password - ].join(","); + const fieldSelector = + "#username-field, #password-field, #remember-me, #remember-me + div"; document .querySelectorAll(fieldSelector) .forEach((el) => el.classList.add("d-none")); const wrap = document.createElement("div"); - wrap.innerHTML = html; + wrap.innerHTML = html || ""; wrap.querySelector("#mfa-cancel")?.addEventListener("click", () => { wrap.remove(); rendered = false; @@ -69,19 +67,14 @@ function renderMFAForm() { .querySelectorAll(fieldSelector) .forEach((el) => el.classList.remove("d-none")); }); - const placeholder = document.querySelector( - [ - "#password-field", // login - "#name-field", // new_password - ].join(",") - ); + const placeholder = document.querySelector("#password-field"); placeholder?.parentElement?.insertBefore(wrap, placeholder.nextSibling); const firstInputElement = wrap.querySelector("input"); if (firstInputElement) { firstInputElement.focus(); } - scripts.forEach((src) => { + (scripts || []).forEach((src) => { if (document.querySelector(`script[src="${src}"]`)) { return; } diff --git a/plugins/MFA/config.yaml b/plugins/MFA/config.yaml index 8d5ab89..3270602 100644 --- a/plugins/MFA/config.yaml +++ b/plugins/MFA/config.yaml @@ -1,6 +1,6 @@ id: MFA name: MFA -version: 1.0.3 +version: 1.0.4 description: <__trans phrase="This plugin provides multi factor authentication feature for the Movable Type."> author_link: http://www.movabletype.org/ @@ -14,13 +14,14 @@ l10n_lexicon: Reset MFA settings: "多要素認証のリセット" You have successfully Reset the MFA settings of selected user(s).: "選択したユーザーの多要素認証の設定をリセットしました。" Failed to reset MFA settings of selected user(s).: "選択したユーザーの多要素認証の設定のリセットに失敗しました。" + Your password has been updated. Please sign in again.: "パスワードを更新しました。再度サインインしてください。" callbacks: MT::App::CMS::init_app: $MFA::MT::Plugin::MFA::init_app MT::App::CMS::template_param.login: $MFA::MT::Plugin::MFA::template_param_login - MT::App::CMS::template_param.new_password: $MFA::MT::Plugin::MFA::template_param_login MT::App::CMS::template_param.author_list_header: $MFA::MT::Plugin::MFA::template_param_author_list_header MT::App::CMS::template_param.edit_author: $MFA::MT::Plugin::MFA::template_param_edit_author + MT::App::CMS::template_source.new_password: $MFA::MT::Plugin::MFA::template_source_new_password applications: cms: @@ -33,6 +34,9 @@ applications: handler: $MFA::MT::Plugin::MFA::page_actions app_mode: JSON mfa_reset: $MFA::MT::Plugin::MFA::reset_settings + mfa_new_password: + handler: $MFA::MT::Plugin::MFA::new_password + requires_login: 0 list_actions: author: mfa_reset: diff --git a/plugins/MFA/lib/MT/Plugin/MFA.pm b/plugins/MFA/lib/MT/Plugin/MFA.pm index 6286f85..e3d07c0 100644 --- a/plugins/MFA/lib/MT/Plugin/MFA.pm +++ b/plugins/MFA/lib/MT/Plugin/MFA.pm @@ -25,6 +25,7 @@ sub template_param_login { my ($cb, $app, $param, $tmpl) = @_; $param->{plugin_mfa_version} = _plugin()->version; _insert_after_by_name($tmpl, 'layout/chromeless.tmpl', 'login_footer.tmpl'); + _insert_after_by_name($tmpl, 'logged_out', 'login_status_message.tmpl'); } sub template_param_author_list_header { @@ -38,6 +39,11 @@ sub template_param_edit_author { _insert_after_by_name($tmpl, 'related_content', 'edit_author.tmpl'); } +sub template_source_new_password { + my ($cb, $app, $tmpl) = @_; + $$tmpl =~ s{\bvalue="new_pw"}{value="mfa_new_password"}; +} + our $pre_check_credentials = 0; sub login_form { my $app = shift; @@ -93,6 +99,22 @@ sub login_form { }); } +our $disable_login = 0; +sub new_password { + my $app = shift; + require MT::CMS::Tools; + local $disable_login = 1; + my $res = MT::CMS::Tools::new_password($app); + + if (ref $app eq 'MT::App::CMS' && $app->{redirect}) { + # password has been updated + $app->redirect($app->mt_uri(args => { mfa_password_updated => 1 })); + return; + } + + return $res; +} + my $app_initialized = 0; sub init_app { return if $app_initialized; @@ -130,6 +152,8 @@ sub init_app { my $orig = shift; my $self = shift; + return if $disable_login; + my @res = $self->$orig(@_); return @res unless $self->isa('MT::App::CMS'); @@ -184,7 +208,8 @@ sub page_actions { }) or return; # TODO: Allow super users to also perform actions on other users. - if ($app->param('id') != $app->user->id) { + my $page_user_id = $app->param('id'); + if ($page_user_id && $page_user_id != $app->user->id) { return $app->json_result({ page_actions => [] }); } diff --git a/plugins/MFA/t/01-new-password.t b/plugins/MFA/t/01-new-password.t new file mode 100644 index 0000000..3cd2fb7 --- /dev/null +++ b/plugins/MFA/t/01-new-password.t @@ -0,0 +1,101 @@ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../../t/lib"; + +use Test::More; +use MT::Test::Env; + +our $test_env; +BEGIN { + $test_env = MT::Test::Env->new; + $ENV{MT_CONFIG} = $test_env->config_file; +} + +use MT::Test; +use MT::Test::Permission; +use MT::Test::App; +use MT::Util; +use MT::Util::Captcha; +use MT::Util::UniqueID; + +$test_env->prepare_fixture('db'); + +my $password = 'password'; +my $user = MT::Author->load(1); +$user->set_password($password); +$user->save or die $user->errstr; + +my $app = MT::Test::App->new('MT::App::CMS'); + +sub _start_recover { + my $user = shift; + require MT::Util::Captcha; + my $salt = MT::Util::Captcha->_generate_code(8); + my $expires = time + (60 * 60); + my $token = MT::Util::perl_sha1_digest_hex($salt . $expires . MT->config->SecretToken); + + $user->password_reset($salt); + $user->password_reset_expires($expires); + $user->save; + $token; +} + +subtest '__mode=new_pw' => sub { + subtest '__mode should be replaced' => sub { + my $token = _start_recover($user); + $app->get_ok({ + __mode => 'new_pw', + token => $token, + email => $user->email, + }); + $app->content_like(qr/value="mfa_new_password"/); + }; + + subtest 'should not be replaced if token is invalid' => sub { + my $token = _start_recover($user); + $app->get_ok({ + __mode => 'new_pw', + token => $token . '-invalid', + email => $user->email, + }); + $app->content_unlike(qr/value="mfa_new_password"/); + }; +}; + +subtest '__mode=mfa_new_password' => sub { + subtest 'redirect target should be replaced' => sub { + local $app->{no_redirect} = 1; + my $new_password = MT::Util::UniqueID->create_md5_id(); + my $token = _start_recover($user); + $app->post_ok({ + __mode => 'mfa_new_password', + token => $token, + email => $user->email, + username => $user->name, + password => $new_password, + password_again => $new_password, + }); + my $location = $app->last_location; + ok $location->query_param('mfa_password_updated'); + ok !$location->query_param('__mode'); + }; + + subtest 'the result is the same as the original new_password if not redirected' => sub { + local $app->{no_redirect} = 1; + my $new_password = MT::Util::UniqueID->create_md5_id(); + my $token = _start_recover($user); + $app->post_ok({ + __mode => 'mfa_new_password', + token => $token, + email => $user->email, + username => $user->name, + password => $new_password, + password_again => $new_password . '-typo', + }); + $app->content_like(qr/value="mfa_new_password"/); + }; +}; + +done_testing(); diff --git a/plugins/MFA/t/01-sign-in.t b/plugins/MFA/t/01-sign-in.t index e772f76..ee8c312 100644 --- a/plugins/MFA/t/01-sign-in.t +++ b/plugins/MFA/t/01-sign-in.t @@ -87,6 +87,21 @@ sub insert_failedlogin { $failedlogin->save or die $failedlogin->errstr; } +subtest 'sign in page' => sub { + my $message_re = qr/Your password has been updated. Please sign in again./; + subtest 'with mfa_password_updated=1' => sub { + $app->get_ok({ + mfa_password_updated => 1, + }); + $app->content_like($message_re); + }; + + subtest 'without parameters' => sub { + $app->get_ok(); + $app->content_unlike($message_re); + }; +}; + subtest 'sign in' => sub { subtest 'Should not affect to "sign in" function for users who have not configured MFA' => sub { subtest 'valid password' => sub { diff --git a/plugins/MFA/tmpl/login_status_message.tmpl b/plugins/MFA/tmpl/login_status_message.tmpl new file mode 100644 index 0000000..c30261f --- /dev/null +++ b/plugins/MFA/tmpl/login_status_message.tmpl @@ -0,0 +1,8 @@ + + + <__trans phrase="Your password has been updated. Please sign in again."> + +