File Coverage: lib/Yukki/Web/Controller/Login.pm

# File Coverage

File: | lib/Yukki/Web/Controller/Login.pm
Coverage: | 37.1%

line | stmt | bran | cond | sub | pod | time | code
--- | --- | --- | --- | --- | --- | --- | ---
1 |  |  |  |  |  |  | package Yukki::Web::Controller::Login;
2 |
3 | 1 1 |  |  | 1 |  | 617 3 | use v5.24;
4 | 1 1 1 |  |  | 1 |  | 3 1 5 | use utf8;
5 | 1 1 1 |  |  | 1 |  | 10 1 3 | use Moo;
6 |
7 |  |  |  |  |  |  | with 'Yukki::Web::Controller';
8 |
9 | 1 1 1 |  |  | 1 |  | 184 2 12 | use Email::Address;
10 | 1 1 1 |  |  | 1 |  | 3 1 3 | use Yukki::Error qw( http_throw );
11 | 1 1 1 |  |  | 1 |  | 145 1 5 | use Yukki::TextUtil qw( dump_file );
12 |
13 | 1 1 1 |  |  | 1 |  | 127 2 3 | use namespace::clean;
14 |
15 |  |  |  |  |  |  | # ABSTRACT: shows the login page and handles login
16 |
17 - 27 |  |
    =head1 DESCRIPTION

    Shows the login page and handles login.

    =head1 METHODS

    =head2 fire

    Routes page reqquests to L</show_login_page>, submit requests to L</check_login_submission>, and exit requests to L</logout>.

    =cut
28 |
29 |  |  |  |  |  |  | sub fire {
30 | 1 |  |  | 1 | 1 | 2 | my ($self, $ctx) = @_;
31 |
32 | 1 |  |  |  |  | 1 | my $res;
33 | 1 |  |  |  |  | 12 | my $action = $ctx->request->path_parameters->{action};
34 | 1 0 | 50 50 0 0 0 |  |  |  | 19 0 | if ($action eq 'page') { $self->show_login_page($ctx) }
35 | 1 |  |  |  |  | 4 | elsif ($action eq 'submit') { $self->check_login_submission($ctx) }
36 | 0 |  |  |  |  | 0 | elsif ($action eq 'profile') { $self->show_profile_page($ctx) }
37 | 0 |  |  |  |  | 0 | elsif ($action eq 'update') { $self->update_profile($ctx) }
38 | 0 |  |  |  |  | 0 | elsif ($action eq 'exit') { $self->logout($ctx) }
39 |  |  |  |  |  |  | else {
40 | 0 |  |  |  |  | 0 | http_throw('That login action does not exist.', {
41 |  |  |  |  |  |  | status => 'NotFound',
42 |  |  |  |  |  |  | })
43 |  |  |  |  |  |  | }
44 |  |  |  |  |  |  | }
45 |
46 - 50 |  |
    =head2 show_login_page

    Calls L<Yukki::Web::View::Login/page> to display the login page.

    =cut
51 |
52 |  |  |  |  |  |  | sub show_login_page {
53 | 0 |  |  | 0 | 1 | 0 | my ($self, $ctx) = @_;
54 |
55 | 0 |  |  |  |  | 0 | $ctx->response->body( $self->view('Login')->page($ctx) );
56 |  |  |  |  |  |  | }
57 |
58 - 62 |  |
    =head2 show_profile_page

    Calls L<Yukki::Web::View::Login/profile> to display the profile page.

    =cut
63 |
64 |  |  |  |  |  |  | sub show_profile_page {
65 | 0 |  |  | 0 | 1 | 0 | my ($self, $ctx, $name, $email) = @_;
66 |
67 | 0 |  |  |  |  | 0 | $ctx->response->body(
68 |  |  |  |  |  |  | $self->view('Login')->profile($ctx, $name, $email)
69 |  |  |  |  |  |  | );
70 |  |  |  |  |  |  | }
71 |
72 - 76 |  |
    =head2 update_profile

    Validates the input user information and updates the user. Redirects the user back to the profile page.

    =cut
