use Test::MockModule;
use FixMyStreet::TestMech;
my $mech = FixMyStreet::TestMech->new;
my $test_email = 'test@example.com';
my $test_email3 = 'newuser@example.org';
my $test_password = 'foobar123';
END {
done_testing();
}
$mech->get_ok('/auth');
# check that we can't reach a page that is only available to authenticated users
$mech->not_logged_in_ok;
# check that submitting form with no / bad email creates an error.
$mech->get_ok('/auth');
for my $test (
[ '' => 'Please enter your email' ],
[ 'not an email' => 'Please check your email address is correct' ],
[ 'bob@foo' => 'Please check your email address is correct' ],
[ 'bob@foonaoedudnueu.co.uk' => 'Please check your email address is correct' ],
)
{
my ( $email, $error_message ) = @$test;
my $resolver = Test::MockModule->new('Net::DNS::Resolver');
$resolver->mock('send', sub {
my ($self, $domain, $type) = @_;
return Net::DNS::Packet->new;
});
pass "--- testing bad email '$email' gives error '$error_message'";
$mech->get_ok('/auth');
is_deeply $mech->page_errors, [], 'no errors initially';
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => { username => $email, },
button => 'sign_in_by_code',
},
"try to create an account with email '$email'"
);
is $mech->uri->path, '/auth', "still on auth page";
is_deeply $mech->page_errors, [ $error_message ], 'errors match';
}
# Email address parsing should pass from here
my $resolver = Test::MockModule->new('Email::Valid');
$resolver->mock('address', sub { $_[1] });
# create a new account
$mech->clear_emails_ok;
$mech->get_ok('/auth');
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => { username => $test_email, password_register => $test_password },
button => 'sign_in_by_code',
},
"create an account for '$test_email'"
);
# check that we are not logged in yet
$mech->not_logged_in_ok;
# check that we got one email
{
my $email = $mech->get_email;
$mech->clear_emails_ok;
is $email->header('Subject'), "Your FixMyStreet account details",
"subject is correct";
is $email->header('To'), $test_email, "to is correct";
# extract the link
my $link = $mech->get_link_from_email($email);
# check that the user does not exist
sub get_user {
FixMyStreet::App->model('DB::User')->find( { email => $test_email } );
}
ok !get_user(), "no user exists";
# visit the confirm link (with bad token) and check user no confirmed
$mech->get_ok( $link . 'XXX' );
ok !get_user(), "no user exists";
$mech->not_logged_in_ok;
# visit the confirm link and check user is confirmed
$mech->get_ok($link);
ok get_user(), "user created";
is $mech->uri->path, '/my', "redirected to the 'my' section of site";
$mech->logged_in_ok;
# logout
$mech->log_out_ok;
}
foreach my $remember_me ( '1', '0' ) {
subtest "sign in using valid details (remember_me => '$remember_me')" => sub {
$mech->get_ok('/auth');
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => {
username => $test_email,
password_sign_in => $test_password,
remember_me => ( $remember_me ? 1 : undef ),
},
button => 'sign_in_by_password',
},
"sign in with '$test_email' & '$test_password'"
);
is $mech->uri->path, '/my', "redirected to correct page";
my $expiry = $mech->session_cookie_expiry;
$remember_me
? cmp_ok( $expiry, '>', 86400, "long expiry time" )
: is( $expiry, 0, "no expiry time" );
# logout
$mech->log_out_ok;
};
}
# try to sign in with bad details
$mech->get_ok('/auth');
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => {
username => $test_email,
password_sign_in => 'not the password',
},
button => 'sign_in_by_password',
},
"sign in with '$test_email' & 'not the password'"
);
is $mech->uri->path, '/auth', "redirected to correct page";
$mech->content_contains( 'problem with your login information', 'found error message' );
subtest "sign in but have email form autofilled" => sub {
$mech->get_ok('/auth');
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => {
username => $test_email,
password_sign_in => $test_password,
name => 'Auto-completed from elsewhere',
},
button => 'sign_in_by_password',
},
"sign in with '$test_email' and auto-completed name"
);
is $mech->uri->path, '/my', "redirected to correct page";
};
$mech->log_out_ok;
subtest "sign in with uppercase email" => sub {
$mech->get_ok('/auth');
my $uc_test_email = uc $test_email;
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => {
username => $uc_test_email,
password_sign_in => $test_password,
},
button => 'sign_in_by_password',
},
"sign in with '$uc_test_email' and auto-completed name"
);
is $mech->uri->path, '/my', "redirected to correct page";
$mech->content_contains($test_email);
$mech->content_lacks($uc_test_email);
my $count = FixMyStreet::App->model('DB::User')->search( { email => $uc_test_email } )->count;
is $count, 0, "uppercase user wasn't created";
};
FixMyStreet::override_config {
SIGNUPS_DISABLED => 1,
}, sub {
subtest 'signing in with an unknown email address disallowed' => sub {
$mech->log_out_ok;
# create a new account
$mech->clear_emails_ok;
$mech->get_ok('/auth');
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => { username => $test_email3, },
button => 'sign_in_by_code',
},
"create a new account"
);
ok $mech->email_count_is(0);
my $count = FixMyStreet::App->model('DB::User')->search( { email => $test_email3 } )->count;
is $count, 0, "no user exists";
};
subtest 'signing in as known email address with new password is allowed' => sub {
my $new_password = "myshinynewpassword";
$mech->clear_emails_ok;
$mech->get_ok('/auth');
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => {
username => "$test_email",
password_register => $new_password,
r => 'faq', # Just as a test
},
button => 'sign_in_by_code',
},
"sign_in_by_code with '$test_email'"
);
$mech->not_logged_in_ok;
ok $mech->email_count_is(1);
my $link = $mech->get_link_from_email;
$mech->get_ok($link);
is $mech->uri->path, '/faq', "redirected to the Help page";
$mech->log_out_ok;
$mech->get_ok('/auth');
$mech->submit_form_ok(
{
form_name => 'general_auth',
fields => {
username => $test_email,
password_sign_in => $new_password,
},
button => 'sign_in_by_password',
},
"sign in with '$test_email' and new password"
);
is $mech->uri->path, '/my', "redirected to correct page";
};
};
subtest "check logging in with token" => sub {
$mech->log_out_ok;
$mech->not_logged_in_ok;
my $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } );
# token needs to be 18 characters
$user->set_extra_metadata('access_token', '1234567890abcdefgh');
$user->update();
$mech->add_header('Authorization', 'Bearer 1234567890abcdefgh');
$mech->logged_in_ok;
$mech->delete_header('Authorization');
$mech->not_logged_in_ok;
$mech->get_ok('/auth/check_auth?access_token=1234567890abcdefgh');
$mech->add_header('Authorization', 'Bearer 1234567890abcdefgh');
$user->set_extra_metadata('access_token', 'XXXXXXXXXXXXXXXXXX');
$user->update();
$mech->not_logged_in_ok;
$mech->delete_header('Authorization');
};
subtest 'check password length/common' => sub {
$mech->get_ok('/auth');
$mech->submit_form_ok({
form_name => 'general_auth',
fields => { username => $test_email, password_register => 'short' },
button => 'sign_in_by_code',
});
$mech->content_contains("Please make sure your password is at least");
$mech->submit_form_ok({
form_name => 'general_auth',
fields => { username => $test_email, password_register => 'common' },
button => 'sign_in_by_code',
});
$mech->content_contains("Please choose a less commonly-used password");
};
subtest 'check common password AJAX call' => sub {
$mech->post_ok('/auth/common_password', { password_register => 'password' });
$mech->content_contains("Please choose a less commonly-used password");
$mech->post_ok('/auth/common_password', { password_register => 'squirblewirble' });
$mech->content_contains("true");
};
subtest "Test two-factor authentication login" => sub {
use Auth::GoogleAuth;
my $auth = Auth::GoogleAuth->new;
my $code = $auth->code;
my $wrong_code = $auth->code(undef, time() - 120);
my $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } );
$user->is_superuser(1);
$user->password('password');
$user->set_extra_metadata('2fa_secret', $auth->secret32);
$user->update;
$mech->get_ok('/auth');
$mech->submit_form_ok(
{ with_fields => { username => $test_email, password_sign_in => 'password' } },
"sign in using form" );
$mech->content_contains('Please generate a two-factor code');
$mech->submit_form_ok({ with_fields => { '2fa_code' => $wrong_code } }, "provide wrong 2FA code" );
$mech->content_contains('Try again');
$mech->submit_form_ok({ with_fields => { '2fa_code' => $code } }, "provide correct 2FA code" );
$mech->logged_in_ok;
};