OSDN Git Service

21c11133e271f75c5859d3d90ff5d7734d47e62d
[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 reset_password {
20     my ($self, $user) = @_;
21
22     $self->app->event_que->emit("user", "resetpw", $user->{uid}, $user->{uid});
23     return 1;
24 }
25
26 sub cancel_activation {
27     my ($self, $user) = @_;
28     my $users = $self->app->model('users');
29
30     my $rs = $users->update(uid => $user->{uid},
31                             newpasswd => "",
32                             newpasswd_ts => {function => "NULL"});
33     if (!defined $rs) {
34         $self->app->log->error("Users: newpasswd reset error! uid: $user->{uid}");
35         $self->last_error($users->last_error);
36         return;
37     }
38     return 1;
39 }
40
41 sub update_password_by_token {
42     my ($self, $nickname, $token, $password) = @_;
43
44     # check nickname and token pair
45     my $the_user = $self->activation($nickname, $token);
46     return if !$the_user;
47
48     return $self->_update_password($the_user, $password);
49 }
50
51 sub update_password {
52     my ($self, $user, $old_password, $new_password) = @_;
53     return;
54 }
55
56 sub _update_password {
57     my ($self, $the_user, $password) = @_;
58     my $users = $self->app->model('users');
59
60     my @params = (uid => $the_user->{uid},
61                   passwd => $password );
62
63     if ($the_user->{seclev} < 1) {
64         push @params, seclev => 1;
65     }
66
67     if ($the_user->{newpasswd}) {
68         push @params, newpasswd => "";
69         push @params, newpasswd_ts => { function => "NULL" };
70     }
71
72     my $rs = $users->update(@params);
73
74     if (!defined $rs) {
75         $self->last_error($users->last_error);
76         return;
77     }
78     return 1;
79 }
80
81
82 sub activation {
83     my ($self, $nickname, $token) = @_;
84     return if (!$nickname || !$token);
85
86     my $users = $self->app->model('users');
87     my $the_user = $users->select(nickname => $nickname);
88
89     # check if token is correct
90     if (!$the_user
91         || !$users->passwords->compare_password($token, $the_user->{newpasswd})
92         || !$the_user->{newpasswd_ts}) {
93         $self->last_error("INVALID_TOKEN");
94         return;
95     }
96
97     # check if token is expired
98     my $expiration_limit = $self->app->config->{Users}->{newpasswd_expiration};
99     my $expire_dt = eval { DateTime::Format::MySQL->parse_datetime($the_user->{newpasswd_ts}) };
100     if (!$expire_dt) {
101         $self->app->log->error("Users: invalid newpasswd_ts ($the_user->{newpasswd_ts}). uid: $the_user->{uid}");
102         $self->last_error("INVALID_TOKEN");
103         return;
104     }
105     $expire_dt->add( seconds => $expiration_limit);
106     if ($expire_dt->epoch() < time()) {
107         $self->last_error("TOKEN_EXPIRED");
108         return;
109     }
110
111     # ok
112     return $the_user;
113 }
114
115 sub create_new_user {
116     my ($self, $nickname, $email, $options) = @_;
117     my $users = $self->app->model('users');
118     $options ||= {};
119
120     # check $nickname and $email
121     my ($id_error, $email_error) = $self->validate_new_user($nickname, $email);
122     if ($id_error || $email_error) {
123         $self->last_error({ id_error => $id_error,
124                             email_error => $email_error });
125         return;
126     }
127
128     my $uid = $users->create($nickname, $email, "", { seclev => 0 });
129     if (!$uid) {
130         # error occured
131         $self->last_error({ id_error => $id_error,
132                             email_error => $email_error,
133                             system_error => $users->last_error });
134         return;
135     }
136
137     # check options
138     if ($options->{message}) {
139         my $message_types = $self->app->model('messages');
140         for my $k (keys %{$options->{message}}) {
141             my $rs = $users->messages->update(uid => $uid,
142                                               name => $k,
143                                               mode => $options->{message}->{$k});
144             if (!defined $rs) {
145                 $self->app->log->error("Users: message update failed! uid: $uid, name: $k, mode: $options->{message}->{$k}");
146             }
147         }
148     }
149
150     $self->app->event_que->emit("user", "create", $uid, $uid);
151     return $uid;
152 }
153
154 sub validate_new_user {
155     my ($self, $nickname, $email) = @_;
156     my $nick_regex = qr/^[a-zA-Z_][ a-zA-Z0-9\$_.+!*\'(),-]{0,19}$/;
157     my $users = $self->app->model('users');
158
159     my ($id_error, $email_error);
160     $id_error = "BLANK_ID" if !$nickname;
161     $email_error = "BLANK_EMAIL" if !$email;
162
163     if (!$id_error) {
164         # nickname is valid?
165         if ($nickname =~ $nick_regex) {
166             my $matchname = $users->nickname_to_matchname($nickname);
167             my $rs = $users->select(matchname => $matchname);
168             if ($rs) {
169                 $id_error = "ID_EXISTS";
170             }
171         }
172         else {
173             $id_error = "INVALID_ID";
174         }
175     }
176
177     if (!$email_error) {
178         if (Email::Valid->address($email)) {
179             my $rs = $users->select(realemail => $email);
180             if ($rs) {
181                 $email_error = "EMAIL_EXISTS";
182             }
183         }
184         else {
185             $email_error = "INVALID_EMAIL";
186         }
187     }
188
189     if ($id_error || $email_error) {
190         $self->last_error({id_error => $id_error, email_error => $email_error});
191         return;
192     }
193
194     return 1;
195 }
196
197 1;
198
199 =encoding utf8
200
201 =head1 NAME
202
203 Newslash::Plugin::Users - Newslash users manipulation plugin
204
205 =head1 SYNOPSIS
206
207   # Mojolicious
208   $app->plugin('Newslash::Plugin::Users');
209
210 =head1 DESCRIPTION
211
212 L<Newslash::Plugin::Users> is 'Business Logic' layer of Newslash.
213
214
215 =head1 HELPERS
216
217 L<Mojolicious::Plugin::Users> implements the following helpers.
218
219 =head2 boxes
220
221   [% helpers.boxes() %]
222
223 Fetch box contents for current user and returns them.
224
225 =head2 format_timestamp
226
227   $c->format_timestamp(user => $user, epoch => $epoch, format => "user")
228   $c->format_timestamp(datetime => $dt, format => "simple")
229
230 Return formated string.
231
232 =head1 METHODS
233
234 =head2 register
235
236   $plugin->register(Mojolicious->new);
237
238 Register helpers in L<Mojolicious> application.
239
240 =head2 validate_new_user
241
242   my ($id_error, $email_error) = $plugin->validate_new_user($nickname, $email);
243
244 Check nickname and email address are not registered.
245
246 $id_error is one of below string or undef (no error).
247
248   BLANK_ID
249   ID_EXISTS
250   INVALID_ID
251
252 $email_error is one of below string or undef (no error).
253
254   BLANK_EMAIL
255   EMAIL_EXISTS
256   INVALID_EMAIL
257
258
259 =head1 SEE ALSO
260
261 L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicious.org>.
262
263 =cut