77 |
78 |  |  |  |  |  |  | sub update_profile {
79 | 0 |  |  | 0 | 1 | 0 | my ($self, $ctx) = @_;
80 |
81 |  |  |  |  |  |  | http_throw('You are not authorized to run this action.', {
82 |  |  |  |  |  |  | status => 'Forbidden',
83 | 0 | 0 |  |  |  | 0 | }) unless $ctx->session->{user};
84 |
85 | 0 |  |  |  |  | 0 | my $login_name = $ctx->request->body_parameters->{login_name};
86 |
87 | 0 | 0 |  |  |  | 0 | unless ($login_name eq $ctx->session->{user}{login_name}) {
88 | 0 |  |  |  |  | 0 | $ctx->add_errors('Are you sure you are logged in as the correct user? Please make sure and try again.');
89 | 0 |  |  |  |  | 0 | $ctx->response->redirect('/profile');
90 | 0 |  |  |  |  | 0 | return;
91 |  |  |  |  |  |  | }
92 |
93 | 0 |  |  |  |  | 0 | my $name = $ctx->request->body_parameters->{name};
94 | 0 |  |  |  |  | 0 | my $email = $ctx->request->body_parameters->{email};
95 |
96 | 0 0 |  |  |  |  | 0 0 | $name =~ s/^\s+//; $name =~ s/\s+$//;
97 | 0 0 |  |  |  |  | 0 0 | $email =~ s/^\s+//; $email =~ s/\s+$//;
98 |
99 | 0 |  |  |  |  | 0 | my $invalid = 0;
100 | 0 | 0 |  |  |  | 0 | unless ($name =~ /\S+/) {
101 | 0 |  |  |  |  | 0 | $ctx->add_errors('name must contain at least one letter');
102 | 0 |  |  |  |  | 0 | $invalid++;
103 |  |  |  |  |  |  | }
104 |
105 | 0 |  |  |  |  | 0 | my @emails = Email::Address->parse($email);
106 | 0 | 0 |  |  |  | 0 | if (@emails != 1) {
107 | 0 |  |  |  |  | 0 | $ctx->add_errors('that does not appear to be an email address');
108 | 0 |  |  |  |  | 0 | $invalid++;
109 |  |  |  |  |  |  | }
110 |
111 | 0 | 0 |  |  |  | 0 | if ($invalid) {
112 | 0 |  |  |  |  | 0 | return $self->show_profile_page($ctx, $name, $email);
113 |  |  |  |  |  |  | }
114 |
115 | 1 1 1 |  |  | 1 |  | 909 28038 6 | use DDP;
116 |
117 | 0 |  |  |  |  | 0 | my %user = $ctx->session->{user}->%*;
118 | 0 |  |  |  |  | 0 | p %user;
119 | 0 |  |  |  |  | 0 | $user{name} = $name;
120 | 0 |  |  |  |  | 0 | $user{email} = $email;
121 |
122 | 0 |  |  |  |  | 0 | my $password_old = $ctx->request->body_parameters->{password_old};
123 | 0 |  |  |  |  | 0 | my $password_new = $ctx->request->body_parameters->{password_new};
124 | 0 |  |  |  |  | 0 | my $password_con = $ctx->request->body_parameters->{password_confirm};
125 |
126 |  |  |  |  |  |  | # Only activate password check/reset if they use it
127 | 0 | 0 |  |  |  | 0 | if (length $password_old) {
128 |  |  |  |  |  |  | my $okay = $self->check_password(
129 |  |  |  |  |  |  | $ctx->session->{user},
130 | 0 |  |  |  |  | 0 | $password_old,
131 |  |  |  |  |  |  | );
132 |
133 | 0 | 0 |  |  |  | 0 | unless ($okay) {
134 | 0 |  |  |  |  | 0 | $ctx->add_errors('the current password you entered is incorrect');
135 | 0 |  |  |  |  | 0 | return $self->show_profile_page($ctx, $name, $email);
136 |  |  |  |  |  |  | }
137 |
138 | 0 | 0 |  |  |  | 0 | if (length($password_new) == 0) {
139 | 0 |  |  |  |  | 0 | $ctx->add_errors('you must enter a new password');
140 | 0 |  |  |  |  | 0 | return $self->show_profile_page($ctx, $name, $email);
141 |  |  |  |  |  |  | }
142 |
143 | 0 | 0 |  |  |  | 0 | if ($password_old eq $password_new) {
144 | 0 |  |  |  |  | 0 | $ctx->add_errors('the new and old passwords you entered are the same');
145 | 0 |  |  |  |  | 0 | return $self->show_profile_page($ctx, $name, $email);
146 |  |  |  |  |  |  | }
147 |
148 | 0 | 0 |  |  |  | 0 | if ($password_new ne $password_con) {
149 | 0 |  |  |  |  | 0 | $ctx->add_errors('the new passwords you entered do not match');
150 | 0 |  |  |  |  | 0 | return $self->show_profile_page($ctx, $name, $email);
151 |  |  |  |  |  |  | }
152 |
153 | 0 |  |  |  |  | 0 | my $digest = $self->app->hasher;
154 | 0 |  |  |  |  | 0 | $digest->add($password_new);
155 | 0 |  |  |  |  | 0 | $user{password} = $digest->generate;
156 |  |  |  |  |  |  | }
157 |
158 | 0 |  |  |  |  | 0 | my $user_file = $self->app->locate('user_path', $login_name);
159 | 0 |  |  |  |  | 0 | chmod 0600, "$user_file";
160 | 0 |  |  |  |  | 0 | dump_file($user_file, \%user);
161 | 0 |  |  |  |  | 0 | chmod 0400, "$user_file";
162 |
163 | 0 |  |  |  |  | 0 | $ctx->session->{user} = \%user;
164 |
165 | 0 |  |  |  |  | 0 | $ctx->response->redirect('/profile');
166 | 0 |  |  |  |  | 0 | return;
167 |  |  |  |  |  |  | }
168 |
169 - 173 |  |
    =head2 check_password

    Checks that the user's password is valid.

    =cut
