|
@@ -6,17 +6,17 @@ Add the JavaScript validation into the display page using the class
|
6
|
6
|
|
7
|
7
|
// Allow users to reset their password while logged in
|
8
|
8
|
if (!empty($LoggedUser['ID']) && $_REQUEST['act'] != 'recover') {
|
9
|
|
- header('Location: index.php');
|
10
|
|
- die();
|
|
9
|
+ header('Location: index.php');
|
|
10
|
+ die();
|
11
|
11
|
}
|
12
|
12
|
|
13
|
13
|
if (BLOCK_OPERA_MINI && isset($_SERVER['HTTP_X_OPERAMINI_PHONE'])) {
|
14
|
|
- error('Opera Mini is banned. Please use another browser.');
|
|
14
|
+ error('Opera Mini is banned. Please use another browser.');
|
15
|
15
|
}
|
16
|
16
|
|
17
|
17
|
// Check if IP is banned
|
18
|
18
|
if (Tools::site_ban_ip($_SERVER['REMOTE_ADDR'])) {
|
19
|
|
- error('Your IP address has been banned.');
|
|
19
|
+ error('Your IP address has been banned.');
|
20
|
20
|
}
|
21
|
21
|
|
22
|
22
|
require_once SERVER_ROOT.'/classes/twofa.class.php';
|
|
@@ -27,404 +27,407 @@ $Validate = new Validate;
|
27
|
27
|
$TwoFA = new TwoFactorAuth(SITE_NAME);
|
28
|
28
|
$U2F = new u2f\U2F('https://'.SITE_DOMAIN);
|
29
|
29
|
|
|
30
|
+# todo: Test strict equality very gently here
|
30
|
31
|
if (array_key_exists('action', $_GET) && $_GET['action'] == 'disabled') {
|
31
|
|
- require('disabled.php');
|
32
|
|
- die();
|
|
32
|
+ require('disabled.php');
|
|
33
|
+ die();
|
33
|
34
|
}
|
34
|
35
|
|
35
|
36
|
if (isset($_REQUEST['act']) && $_REQUEST['act'] == 'recover') {
|
36
|
|
- // Recover password
|
37
|
|
- if (!empty($_REQUEST['key'])) {
|
38
|
|
- // User has entered a new password, use step 2
|
39
|
|
- $DB->query("
|
40
|
|
- SELECT
|
41
|
|
- m.ID,
|
42
|
|
- m.Email,
|
43
|
|
- m.ipcc,
|
44
|
|
- i.ResetExpires
|
45
|
|
- FROM users_main AS m
|
46
|
|
- INNER JOIN users_info AS i ON i.UserID = m.ID
|
47
|
|
- WHERE i.ResetKey = ?
|
48
|
|
- AND i.ResetKey != ''", $_REQUEST['key']);
|
49
|
|
- list($UserID, $Email, $Country, $Expires) = $DB->next_record();
|
50
|
|
-
|
51
|
|
- if (!apcu_exists('DBKEY')) {
|
52
|
|
- error('Database not fully decrypted. Please wait for staff to fix this and try again later');
|
53
|
|
- }
|
54
|
|
-
|
55
|
|
- $Email = Crypto::decrypt($Email);
|
56
|
|
-
|
57
|
|
- if ($UserID && strtotime($Expires) > time()) {
|
58
|
|
- // If the user has requested a password change, and his key has not expired
|
59
|
|
- $Validate->SetFields('password', '1', 'regex', 'You entered an invalid password. Any password at least 6 characters long is accepted, but a strong password is 8 characters or longer, contains at least 1 lowercase and uppercase letter, contains at least a number or symbol', array('regex' => '/(?=^.{6,}$).*$/'));
|
60
|
|
- $Validate->SetFields('verifypassword', '1', 'compare', 'Your passwords did not match.', array('comparefield' => 'password'));
|
61
|
|
-
|
62
|
|
- if (!empty($_REQUEST['password'])) {
|
63
|
|
- // If the user has entered a password.
|
64
|
|
- // If the user has not entered a password, $PassWasReset is not set to 1, and the success message is not shown
|
65
|
|
- $Err = $Validate->ValidateForm($_REQUEST);
|
66
|
|
- if ($Err == '') {
|
67
|
|
- // Form validates without error, set new secret and password.
|
68
|
|
- $DB->query("
|
69
|
|
- UPDATE
|
70
|
|
- users_main AS m,
|
71
|
|
- users_info AS i
|
72
|
|
- SET
|
73
|
|
- m.PassHash = ?,
|
74
|
|
- i.ResetKey = '',
|
75
|
|
- m.LastLogin = NOW(),
|
76
|
|
- i.ResetExpires = NULL
|
77
|
|
- WHERE m.ID = ?
|
78
|
|
- AND i.UserID = m.ID", Users::make_sec_hash($_REQUEST['password']), $UserID);
|
79
|
|
- $DB->query("
|
80
|
|
- INSERT INTO users_history_passwords
|
81
|
|
- (UserID, ChangerIP, ChangeTime)
|
82
|
|
- VALUES
|
83
|
|
- (?, ?, NOW())", $UserID, Crypto::encrypt($_SERVER['REMOTE_ADDR']));
|
84
|
|
- $PassWasReset = true;
|
85
|
|
- $LoggedUser['ID'] = $UserID; // Set $LoggedUser['ID'] for logout_all_sessions() to work
|
86
|
|
- logout_all_sessions();
|
87
|
|
-
|
|
37
|
+ // Recover password
|
|
38
|
+ if (!empty($_REQUEST['key'])) {
|
|
39
|
+ // User has entered a new password, use step 2
|
|
40
|
+ $DB->query("
|
|
41
|
+ SELECT
|
|
42
|
+ m.ID,
|
|
43
|
+ m.Email,
|
|
44
|
+ m.ipcc,
|
|
45
|
+ i.ResetExpires
|
|
46
|
+ FROM users_main AS m
|
|
47
|
+ INNER JOIN users_info AS i ON i.UserID = m.ID
|
|
48
|
+ WHERE i.ResetKey = ?
|
|
49
|
+ AND i.ResetKey != ''", $_REQUEST['key']);
|
|
50
|
+ list($UserID, $Email, $Country, $Expires) = $DB->next_record();
|
|
51
|
+
|
|
52
|
+ if (!apcu_exists('DBKEY')) {
|
|
53
|
+ error('Database not fully decrypted. Please wait for staff to fix this and try again later');
|
88
|
54
|
}
|
89
|
|
- }
|
90
|
|
-
|
91
|
|
- // Either a form asking for them to enter the password
|
92
|
|
- // Or a success message if $PassWasReset is 1
|
93
|
|
- require('recover_step2.php');
|
94
|
55
|
|
95
|
|
- } else {
|
96
|
|
- // Either his key has expired, or he hasn't requested a pass change at all
|
97
|
|
- if (strtotime($Expires) < time() && $UserID) {
|
98
|
|
- // If his key has expired, clear all the reset information
|
99
|
|
- $DB->query("
|
100
|
|
- UPDATE users_info
|
101
|
|
- SET ResetKey = '',
|
102
|
|
- ResetExpires = NULL
|
103
|
|
- WHERE UserID = ?", $UserID);
|
104
|
|
- $_SESSION['reseterr'] = 'The link you were given has expired.'; // Error message to display on form
|
105
|
|
- }
|
106
|
|
- // Show him the first form (enter email address)
|
107
|
|
- header('Location: login.php?act=recover');
|
108
|
|
- }
|
|
56
|
+ $Email = Crypto::decrypt($Email);
|
109
|
57
|
|
110
|
|
- } // End step 2
|
|
58
|
+ if ($UserID && strtotime($Expires) > time()) {
|
|
59
|
+ // If the user has requested a password change, and his key has not expired
|
|
60
|
+ $Validate->SetFields('password', '1', 'regex', 'You entered an invalid password. Any password at least 6 characters long is accepted, but a strong password is 8 characters or longer, contains at least 1 lowercase and uppercase letter, contains at least a number or symbol', array('regex' => '/(?=^.{6,}$).*$/'));
|
|
61
|
+ $Validate->SetFields('verifypassword', '1', 'compare', 'Your passwords did not match.', array('comparefield' => 'password'));
|
|
62
|
+
|
|
63
|
+ if (!empty($_REQUEST['password'])) {
|
|
64
|
+ // If the user has entered a password.
|
|
65
|
+ // If the user has not entered a password, $PassWasReset is not set to 1, and the success message is not shown
|
|
66
|
+ $Err = $Validate->ValidateForm($_REQUEST);
|
|
67
|
+ if ($Err == '') {
|
|
68
|
+ // Form validates without error, set new secret and password.
|
|
69
|
+ $DB->query("
|
|
70
|
+ UPDATE
|
|
71
|
+ users_main AS m,
|
|
72
|
+ users_info AS i
|
|
73
|
+ SET
|
|
74
|
+ m.PassHash = ?,
|
|
75
|
+ i.ResetKey = '',
|
|
76
|
+ m.LastLogin = NOW(),
|
|
77
|
+ i.ResetExpires = NULL
|
|
78
|
+ WHERE m.ID = ?
|
|
79
|
+ AND i.UserID = m.ID", Users::make_sec_hash($_REQUEST['password']), $UserID);
|
|
80
|
+ $DB->query("
|
|
81
|
+ INSERT INTO users_history_passwords
|
|
82
|
+ (UserID, ChangerIP, ChangeTime)
|
|
83
|
+ VALUES
|
|
84
|
+ (?, ?, NOW())", $UserID, Crypto::encrypt($_SERVER['REMOTE_ADDR']));
|
|
85
|
+ $PassWasReset = true;
|
|
86
|
+ $LoggedUser['ID'] = $UserID; // Set $LoggedUser['ID'] for logout_all_sessions() to work
|
|
87
|
+ logout_all_sessions();
|
|
88
|
+ }
|
|
89
|
+ }
|
111
|
90
|
|
112
|
|
- // User has not clicked the link in his email, use step 1
|
113
|
|
- else {
|
114
|
|
- $Validate->SetFields('email', '1', 'email', 'You entered an invalid email address.');
|
|
91
|
+ // Either a form asking for them to enter the password
|
|
92
|
+ // Or a success message if $PassWasReset is 1
|
|
93
|
+ require('recover_step2.php');
|
|
94
|
+ } else {
|
|
95
|
+ // Either his key has expired, or he hasn't requested a pass change at all
|
|
96
|
+ if (strtotime($Expires) < time() && $UserID) {
|
|
97
|
+ // If his key has expired, clear all the reset information
|
|
98
|
+ $DB->query("
|
|
99
|
+ UPDATE users_info
|
|
100
|
+ SET ResetKey = '',
|
|
101
|
+ ResetExpires = NULL
|
|
102
|
+ WHERE UserID = ?", $UserID);
|
|
103
|
+ $_SESSION['reseterr'] = 'The link you were given has expired.'; // Error message to display on form
|
|
104
|
+ }
|
|
105
|
+ // Show him the first form (enter email address)
|
|
106
|
+ header('Location: login.php?act=recover');
|
|
107
|
+ }
|
|
108
|
+ } // End step 2
|
115
|
109
|
|
116
|
|
- if (!empty($_REQUEST['email'])) {
|
117
|
|
- // User has entered email and submitted form
|
118
|
|
- $Err = $Validate->ValidateForm($_REQUEST);
|
119
|
|
- if (!apcu_exists('DBKEY')) {
|
120
|
|
- $Err = 'Database not fully decrypted. Please wait for staff to fix this and try again.';
|
121
|
|
- }
|
|
110
|
+ // User has not clicked the link in his email, use step 1
|
|
111
|
+ else {
|
|
112
|
+ $Validate->SetFields('email', '1', 'email', 'You entered an invalid email address');
|
122
|
113
|
|
123
|
|
- if (!$Err) {
|
124
|
|
- // Form validates correctly
|
125
|
|
- $DB->query("
|
126
|
|
- SELECT
|
127
|
|
- Email
|
128
|
|
- FROM users_main");
|
129
|
|
- while(list($EncEmail) = $DB->next_record()) {
|
130
|
|
- if (strtolower($_REQUEST['email']) == strtolower(Crypto::decrypt($EncEmail))) {
|
131
|
|
- break; // $EncEmail is now the encrypted form of the given email from the database
|
132
|
|
- }
|
133
|
|
- }
|
|
114
|
+ if (!empty($_REQUEST['email'])) {
|
|
115
|
+ // User has entered email and submitted form
|
|
116
|
+ $Err = $Validate->ValidateForm($_REQUEST);
|
|
117
|
+ if (!apcu_exists('DBKEY')) {
|
|
118
|
+ $Err = 'Database not fully decrypted. Please wait for staff to fix this and try again';
|
|
119
|
+ }
|
134
|
120
|
|
135
|
|
- $DB->query("
|
136
|
|
- SELECT
|
137
|
|
- ID,
|
138
|
|
- Username,
|
139
|
|
- Email
|
140
|
|
- FROM users_main
|
141
|
|
- WHERE Email = ?", $EncEmail);
|
142
|
|
- list($UserID, $Username, $Email) = $DB->next_record();
|
143
|
|
- $Email = Crypto::decrypt($Email);
|
|
121
|
+ if (!$Err) {
|
|
122
|
+ // Form validates correctly
|
|
123
|
+ $DB->query("
|
|
124
|
+ SELECT
|
|
125
|
+ Email
|
|
126
|
+ FROM users_main");
|
|
127
|
+ while (list($EncEmail) = $DB->next_record()) {
|
|
128
|
+ if (strtolower($_REQUEST['email']) == strtolower(Crypto::decrypt($EncEmail))) {
|
|
129
|
+ break; // $EncEmail is now the encrypted form of the given email from the database
|
|
130
|
+ }
|
|
131
|
+ }
|
144
|
132
|
|
145
|
|
- if ($UserID) {
|
146
|
|
- // Email exists in the database
|
147
|
|
- // Set ResetKey, send out email, and set $Sent to 1 to show success page
|
148
|
|
- Users::reset_password($UserID, $Username, $Email);
|
149
|
|
-
|
150
|
|
- $Sent = 1; // If $Sent is 1, recover_step1.php displays a success message
|
151
|
|
-
|
152
|
|
- //Log out all of the users current sessions
|
153
|
|
- $Cache->delete_value("user_info_$UserID");
|
154
|
|
- $Cache->delete_value("user_info_heavy_$UserID");
|
155
|
|
- $Cache->delete_value("user_stats_$UserID");
|
156
|
|
- $Cache->delete_value("enabled_$UserID");
|
157
|
|
-
|
158
|
|
- $DB->query("
|
159
|
|
- SELECT SessionID
|
160
|
|
- FROM users_sessions
|
161
|
|
- WHERE UserID = ?", $UserID);
|
162
|
|
- while (list($SessionID) = $DB->next_record()) {
|
163
|
|
- $Cache->delete_value("session_$UserID"."_$SessionID");
|
164
|
|
- }
|
165
|
|
- $DB->query("
|
166
|
|
- UPDATE users_sessions
|
167
|
|
- SET Active = 0
|
168
|
|
- WHERE UserID = ?
|
169
|
|
- AND Active = 1", $UserID);
|
170
|
|
- } else {
|
171
|
|
- $Err = 'There is no user with that email address.';
|
|
133
|
+ $DB->query("
|
|
134
|
+ SELECT
|
|
135
|
+ ID,
|
|
136
|
+ Username,
|
|
137
|
+ Email
|
|
138
|
+ FROM users_main
|
|
139
|
+ WHERE Email = ?", $EncEmail);
|
|
140
|
+ list($UserID, $Username, $Email) = $DB->next_record();
|
|
141
|
+ $Email = Crypto::decrypt($Email);
|
|
142
|
+
|
|
143
|
+ if ($UserID) {
|
|
144
|
+ // Email exists in the database
|
|
145
|
+ // Set ResetKey, send out email, and set $Sent to 1 to show success page
|
|
146
|
+ Users::reset_password($UserID, $Username, $Email);
|
|
147
|
+
|
|
148
|
+ $Sent = 1; // If $Sent is 1, recover_step1.php displays a success message
|
|
149
|
+
|
|
150
|
+ //Log out all of the users current sessions
|
|
151
|
+ $Cache->delete_value("user_info_$UserID");
|
|
152
|
+ $Cache->delete_value("user_info_heavy_$UserID");
|
|
153
|
+ $Cache->delete_value("user_stats_$UserID");
|
|
154
|
+ $Cache->delete_value("enabled_$UserID");
|
|
155
|
+
|
|
156
|
+ $DB->query("
|
|
157
|
+ SELECT SessionID
|
|
158
|
+ FROM users_sessions
|
|
159
|
+ WHERE UserID = ?", $UserID);
|
|
160
|
+ while (list($SessionID) = $DB->next_record()) {
|
|
161
|
+ $Cache->delete_value("session_$UserID"."_$SessionID");
|
|
162
|
+ }
|
|
163
|
+ $DB->query("
|
|
164
|
+ UPDATE users_sessions
|
|
165
|
+ SET Active = 0
|
|
166
|
+ WHERE UserID = ?
|
|
167
|
+ AND Active = 1", $UserID);
|
|
168
|
+ } else {
|
|
169
|
+ $Err = 'There is no user with that email address';
|
|
170
|
+ }
|
|
171
|
+ }
|
|
172
|
+ } elseif (!empty($_SESSION['reseterr'])) {
|
|
173
|
+ // User has not entered email address, and there is an error set in session data
|
|
174
|
+ // This is typically because their key has expired.
|
|
175
|
+ // Stick the error into $Err so recover_step1.php can take care of it
|
|
176
|
+ $Err = $_SESSION['reseterr'];
|
|
177
|
+ unset($_SESSION['reseterr']);
|
172
|
178
|
}
|
173
|
|
- }
|
174
|
|
-
|
175
|
|
- } elseif (!empty($_SESSION['reseterr'])) {
|
176
|
|
- // User has not entered email address, and there is an error set in session data
|
177
|
|
- // This is typically because their key has expired.
|
178
|
|
- // Stick the error into $Err so recover_step1.php can take care of it
|
179
|
|
- $Err = $_SESSION['reseterr'];
|
180
|
|
- unset($_SESSION['reseterr']);
|
181
|
|
- }
|
182
|
|
-
|
183
|
|
- // Either a form for the user's email address, or a success message
|
184
|
|
- require('recover_step1.php');
|
185
|
|
- }
|
186
|
179
|
|
|
180
|
+ // Either a form for the user's email address, or a success message
|
|
181
|
+ require('recover_step1.php');
|
|
182
|
+ }
|
187
|
183
|
} // End password recovery
|
188
|
184
|
|
189
|
|
-else if (isset($_REQUEST['act']) && $_REQUEST['act'] == 'newlocation') {
|
190
|
|
- if (isset($_REQUEST['key'])) {
|
191
|
|
- if ($ASNCache = $Cache->get_value('new_location_'.$_REQUEST['key'])) {
|
192
|
|
- $Cache->cache_value('new_location_'.$ASNCache['UserID'].'_'.$ASNCache['ASN'], true);
|
193
|
|
- require('newlocation.php');
|
194
|
|
- die();
|
|
185
|
+elseif (isset($_REQUEST['act']) && $_REQUEST['act'] == 'newlocation') {
|
|
186
|
+ if (isset($_REQUEST['key'])) {
|
|
187
|
+ if ($ASNCache = $Cache->get_value('new_location_'.$_REQUEST['key'])) {
|
|
188
|
+ $Cache->cache_value('new_location_'.$ASNCache['UserID'].'_'.$ASNCache['ASN'], true);
|
|
189
|
+ require('newlocation.php');
|
|
190
|
+ die();
|
|
191
|
+ } else {
|
|
192
|
+ error(403);
|
|
193
|
+ }
|
195
|
194
|
} else {
|
196
|
|
- error(403);
|
|
195
|
+ error(403);
|
197
|
196
|
}
|
198
|
|
- } else {
|
199
|
|
- error(403);
|
200
|
|
- }
|
201
|
197
|
} // End new location
|
202
|
198
|
|
203
|
199
|
// Normal login
|
204
|
200
|
else {
|
205
|
|
- $Validate->SetFields('username', true, 'regex', 'You did not enter a valid username.', array('regex' => USERNAME_REGEX));
|
206
|
|
- $Validate->SetFields('password', '1', 'string', 'You entered an invalid password.', array('minlength' => '6', 'maxlength' => '307200'));
|
207
|
|
-
|
208
|
|
- list($Attempts, $Banned) = $Cache->get_value('login_attempts_'.db_string($_SERVER['REMOTE_ADDR']));
|
209
|
|
-
|
210
|
|
- // Function to log a user's login attempt
|
211
|
|
- function log_attempt() {
|
212
|
|
- global $Cache, $Attempts;
|
213
|
|
- $Attempts = ($Attempts ?? 0) + 1;
|
214
|
|
- $Cache->cache_value('login_attempts_'.db_string($_SERVER['REMOTE_ADDR']), array($Attempts, ($Attempts > 5)), 60*60*$Attempts);
|
215
|
|
- $AllAttempts = $Cache->get_value('login_attempts');
|
216
|
|
- $AllAttempts[$_SERVER['REMOTE_ADDR']] = time()+(60*60*$Attempts);
|
217
|
|
- foreach($AllAttempts as $IP => $Time) {
|
218
|
|
- if ($Time < time()) { unset($AllAttempts[$IP]); }
|
219
|
|
- }
|
220
|
|
- $Cache->cache_value('login_attempts', $AllAttempts, 0);
|
221
|
|
- }
|
222
|
|
-
|
223
|
|
- // If user has submitted form
|
224
|
|
- if (isset($_POST['username']) && !empty($_POST['username']) && isset($_POST['password']) && !empty($_POST['password'])) {
|
225
|
|
- if ($Banned) {
|
226
|
|
- header("Location: login.php");
|
227
|
|
- die();
|
|
201
|
+ $Validate->SetFields('username', true, 'regex', 'You did not enter a valid username', array('regex' => USERNAME_REGEX));
|
|
202
|
+ $Validate->SetFields('password', '1', 'string', 'You entered an invalid password', array('minlength' => '6', 'maxlength' => '307200'));
|
|
203
|
+
|
|
204
|
+ list($Attempts, $Banned) = $Cache->get_value('login_attempts_'.db_string($_SERVER['REMOTE_ADDR']));
|
|
205
|
+
|
|
206
|
+ // Function to log a user's login attempt
|
|
207
|
+ function log_attempt()
|
|
208
|
+ {
|
|
209
|
+ global $Cache, $Attempts;
|
|
210
|
+ $Attempts = ($Attempts ?? 0) + 1;
|
|
211
|
+ $Cache->cache_value('login_attempts_'.db_string($_SERVER['REMOTE_ADDR']), array($Attempts, ($Attempts > 5)), 60*60*$Attempts);
|
|
212
|
+ $AllAttempts = $Cache->get_value('login_attempts');
|
|
213
|
+ $AllAttempts[$_SERVER['REMOTE_ADDR']] = time()+(60*60*$Attempts);
|
|
214
|
+ foreach ($AllAttempts as $IP => $Time) {
|
|
215
|
+ if ($Time < time()) {
|
|
216
|
+ unset($AllAttempts[$IP]);
|
|
217
|
+ }
|
|
218
|
+ }
|
|
219
|
+ $Cache->cache_value('login_attempts', $AllAttempts, 0);
|
228
|
220
|
}
|
229
|
|
- $Err = $Validate->ValidateForm($_POST);
|
230
|
221
|
|
231
|
|
- if (!$Err) {
|
232
|
|
- // Passes preliminary validation (username and password "look right")
|
233
|
|
- $DB->query("
|
234
|
|
- SELECT
|
235
|
|
- ID,
|
236
|
|
- PermissionID,
|
237
|
|
- CustomPermissions,
|
238
|
|
- PassHash,
|
239
|
|
- TwoFactor,
|
240
|
|
- Enabled
|
241
|
|
- FROM users_main
|
242
|
|
- WHERE Username = ?
|
243
|
|
- AND Username != ''", $_POST['username']);
|
244
|
|
- list($UserID, $PermissionID, $CustomPermissions, $PassHash, $TwoFactor, $Enabled) = $DB->next_record(MYSQLI_NUM, array(2));
|
245
|
|
- if (!$Banned) {
|
246
|
|
- if ($UserID && Users::check_password($_POST['password'], $PassHash)) {
|
247
|
|
- // Update hash if better algorithm available
|
248
|
|
- if (password_needs_rehash($PassHash, PASSWORD_DEFAULT)) {
|
|
222
|
+ // If user has submitted form
|
|
223
|
+ if (isset($_POST['username']) && !empty($_POST['username']) && isset($_POST['password']) && !empty($_POST['password'])) {
|
|
224
|
+ if ($Banned) {
|
|
225
|
+ header("Location: login.php");
|
|
226
|
+ die();
|
|
227
|
+ }
|
|
228
|
+ $Err = $Validate->ValidateForm($_POST);
|
|
229
|
+
|
|
230
|
+ if (!$Err) {
|
|
231
|
+ // Passes preliminary validation (username and password "look right")
|
249
|
232
|
$DB->query("
|
250
|
|
- UPDATE users_main
|
251
|
|
- SET PassHash = ?
|
252
|
|
- WHERE Username = ?", make_sec_hash($_POST['password']), $_POST['username']);
|
253
|
|
- }
|
|
233
|
+ SELECT
|
|
234
|
+ ID,
|
|
235
|
+ PermissionID,
|
|
236
|
+ CustomPermissions,
|
|
237
|
+ PassHash,
|
|
238
|
+ TwoFactor,
|
|
239
|
+ Enabled
|
|
240
|
+ FROM users_main
|
|
241
|
+ WHERE Username = ?
|
|
242
|
+ AND Username != ''", $_POST['username']);
|
|
243
|
+ list($UserID, $PermissionID, $CustomPermissions, $PassHash, $TwoFactor, $Enabled) = $DB->next_record(MYSQLI_NUM, array(2));
|
|
244
|
+ if (!$Banned) {
|
|
245
|
+ if ($UserID && Users::check_password($_POST['password'], $PassHash)) {
|
|
246
|
+ // Update hash if better algorithm available
|
|
247
|
+ if (password_needs_rehash($PassHash, PASSWORD_DEFAULT)) {
|
|
248
|
+ $DB->query("
|
|
249
|
+ UPDATE users_main
|
|
250
|
+ SET PassHash = ?
|
|
251
|
+ WHERE Username = ?", make_sec_hash($_POST['password']), $_POST['username']);
|
|
252
|
+ }
|
254
|
253
|
|
255
|
|
- if (empty($TwoFactor) || $TwoFA->verifyCode($TwoFactor, $_POST['twofa'])) {
|
256
|
|
- # todo: Make sure the type is (int)
|
257
|
|
- if ($Enabled === '1') {
|
|
254
|
+ if (empty($TwoFactor) || $TwoFA->verifyCode($TwoFactor, $_POST['twofa'])) {
|
|
255
|
+ # todo: Make sure the type is (int)
|
|
256
|
+ if ($Enabled === '1') {
|
258
|
257
|
|
259
|
258
|
// Check if the current login attempt is from a location previously logged in from
|
260
|
|
- if (apcu_exists('DBKEY')) {
|
261
|
|
- $DB->query("
|
262
|
|
- SELECT IP
|
263
|
|
- FROM users_history_ips
|
264
|
|
- WHERE UserID = ?", $UserID);
|
265
|
|
- $IPs = $DB->to_array(false, MYSQLI_NUM);
|
266
|
|
- $QueryParts = [];
|
267
|
|
- foreach ($IPs as $i => $IP) {
|
268
|
|
- $IPs[$i] = Crypto::decrypt($IP[0]);
|
269
|
|
- }
|
270
|
|
- $IPs = array_unique($IPs);
|
271
|
|
- if (count($IPs) > 0) { // Always allow first login
|
272
|
|
- foreach ($IPs as $IP) {
|
273
|
|
- $QueryParts[] = "(StartIP<=INET6_ATON('$IP') AND EndIP>=INET6_ATON('$IP'))";
|
274
|
|
- }
|
275
|
|
- $DB->query('SELECT ASN FROM geoip_asn WHERE '.implode(' OR ', $QueryParts));
|
276
|
|
- $PastASNs = array_column($DB->to_array(false, MYSQLI_NUM), 0);
|
277
|
|
- $DB->query("SELECT ASN FROM geoip_asn WHERE StartIP<=INET6_ATON('$_SERVER[REMOTE_ADDR]') AND EndIP>=INET6_ATON('$_SERVER[REMOTE_ADDR]')");
|
278
|
|
- list($CurrentASN) = $DB->next_record();
|
279
|
|
-
|
280
|
|
- // If FEATURE_ENFORCE_LOCATIONS is enabled, require users to confirm new logins
|
281
|
|
- if (!in_array($CurrentASN, $PastASNs) && FEATURE_ENFORCE_LOCATIONS) {
|
282
|
|
- // Never logged in from this location before
|
283
|
|
- if ($Cache->get_value('new_location_'.$UserID.'_'.$CurrentASN) !== true) {
|
284
|
|
- $DB->query("
|
285
|
|
- SELECT
|
286
|
|
- UserName,
|
287
|
|
- Email
|
288
|
|
- FROM users_main
|
289
|
|
- WHERE ID = ?", $UserID);
|
290
|
|
- list($Username, $Email) = $DB->next_record();
|
291
|
|
- Users::auth_location($UserID, $Username, $CurrentASN, Crypto::decrypt($Email));
|
292
|
|
- require('newlocation.php');
|
293
|
|
- die();
|
294
|
|
- }
|
295
|
|
- }
|
296
|
|
- }
|
297
|
|
- }
|
298
|
|
-
|
299
|
|
- $U2FRegs = [];
|
300
|
|
- $DB->query("
|
301
|
|
- SELECT KeyHandle, PublicKey, Certificate, Counter, Valid
|
302
|
|
- FROM u2f
|
303
|
|
- WHERE UserID = ?", $UserID);
|
304
|
|
- // Needs to be an array of objects, so we can't use to_array()
|
305
|
|
- while (list($KeyHandle, $PublicKey, $Certificate, $Counter, $Valid) = $DB->next_record()) {
|
306
|
|
- $U2FRegs[] = (object)['keyHandle'=>$KeyHandle, 'publicKey'=>$PublicKey, 'certificate'=>$Certificate, 'counter'=>$Counter, 'valid'=>$Valid];
|
307
|
|
- }
|
308
|
|
-
|
309
|
|
- if (sizeof($U2FRegs) > 0) {
|
310
|
|
- // U2F is enabled for this account
|
311
|
|
- if (isset($_POST['u2f-request']) && isset($_POST['u2f-response'])) {
|
312
|
|
- // Data from the U2F login page is present. Verify it.
|
313
|
|
- try {
|
314
|
|
- $U2FReg = $U2F->doAuthenticate(json_decode($_POST['u2f-request']), $U2FRegs, json_decode($_POST['u2f-response']));
|
315
|
|
- if ($U2FReg->valid != '1') throw new Exception('Token disabled.');
|
316
|
|
- $DB->query("UPDATE u2f
|
|
259
|
+ if (apcu_exists('DBKEY')) {
|
|
260
|
+ $DB->query("
|
|
261
|
+ SELECT IP
|
|
262
|
+ FROM users_history_ips
|
|
263
|
+ WHERE UserID = ?", $UserID);
|
|
264
|
+ $IPs = $DB->to_array(false, MYSQLI_NUM);
|
|
265
|
+ $QueryParts = [];
|
|
266
|
+ foreach ($IPs as $i => $IP) {
|
|
267
|
+ $IPs[$i] = Crypto::decrypt($IP[0]);
|
|
268
|
+ }
|
|
269
|
+ $IPs = array_unique($IPs);
|
|
270
|
+ if (count($IPs) > 0) { // Always allow first login
|
|
271
|
+ foreach ($IPs as $IP) {
|
|
272
|
+ $QueryParts[] = "(StartIP<=INET6_ATON('$IP') AND EndIP>=INET6_ATON('$IP'))";
|
|
273
|
+ }
|
|
274
|
+ $DB->query('SELECT ASN FROM geoip_asn WHERE '.implode(' OR ', $QueryParts));
|
|
275
|
+ $PastASNs = array_column($DB->to_array(false, MYSQLI_NUM), 0);
|
|
276
|
+ $DB->query("SELECT ASN FROM geoip_asn WHERE StartIP<=INET6_ATON('$_SERVER[REMOTE_ADDR]') AND EndIP>=INET6_ATON('$_SERVER[REMOTE_ADDR]')");
|
|
277
|
+ list($CurrentASN) = $DB->next_record();
|
|
278
|
+
|
|
279
|
+ // If FEATURE_ENFORCE_LOCATIONS is enabled, require users to confirm new logins
|
|
280
|
+ if (!in_array($CurrentASN, $PastASNs) && FEATURE_ENFORCE_LOCATIONS) {
|
|
281
|
+ // Never logged in from this location before
|
|
282
|
+ if ($Cache->get_value('new_location_'.$UserID.'_'.$CurrentASN) !== true) {
|
|
283
|
+ $DB->query("
|
|
284
|
+ SELECT
|
|
285
|
+ UserName,
|
|
286
|
+ Email
|
|
287
|
+ FROM users_main
|
|
288
|
+ WHERE ID = ?", $UserID);
|
|
289
|
+ list($Username, $Email) = $DB->next_record();
|
|
290
|
+ Users::auth_location($UserID, $Username, $CurrentASN, Crypto::decrypt($Email));
|
|
291
|
+ require('newlocation.php');
|
|
292
|
+ die();
|
|
293
|
+ }
|
|
294
|
+ }
|
|
295
|
+ }
|
|
296
|
+ }
|
|
297
|
+
|
|
298
|
+ $U2FRegs = [];
|
|
299
|
+ $DB->query("
|
|
300
|
+ SELECT KeyHandle, PublicKey, Certificate, Counter, Valid
|
|
301
|
+ FROM u2f
|
|
302
|
+ WHERE UserID = ?", $UserID);
|
|
303
|
+ // Needs to be an array of objects, so we can't use to_array()
|
|
304
|
+ while (list($KeyHandle, $PublicKey, $Certificate, $Counter, $Valid) = $DB->next_record()) {
|
|
305
|
+ $U2FRegs[] = (object)['keyHandle'=>$KeyHandle, 'publicKey'=>$PublicKey, 'certificate'=>$Certificate, 'counter'=>$Counter, 'valid'=>$Valid];
|
|
306
|
+ }
|
|
307
|
+
|
|
308
|
+ if (sizeof($U2FRegs) > 0) {
|
|
309
|
+ // U2F is enabled for this account
|
|
310
|
+ if (isset($_POST['u2f-request']) && isset($_POST['u2f-response'])) {
|
|
311
|
+ // Data from the U2F login page is present. Verify it.
|
|
312
|
+ try {
|
|
313
|
+ $U2FReg = $U2F->doAuthenticate(json_decode($_POST['u2f-request']), $U2FRegs, json_decode($_POST['u2f-response']));
|
|
314
|
+ if ($U2FReg->valid != '1') {
|
|
315
|
+ throw new Exception('Token disabled.');
|
|
316
|
+ }
|
|
317
|
+ $DB->query(
|
|
318
|
+ "UPDATE u2f
|
317
|
319
|
SET Counter = ?
|
318
|
320
|
WHERE KeyHandle = ?
|
319
|
321
|
AND UserID = ?",
|
320
|
|
- $U2FReg->counter, $U2FReg->keyHandle, $UserID);
|
321
|
|
- } catch (Exception $e) {
|
322
|
|
- $U2FErr = 'U2F key invalid. Error: '.($e->getMessage());
|
323
|
|
- if ($e->getMessage() == 'Token disabled.') {
|
324
|
|
- $U2FErr = 'This token was disabled due to suspected cloning. Contact staff for assistance';
|
325
|
|
- }
|
326
|
|
- if ($e->getMessage() == 'Counter too low.') {
|
327
|
|
- $BadHandle = json_decode($_POST['u2f-response'], true)['keyHandle'];
|
328
|
|
- $DB->query("UPDATE u2f
|
|
322
|
+ $U2FReg->counter,
|
|
323
|
+ $U2FReg->keyHandle,
|
|
324
|
+ $UserID
|
|
325
|
+ );
|
|
326
|
+ } catch (Exception $e) {
|
|
327
|
+ $U2FErr = 'U2F key invalid. Error: '.($e->getMessage());
|
|
328
|
+ if ($e->getMessage() == 'Token disabled.') {
|
|
329
|
+ $U2FErr = 'This token was disabled due to suspected cloning. Contact staff for assistance';
|
|
330
|
+ }
|
|
331
|
+ if ($e->getMessage() == 'Counter too low.') {
|
|
332
|
+ $BadHandle = json_decode($_POST['u2f-response'], true)['keyHandle'];
|
|
333
|
+ $DB->query("UPDATE u2f
|
329
|
334
|
SET Valid = '0'
|
330
|
335
|
WHERE KeyHandle = ?
|
331
|
336
|
AND UserID = ?", $BadHandle, $UserID);
|
332
|
|
- $U2FErr = 'U2F counter too low. This token has been disabled due to suspected cloning. Contact staff for assistance.';
|
333
|
|
- }
|
334
|
|
- }
|
335
|
|
- } else {
|
336
|
|
- // Data from the U2F login page is not present. Go there
|
337
|
|
- require('u2f.php');
|
338
|
|
- die();
|
339
|
|
- }
|
340
|
|
- }
|
341
|
|
-
|
342
|
|
- if (sizeof($U2FRegs) == 0 || !isset($U2FErr)) {
|
343
|
|
- $SessionID = Users::make_secret(64);
|
344
|
|
- setcookie('session', $SessionID, (time()+60*60*24*365), '/', '', true, true);
|
345
|
|
- setcookie('userid', $UserID, (time()+60*60*24*365), '/', '', true, true);
|
346
|
|
-
|
347
|
|
- // Because we <3 our staff
|
348
|
|
- $Permissions = Permissions::get_permissions($PermissionID);
|
349
|
|
- $CustomPermissions = unserialize($CustomPermissions);
|
350
|
|
- if (isset($Permissions['Permissions']['site_disable_ip_history'])
|
351
|
|
- || isset($CustomPermissions['site_disable_ip_history'])
|
|
337
|
+ $U2FErr = 'U2F counter too low. This token has been disabled due to suspected cloning. Contact staff for assistance';
|
|
338
|
+ }
|
|
339
|
+ }
|
|
340
|
+ } else {
|
|
341
|
+ // Data from the U2F login page is not present. Go there
|
|
342
|
+ require('u2f.php');
|
|
343
|
+ die();
|
|
344
|
+ }
|
|
345
|
+ }
|
|
346
|
+
|
|
347
|
+ if (sizeof($U2FRegs) == 0 || !isset($U2FErr)) {
|
|
348
|
+ $SessionID = Users::make_secret(64);
|
|
349
|
+ setcookie('session', $SessionID, (time()+60*60*24*365), '/', '', true, true);
|
|
350
|
+ setcookie('userid', $UserID, (time()+60*60*24*365), '/', '', true, true);
|
|
351
|
+
|
|
352
|
+ // Because we <3 our staff
|
|
353
|
+ $Permissions = Permissions::get_permissions($PermissionID);
|
|
354
|
+ $CustomPermissions = unserialize($CustomPermissions);
|
|
355
|
+ if (isset($Permissions['Permissions']['site_disable_ip_history'])
|
|
356
|
+ || isset($CustomPermissions['site_disable_ip_history'])
|
352
|
357
|
) {
|
353
|
|
- $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
354
|
|
- }
|
355
|
|
-
|
356
|
|
- $DB->query("
|
357
|
|
- INSERT INTO users_sessions
|
358
|
|
- (UserID, SessionID, KeepLogged, Browser, OperatingSystem, IP, LastUpdate, FullUA)
|
359
|
|
- VALUES
|
360
|
|
- ('$UserID', '".db_string($SessionID)."', '1', '$Browser', '$OperatingSystem', '".db_string(apcu_exists('DBKEY')?Crypto::encrypt($_SERVER['REMOTE_ADDR']):'0.0.0.0')."', NOW(), '".db_string($_SERVER['HTTP_USER_AGENT'])."')");
|
361
|
|
-
|
362
|
|
- $Cache->begin_transaction("users_sessions_$UserID");
|
363
|
|
- $Cache->insert_front($SessionID, [
|
364
|
|
- 'SessionID' => $SessionID,
|
365
|
|
- 'Browser' => $Browser,
|
366
|
|
- 'OperatingSystem' => $OperatingSystem,
|
367
|
|
- 'IP' => (apcu_exists('DBKEY')?Crypto::encrypt($_SERVER['REMOTE_ADDR']):'0.0.0.0'),
|
368
|
|
- 'LastUpdate' => sqltime()
|
369
|
|
- ]);
|
370
|
|
- $Cache->commit_transaction(0);
|
371
|
|
-
|
372
|
|
- $Sql = "
|
373
|
|
- UPDATE users_main
|
374
|
|
- SET
|
375
|
|
- LastLogin = NOW(),
|
376
|
|
- LastAccess = NOW()
|
377
|
|
- WHERE ID = '".db_string($UserID)."'";
|
378
|
|
-
|
379
|
|
- $DB->query($Sql);
|
380
|
|
-
|
381
|
|
- if (!empty($_COOKIE['redirect'])) {
|
382
|
|
- $URL = $_COOKIE['redirect'];
|
383
|
|
- setcookie('redirect', '', time() - 60 * 60 * 24, '/', '', false);
|
384
|
|
- header("Location: $URL");
|
385
|
|
- die();
|
|
358
|
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
|
359
|
+ }
|
|
360
|
+
|
|
361
|
+ $DB->query("
|
|
362
|
+ INSERT INTO users_sessions
|
|
363
|
+ (UserID, SessionID, KeepLogged, Browser, OperatingSystem, IP, LastUpdate, FullUA)
|
|
364
|
+ VALUES
|
|
365
|
+ ('$UserID', '".db_string($SessionID)."', '1', '$Browser', '$OperatingSystem', '".db_string(apcu_exists('DBKEY')?Crypto::encrypt($_SERVER['REMOTE_ADDR']):'0.0.0.0')."', NOW(), '".db_string($_SERVER['HTTP_USER_AGENT'])."')");
|
|
366
|
+
|
|
367
|
+ $Cache->begin_transaction("users_sessions_$UserID");
|
|
368
|
+ $Cache->insert_front($SessionID, [
|
|
369
|
+ 'SessionID' => $SessionID,
|
|
370
|
+ 'Browser' => $Browser,
|
|
371
|
+ 'OperatingSystem' => $OperatingSystem,
|
|
372
|
+ 'IP' => (apcu_exists('DBKEY')?Crypto::encrypt($_SERVER['REMOTE_ADDR']):'0.0.0.0'),
|
|
373
|
+ 'LastUpdate' => sqltime()
|
|
374
|
+ ]);
|
|
375
|
+ $Cache->commit_transaction(0);
|
|
376
|
+
|
|
377
|
+ $Sql = "
|
|
378
|
+ UPDATE users_main
|
|
379
|
+ SET
|
|
380
|
+ LastLogin = NOW(),
|
|
381
|
+ LastAccess = NOW()
|
|
382
|
+ WHERE ID = '".db_string($UserID)."'";
|
|
383
|
+
|
|
384
|
+ $DB->query($Sql);
|
|
385
|
+
|
|
386
|
+ if (!empty($_COOKIE['redirect'])) {
|
|
387
|
+ $URL = $_COOKIE['redirect'];
|
|
388
|
+ setcookie('redirect', '', time() - 60 * 60 * 24, '/', '', false);
|
|
389
|
+ header("Location: $URL");
|
|
390
|
+ die();
|
|
391
|
+ } else {
|
|
392
|
+ header('Location: index.php');
|
|
393
|
+ die();
|
|
394
|
+ }
|
|
395
|
+ } else {
|
|
396
|
+ log_attempt();
|
|
397
|
+ $Err = $U2FErr;
|
|
398
|
+ setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
|
399
|
+ }
|
|
400
|
+ } else {
|
|
401
|
+ log_attempt();
|
|
402
|
+ if ($Enabled == 2) {
|
|
403
|
+
|
|
404
|
+ // Save the username in a cookie for the disabled page
|
|
405
|
+ setcookie('username', db_string($_POST['username']), time() + 60 * 60, '/', '', false);
|
|
406
|
+ header('Location: login.php?action=disabled');
|
|
407
|
+ # todo: Make sure the type is (int)
|
|
408
|
+ } elseif ($Enabled === '0') {
|
|
409
|
+ $Err = 'Your account has not been confirmed. Please check your email, including the spam folder';
|
|
410
|
+ }
|
|
411
|
+ setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
|
412
|
+ }
|
|
413
|
+ } else {
|
|
414
|
+ log_attempt();
|
|
415
|
+ $Err = 'Two-factor authentication failed';
|
|
416
|
+ setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
|
417
|
+ }
|
386
|
418
|
} else {
|
387
|
|
- header('Location: index.php');
|
388
|
|
- die();
|
|
419
|
+ log_attempt();
|
|
420
|
+ $Err = 'Your username or password was incorrect';
|
|
421
|
+ setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
389
|
422
|
}
|
390
|
|
- } else {
|
|
423
|
+ } else {
|
391
|
424
|
log_attempt();
|
392
|
|
- $Err = $U2FErr;
|
393
|
425
|
setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
394
|
|
- }
|
395
|
|
- } else {
|
396
|
|
- log_attempt();
|
397
|
|
- if ($Enabled == 2) {
|
398
|
|
-
|
399
|
|
- // Save the username in a cookie for the disabled page
|
400
|
|
- setcookie('username', db_string($_POST['username']), time() + 60 * 60, '/', '', false);
|
401
|
|
- header('Location: login.php?action=disabled');
|
402
|
|
- # todo: Make sure the type is (int)
|
403
|
|
- } elseif ($Enabled === '0') {
|
404
|
|
- $Err = 'Your account has not been confirmed.<br />Please check your email.';
|
405
|
|
- }
|
406
|
|
- setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
407
|
426
|
}
|
408
|
|
- } else {
|
|
427
|
+ } else {
|
409
|
428
|
log_attempt();
|
410
|
|
- $Err = 'Two-factor authentication failed.';
|
411
|
429
|
setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
412
|
|
- }
|
413
|
|
- } else {
|
414
|
|
- log_attempt();
|
415
|
|
- $Err = 'Your username or password was incorrect.';
|
416
|
|
- setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
417
|
430
|
}
|
418
|
|
-
|
419
|
|
- } else {
|
420
|
|
- log_attempt();
|
421
|
|
- setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
422
|
|
- }
|
423
|
|
-
|
424
|
|
- } else {
|
425
|
|
- log_attempt();
|
426
|
|
- setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
|
427
|
431
|
}
|
428
|
|
- }
|
429
|
|
- require('sections/login/login.php');
|
|
432
|
+ require('sections/login/login.php');
|
430
|
433
|
}
|