1 <?php
2
3 4 5 6 7 8 9 10
11
12 namespace Icybee\Modules\Users;
13
14 use ICanBoogie\I18n;
15 use ICanBoogie\I18n\Translator\Proxi;
16
17 18 19
20 class LoginOperation extends \ICanBoogie\Operation
21 {
22 23 24
25 protected function get_controls()
26 {
27 return [
28
29 self::CONTROL_FORM => true
30
31 ] + parent::get_controls();
32 }
33
34 35 36
37 protected function lazy_get_form()
38 {
39 return new LoginForm();
40 }
41
42 protected function validate(\ICanboogie\Errors $errors)
43 {
44 global $core;
45
46 $request = $this->request;
47 $username = $request[User::USERNAME];
48 $password = $request[User::PASSWORD];
49
50 $uid = $core->models['users']
51 ->select('uid')
52 ->where('username = ? OR email = ?', $username, $username)
53 ->rc;
54
55 if (!$uid)
56 {
57 $errors[User::PASSWORD] = $errors->format('Unknown username/password combination.');
58
59 return false;
60 }
61
62 $user = $core->models['users'][$uid];
63
64 $now = time();
65 $login_unlock_time = $user->metas['login_unlock_time'];
66
67 if ($login_unlock_time)
68 {
69 if ($login_unlock_time > $now)
70 {
71 throw new \ICanBoogie\HTTP\HTTPError
72 (
73 \ICanBoogie\format("The user account has been locked after multiple failed login attempts.
74 An e-mail has been sent to unlock the account. Login attempts are locked until %time,
75 unless you unlock the account using the email sent.", [
76
77 '%count' => $user->metas['failed_login_count'],
78 '%time' => I18n\format_date($login_unlock_time, 'HH:mm')
79
80 ]),
81
82 403
83 );
84 }
85
86 $user->metas['login_unlock_time'] = null;
87 }
88
89 if (!$user->verify_password($password))
90 {
91 $errors[User::PASSWORD] = $errors->format('Unknown username/password combination.');
92
93 $user->metas['failed_login_count'] += 1;
94 $user->metas['failed_login_time'] = $now;
95
96 if ($user->metas['failed_login_count'] >= 10)
97 {
98 $token = \ICanBoogie\generate_token(40, \ICanBoogie\TOKEN_ALPHA . \ICanBoogie\TOKEN_NUMERIC);
99
100 $user->metas['login_unlock_token'] = $token;
101 $user->metas['login_unlock_time'] = $now + 3600;
102
103 $until = I18n\format_date($now + 3600, 'HH:mm');
104
105 $url = $core->site->url . '/api/users/unlock_login?' . http_build_query([
106
107 'username' => $username,
108 'token' => $token,
109 'continue' => $request->uri
110
111 ]);
112
113 $t = new Proxi([ 'scope' => [ \ICanBoogie\normalize($user->constructor, '_'), 'connect', 'operation' ] ]);
114
115 $core->mail([
116
117 'destination' => $user->email,
118 'from' => 'no-reply@' . $_SERVER['HTTP_HOST'],
119 'subject' => "Your account has been locked",
120 'body' => <<<EOT
121 You receive this message because your account has been locked.
122
123 After multiple failed login attempts your account has been locked until $until. You can use the
124 following link to unlock your account and try to login again:
125
126 <$url>
127
128 If you forgot your password, you'll be able to request a new one.
129
130 If you didn't try to login neither forgot your password, this message might be the result of an
131 attack attempt on the website. If you think this is the case, please contact its admin.
132
133 The remote address of the request was: $request->ip.
134 EOT
135 ]);
136
137 unset($errors[User::PASSWORD]);
138
139 $errors[] = $errors->format("Your account has been locked, a message has been sent to your e-mail address.");
140 }
141
142 return false;
143 }
144
145 if (!$user->is_admin && !$user->is_activated)
146 {
147 $errors[] = $errors->format('User %username is not activated', [ '%username' => $username ]);
148
149 return false;
150 }
151
152 $this->record = $user;
153
154 return true;
155 }
156
157 /**
158 * Saves the user id in the session, sets the `user` property of the core object, updates the
159 * user's last connection date and finaly changes the operation location to the same request
160 * uri.
161 *
162 * @return bool `true` if the user is logged.
163 */
164 protected function process()
165 {
166 $user = $this->record;
167 $user->metas['failed_login_count'] = null;
168 $user->metas['failed_login_time'] = null;
169 $user->login();
170 $user->logged_at = 'now';
171 $user->save();
172
173 $redirect_to = ($this->request['redirect_to'] ?: $this->request['continue']) ?: null;
174
175 if ($redirect_to)
176 {
177 $this->response->location = $redirect_to;
178 }
179
180 return true;
181 }
182 }