OSDN Git Service

Plugin::Users: change_email() doesn't "new_email_ts" param
[newslash/newslash.git] / src / newslash_web / lib / Newslash / Plugin / Users.pm
1 package Newslash::Plugin::Users;
2 use Mojo::Base 'Mojolicious::Plugin';
3 use Email::Valid;
4 use DateTime;
5
6 has 'last_error';
7 has 'app';
8
9 sub register {
10     my ($self, $app, $conf) = @_;
11     $self->app($app);
12     $app->helper(users => sub { state $users = $self; });
13
14     # default config values
15     my $cnf = $app->config->{Users} ||= {};
16     $cnf->{newpasswd_expiration} ||= 60 * 60 * 24; # 60[sec] * 60[min] * 24[hour]
17 }
18
19 sub change_email {
20     my ($self, $user, $email) = @_;
21
22     if (!$user || !$user->{uid}) {
23         $self->last_error("INVALID_USER");
24         return;
25     }
26
27     if (!$email || !Email::Valid->address($email)) {
28         $self->last_error("INVALID_EMAIL");
29         return;
30     }
31
32     # use users_param table to save temporary new email address
33
34     my $users = $self->app->model('users');
35     my $param = $users->param;
36
37     my $rs = $param->set(uid => $user->{uid},
38                          name => "new_email",
39                          value => $email);
40
41     if (!defined $rs) {
42         $self->app->error("Users: update users_param table for change_email failed! uid: $user->{uid}");
43         $self->last_error($param->last_error);
44         return;
45     }
46
47     $self->app->event_que->emit("user", "change_email", $user->{uid}, $user->{uid}, 10);
48     return 1;
49 }
50
51 sub reset_password {
52     my ($self, $user) = @_;
53
54     if (!$user || $user->{uid}) {
55         $self->last_error("INVALID_USER");
56         return;
57     }
58
59     $self->app->event_que->emit("user", "reset_password", $user->{uid}, $user->{uid}, 10);
60     return 1;
61 }
62
63 sub cancel_activation {
64     my ($self, $user) = @_;
65     if (!$user || $user->{uid}) {
66         $self->last_error("INVALID_USER");
67         return;
68     }
69
70     my $users = $self->app->model('users');
71
72     my $rs = $users->update(uid => $user->{uid},
73                             newpasswd => "",
74                             newpasswd_ts => {function => "NULL"});
75     if (!defined $rs) {
76         $self->app->log->error("Users: newpasswd reset error! uid: $user->{uid}");
77         $self->last_error($users->last_error);
78         return;
79     }
80     return 1;
81 }
82
83 sub update_password_by_token {
84     my ($self, $nickname, $token, $password) = @_;
85
86     # check nickname and token pair
87     my $user = $self->activation($nickname, $token);
88     return if !$user;
89
90     return $self->_update_password($user, $password);
91 }
92
93 sub update_password {
94     my ($self, $user, $old_password, $new_password) = @_;
95     if (!$user || !$user->{uid}) {
96         $self->last_error("INVALID_USER");
97         return;
98     }
99
100     my $users = $self->app->model('users');
101
102     if ($users->passwords->verify_password(uid => $user->{uid},
103                                            password => $old_password)) {
104         # old password is correct.
105         return $self->_update_password($user, $new_password);
106     }
107
108     # old password is incorrect!
109     $self->last_error("INCORRECT_PASSWORD");
110     return;
111 }
112
113 sub _update_password {
114     my ($self, $user, $password) = @_;
115
116     if (!$user || !$user->{uid}) {
117         $self->last_error("INVALID_USER");
118         return;
119     }
120
121     my $users = $self->app->model('users');
122
123     my @params = (uid => $user->{uid},
124                   passwd => $password );
125
126     if ($user->{seclev} < 1) {
127         push @params, seclev => 1;
128     }
129
130     if ($user->{newpasswd}) {
131         push @params, newpasswd => "";
132         push @params, newpasswd_ts => { function => "NULL" };
133     }
134
135     my $rs = $users->update(@params);
136
137     if (!defined $rs) {
138         $self->last_error($users->last_error);
139         return;
140     }
141     $self->app->event_que->emit("user", "update_password", $user->{uid}, $user->{uid}, 10);
142     return 1;
143 }
144
145
146 sub activation {
147     my ($self, $nickname, $token) = @_;
148     return if (!$nickname || !$token);
149
150     my $users = $self->app->model('users');
151     my $user = $users->select(nickname => $nickname);
152
153     # check if token is correct
154     if (!$user
155         || !$users->passwords->compare_password($token, $user->{newpasswd})
156         || !$user->{newpasswd_ts}) {
157         $self->last_error("INVALID_TOKEN");
158         return;
159     }
160
161     # check if token is expired
162     my $expiration_limit = $self->app->config->{Users}->{newpasswd_expiration};
163     my $expire_dt = eval { DateTime::Format::MySQL->parse_datetime($user->{newpasswd_ts}) };
164     if (!$expire_dt) {
165         $self->app->log->error("Users: invalid newpasswd_ts ($user->{newpasswd_ts}). uid: $user->{uid}");
166         $self->last_error("INVALID_TOKEN");
167         return;
168     }
169     $expire_dt->add( seconds => $expiration_limit);
170     if ($expire_dt->epoch() < time()) {
171         $self->last_error("TOKEN_EXPIRED");
172         return;
173     }
174
175     # ok
176     return $user;
177 }
178
179 sub create_new_user {
180     my ($self, $nickname, $email, $options) = @_;
181     my $users = $self->app->model('users');
182     $options ||= {};
183
184     # check $nickname and $email
185     my ($id_error, $email_error) = $self->validate_new_user($nickname, $email);
186     if ($id_error || $email_error) {
187         $self->last_error({ id_error => $id_error,
188                             email_error => $email_error });
189         return;
190     }
191
192     my $uid = $users->create($nickname, $email, "", { seclev => 0 });
193     if (!$uid) {
194         # error occured
195         $self->last_error({ id_error => $id_error,
196                             email_error => $email_error,
197                             system_error => $users->last_error });
198         return;
199     }
200
201     # check options
202     if ($options->{message}) {
203         my $message_types = $self->app->model('messages');
204         for my $k (keys %{$options->{message}}) {
205             my $rs = $users->messages->update(uid => $uid,
206                                               name => $k,
207                                               mode => $options->{message}->{$k});
208             if (!defined $rs) {
209                 $self->app->log->error("Users: message update failed! uid: $uid, name: $k, mode: $options->{message}->{$k}");
210             }
211         }
212     }
213
214     $self->app->event_que->emit("user", "create", $uid, $uid, 10);
215     return $uid;
216 }
217
218 sub validate_new_user {
219     my ($self, $nickname, $email) = @_;
220     my $nick_regex = qr/^[a-zA-Z_][ a-zA-Z0-9\$_.+!*\'(),-]{0,19}$/;
221     my $users = $self->app->model('users');
222
223     my ($id_error, $email_error);
224     $id_error = "BLANK_ID" if !$nickname;
225     $email_error = "BLANK_EMAIL" if !$email;
226
227     if (!$id_error) {
228         # nickname is valid?
229         if ($nickname =~ $nick_regex) {
230             my $matchname = $users->nickname_to_matchname($nickname);
231             my $rs = $users->select(matchname => $matchname);
232             if ($rs) {
233                 $id_error = "ID_EXISTS";
234             }
235         }
236         else {
237             $id_error = "INVALID_ID";
238         }
239     }
240
241     if (!$email_error) {
242         if (Email::Valid->address($email)) {
243             my $rs = $users->select(realemail => $email);
244             if ($rs) {
245                 $email_error = "EMAIL_EXISTS";
246             }
247         }
248         else {
249             $email_error = "INVALID_EMAIL";
250         }
251     }
252
253     if ($id_error || $email_error) {
254         $self->last_error({id_error => $id_error, email_error => $email_error});
255         return;
256     }
257
258     return 1;
259 }
260
261 1;
262
263 =encoding utf8
264
265 =head1 NAME
266
267 Newslash::Plugin::Users - Newslash users manipulation plugin
268
269 =head1 SYNOPSIS
270
271   # Mojolicious
272   $app->plugin('Newslash::Plugin::Users');
273
274 =head1 DESCRIPTION
275
276 L<Newslash::Plugin::Users> is 'Business Logic' layer of Newslash.
277
278
279 =head1 HELPERS
280
281 L<Mojolicious::Plugin::Users> implements the following helpers.
282
283 =head2 boxes
284
285   [% helpers.boxes() %]
286
287 Fetch box contents for current user and returns them.
288
289 =head2 format_timestamp
290
291   $c->format_timestamp(user => $user, epoch => $epoch, format => "user")
292   $c->format_timestamp(datetime => $dt, format => "simple")
293
294 Return formated string.
295
296 =head1 METHODS
297
298 =head2 register
299
300   $plugin->register(Mojolicious->new);
301
302 Register helpers in L<Mojolicious> application.
303
304 =head2 validate_new_user
305
306   my ($id_error, $email_error) = $plugin->validate_new_user($nickname, $email);
307
308 Check nickname and email address are not registered.
309
310 $id_error is one of below string or undef (no error).
311
312   BLANK_ID
313   ID_EXISTS
314   INVALID_ID
315
316 $email_error is one of below string or undef (no error).
317
318   BLANK_EMAIL
319   EMAIL_EXISTS
320   INVALID_EMAIL
321
322
323 =head1 SEE ALSO
324
325 L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicious.org>.
326
327 =cut