174 |
175 |  |  |  |  |  |  | sub check_password {
176 | 1 |  |  | 1 | 1 | 2 | my ($self, $user, $password) = @_;
177 |
178 |  |  |  |  |  |  | return scalar $self->app->hasher->validate(
179 |  |  |  |  |  |  | $user->{password},
180 | 1 |  |  |  |  | 9 | $password,
181 |  |  |  |  |  |  | );
182 |  |  |  |  |  |  | }
183 |
184 - 188 |  |
    =head2 check_login_submission

    Authenticates a user login.

    =cut
189 |
190 |  |  |  |  |  |  | sub check_login_submission {
191 | 1 |  |  | 1 | 1 | 3 | my ($self, $ctx) = @_;
192 |
193 | 1 |  |  |  |  | 11 | my $login_name = $ctx->request->body_parameters->{login_name};
194 | 1 |  |  |  |  | 445 | my $password = $ctx->request->body_parameters->{password};
195 |
196 | 1 |  |  |  |  | 47 | my $user = $self->model('User')->find(login_name => $login_name);
197 |
198 | 1 | 50 | 33 |  |  | 70 | if (not ($user and $self->check_password($user, $password))) {
199 | 0 |  |  |  |  | 0 | $ctx->add_errors('no such user or you typed your password incorrectly');
200 |  |  |  |  |  |  | }
201 |
202 | 1 | 50 |  |  |  | 317 | if ($ctx->has_errors) {
203 | 0 |  |  |  |  | 0 | $self->show_login_page($ctx);
204 | 0 |  |  |  |  | 0 | return;
205 |  |  |  |  |  |  | }
206 |
207 |  |  |  |  |  |  | else {
208 | 1 |  |  |  |  | 20 | $ctx->session->{user} = $user;
209 |
210 | 1 |  |  |  |  | 79 | $ctx->response->redirect($ctx->rebase_url('page/view/main'));
211 | 1 |  |  |  |  | 184 | return;
212 |  |  |  |  |  |  | }
213 |  |  |  |  |  |  | }
214 |
215 - 219 |  |
    =head2 logout

    Expires the session, causing logout.

    =cut
220 |
221 |  |  |  |  |  |  | sub logout {
222 | 0 |  |  | 0 | 1 |  | my ($self, $ctx) = @_;
223 |
224 | 0 |  |  |  |  |  | $ctx->session_options->{expire} = 1;
225 | 0 |  |  |  |  |  | $ctx->response->redirect($ctx->rebase_url('page/view/main'));
226 |  |  |  |  |  |  | }
227 |
228 |  |  |  |  |  |  | 1;