aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth/Profile.pm34
-rw-r--r--t/app/controller/auth_profile.t63
-rw-r--r--templates/web/base/auth/change_password.html16
4 files changed, 103 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 680d4b412..e0ae5ceed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
- Front end improvements:
- Zoom out as much as necessary on body map page, even on mobile. #1958
- Show loading message on initial /around map load #1976
+ - Ask for current password/send email on password change. #1974
- Bugfixes:
- Fix bug specifying category in URL on /around. #1950
- Fix bug with multiple select-multiples on a page. #1951
diff --git a/perllib/FixMyStreet/App/Controller/Auth/Profile.pm b/perllib/FixMyStreet/App/Controller/Auth/Profile.pm
index 5e6fe6266..441f222d1 100644
--- a/perllib/FixMyStreet/App/Controller/Auth/Profile.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth/Profile.pm
@@ -19,7 +19,7 @@ verifying email, phone, password.
=cut
-sub auto {
+sub auto : Private {
my ( $self, $c ) = @_;
$c->detach( '/auth/redirect' ) unless $c->user;
@@ -49,8 +49,17 @@ sub change_password : Path('/auth/change_password') {
my $new = $c->get_param('new_password') // '';
my $confirm = $c->get_param('confirm') // '';
+ my $password_error;
+
+ # Check existing password, if available
+ if ($c->user->password) {
+ my $current = $c->get_param('current_password') // '';
+ $c->stash->{current_password} = $current;
+ $password_error = 'incorrect' unless $c->user->check_password($current);
+ }
+
# check for errors
- my $password_error =
+ $password_error ||=
!$new && !$confirm ? 'missing'
: $new ne $confirm ? 'mismatch'
: '';
@@ -62,10 +71,17 @@ sub change_password : Path('/auth/change_password') {
return;
}
- # we should have a usable password - save it to the user
- $c->user->obj->update( { password => $new } );
- $c->stash->{password_changed} = 1;
-
+ if ($c->user->password) {
+ # we should have a usable password - save it to the user
+ $c->user->obj->update( { password => $new } );
+ $c->stash->{password_changed} = 1;
+ } else {
+ # Set up arguments for code sign in
+ $c->set_param('username', $c->user->username);
+ $c->set_param('password_register', $new);
+ $c->set_param('r', 'auth/change_password/success');
+ $c->detach('/auth/code_sign_in');
+ }
}
=head2 change_email
@@ -148,6 +164,12 @@ sub change_phone_success : Path('/auth/change_phone/success') {
$c->res->redirect('/my');
}
+sub change_password_success : Path('/auth/change_password/success') {
+ my ( $self, $c ) = @_;
+ $c->flash->{flash_message} = _('Your password has been changed');
+ $c->res->redirect('/my');
+}
+
sub generate_token : Path('/auth/generate_token') {
my ($self, $c) = @_;
diff --git a/t/app/controller/auth_profile.t b/t/app/controller/auth_profile.t
index 74edccfe6..6040406ef 100644
--- a/t/app/controller/auth_profile.t
+++ b/t/app/controller/auth_profile.t
@@ -75,9 +75,68 @@ subtest "Test change password page" => sub {
{
form_name => 'change_password',
fields =>
- { new_password => $test_password, confirm => $test_password, },
+ { new_password => 'new_password', confirm => 'new_password', },
},
- "change_password with '$test_password' and '$test_password'"
+ "change_password with 'new_password' and 'new_password'"
+ );
+ is $mech->uri->path, '/auth/change_password',
+ "still on change password page";
+ $mech->content_contains('check your email');
+
+ $link = $mech->get_link_from_email;
+ $mech->get_ok($link);
+ is $mech->uri->path, '/my', "redirected to /my";
+
+ $mech->content_contains( 'password has been changed',
+ "found password changed" );
+
+ $user->discard_changes();
+ ok $user->password, "user now has a password";
+};
+
+# Change password, when already got one
+subtest "Test change password page with current password" => sub {
+ $mech->get_ok('/auth/change_password');
+
+ ok my $form = $mech->form_name('change_password'),
+ "found change password form";
+ is_deeply [ sort grep { $_ } map { $_->name } $form->inputs ], #
+ [ 'confirm', 'current_password', 'new_password', 'token' ],
+ "check we got expected fields (ie not old_password)";
+
+ # check the various ways the form can be wrong
+ for my $test (
+ { current => '', new => '', conf => '', err => 'check the passwords', },
+ { current => 'new_password', new => '', conf => '', err => 'enter a password', },
+ { current => 'new_password', new => 'secret', conf => '', err => 'do not match', },
+ { current => 'new_password', new => '', conf => 'secret', err => 'do not match', },
+ { current => 'new_password', new => 'secret', conf => 'not_secret', err => 'do not match', },
+ )
+ {
+ $mech->get_ok('/auth/change_password');
+ $mech->content_lacks( $test->{err}, "did not find expected error" );
+ $mech->submit_form_ok(
+ {
+ form_name => 'change_password',
+ fields =>
+ { current_password => $test->{current}, new_password => $test->{new}, confirm => $test->{conf}, },
+ },
+ "change_password with '$test->{current}', '$test->{new}' and '$test->{conf}'"
+ );
+ $mech->content_contains( $test->{err}, "found expected error" );
+ }
+
+ my $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } );
+ ok $user, "got a user";
+
+ $mech->get_ok('/auth/change_password');
+ $mech->submit_form_ok(
+ {
+ form_name => 'change_password',
+ fields =>
+ { current_password => 'new_password', new_password => $test_password, confirm => $test_password },
+ },
+ "change_password with 'new_password' and '$test_password'"
);
is $mech->uri->path, '/auth/change_password',
"still on change password page";
diff --git a/templates/web/base/auth/change_password.html b/templates/web/base/auth/change_password.html
index a32dbaf9c..bf361ff2d 100644
--- a/templates/web/base/auth/change_password.html
+++ b/templates/web/base/auth/change_password.html
@@ -1,7 +1,9 @@
[%
SET bclass = 'authpage';
SET bclass = 'fullwidthpage' IF password_changed;
-INCLUDE 'header.html', title = loc('Change password'), bodyclass = bclass
+SET title = loc('Set password');
+SET title = loc('Change password') IF c.user.password;
+INCLUDE 'header.html', title = title bodyclass = bclass
%]
[% IF password_changed %]
@@ -13,7 +15,7 @@ INCLUDE 'header.html', title = loc('Change password'), bodyclass = bclass
[% ELSE %]
-<h1>[% loc('Change password') %]</h1>
+<h1>[% title %]</h1>
<form action="[% c.uri_for_action('/auth/profile/change_password') %]" method="post" name="change_password" class="fieldset">
<input type="hidden" name="token" value="[% csrf_token %]">
@@ -31,6 +33,14 @@ INCLUDE 'header.html', title = loc('Change password'), bodyclass = bclass
<div class="form-error">[% loc_password_error %]</div>
[% END %]
+[% IF c.user.password %]
+ <div class="form-field">
+ <label for="current_password">[% loc('Current password:') %]</label>
+ <input class="form-control" type="password" name="current_password" value="[% current_password | html %]">
+ </div>
+ <hr>
+[% END %]
+
<div class="form-field">
<label for="new_password">[% loc('New password:') %]</label>
<input class="form-control" type="password" name="new_password" value="[% new_password | html %]">
@@ -40,7 +50,7 @@ INCLUDE 'header.html', title = loc('Change password'), bodyclass = bclass
<input class="form-control" type="password" name="confirm" value="[% confirm | html %]">
</div>
<div class="final-submit">
- <input type="submit" class="btn" value="[% loc('Change password') %]">
+ <input type="submit" class="btn" value="[% title %]">
</div>
</fieldset>