Browse Source

Require authorization for logins from new locations

spaghetti 2 years ago
parent
commit
102c54e720
4 changed files with 112 additions and 0 deletions
  1. 23
    0
      classes/users.class.php
  2. 54
    0
      sections/login/index.php
  3. 25
    0
      sections/login/newlocation.php
  4. 10
    0
      templates/new_location.tpl

+ 23
- 0
classes/users.class.php View File

@@ -670,4 +670,27 @@ class Users {
670 670
 
671 671
     Misc::send_email($Email, 'Password reset information for ' . SITE_NAME, $TPL->get(), 'noreply');
672 672
   }
673
+
674
+
675
+  /*
676
+   * Authorize a new location
677
+   *
678
+   * @param int $UserID The user ID
679
+   * @param string $Username The username
680
+   * @param string $Email The email address
681
+   */
682
+  public static function authLocation($UserID, $Username, $ASN, $Email) {
683
+    $AuthKey = Users::make_secret();
684
+    G::$Cache->cache_value('new_location_'.$AuthKey, array('UserID'=>$UserID, 'ASN'=>$ASN), 3600*2);
685
+    require(SERVER_ROOT . '/classes/templates.class.php');
686
+    $TPL = NEW TEMPLATE;
687
+    $TPL->open(SERVER_ROOT . '/templates/new_location.tpl');
688
+    $TPL->set('Username', $Username);
689
+    $TPL->set('AuthKey', $AuthKey);
690
+    $TPL->set('IP', $_SERVER['REMOTE_ADDR']);
691
+    $TPL->set('SITE_NAME', SITE_NAME);
692
+    $TPL->set('SITE_DOMAIN', SITE_DOMAIN);
693
+
694
+    Misc::send_email($Email, 'Login from new location for '.SITE_NAME, $TPL->get(), 'noreply');
695
+  }
673 696
 }

+ 54
- 0
sections/login/index.php View File

@@ -184,6 +184,20 @@ if (isset($_REQUEST['act']) && $_REQUEST['act'] == 'recover') {
184 184
 
185 185
 } // End password recovery
186 186
 
187
+else if (isset($_REQUEST['act']) && $_REQUEST['act'] == 'newlocation') {
188
+  if (isset($_REQUEST['key'])) {
189
+    if ($ASNCache = $Cache->get_value('new_location_'.$_REQUEST['key'])) {
190
+      $Cache->cache_value('new_location_'.$ASNCache['UserID'].'_'.$ASNCache['ASN'], true);
191
+      require('newlocation.php');
192
+      die();
193
+    } else {
194
+      error(403);
195
+    }
196
+  } else {
197
+    error(403);
198
+  }
199
+} // End new location
200
+
187 201
 // Normal login
188 202
 else {
189 203
   $Validate->SetFields('username', true, 'regex', 'You did not enter a valid username.', array('regex' => USERNAME_REGEX));
@@ -235,6 +249,46 @@ else {
235 249
               WHERE Username = '".db_string($_POST['username'])."'");
236 250
           }
237 251
           if ($Enabled == 1) {
252
+
253
+            // Check if the current login attempt is from a location previously logged in from
254
+            if (apc_exists('DBKEY')) {
255
+              $DB->query("
256
+                SELECT IP
257
+                FROM users_history_ips
258
+                WHERE UserID = $UserID");
259
+              $IPs = $DB->to_array(false, MYSQLI_NUM);
260
+              $QueryParts = array();
261
+              foreach ($IPs as $i => $IP) {
262
+                $IPs[$i] = DBCrypt::decrypt($IP[0]);
263
+              }
264
+              $IPs = array_unique($IPs);
265
+              if (count($IPs) > 0) { // Always allow first login
266
+                foreach ($IPs as $IP) {
267
+                  $QueryParts[] = "(StartIP<=INET6_ATON('$IP') AND EndIP>=INET6_ATON('$IP'))";
268
+                }
269
+                $DB->query('SELECT ASN FROM geoip_asn WHERE '.implode(' OR ', $QueryParts));
270
+                $PastASNs = array_column($DB->to_array(false, MYSQLI_NUM), 0);
271
+                $DB->query("SELECT ASN FROM geoip_asn WHERE StartIP<=INET_ATON('$_SERVER[REMOTE_ADDR]') AND EndIP>=INET_ATON('$_SERVER[REMOTE_ADDR]')");
272
+                list($CurrentASN) = $DB->next_record();
273
+
274
+                if (!in_array($CurrentASN, $PastASNs)) {
275
+                  // Never logged in from this location before
276
+                  if ($Cache->get_value('new_location_'.$UserID.'_'.$CurrentASN) !== true) {
277
+                    $DB->query("
278
+                      SELECT
279
+                        UserName,
280
+                        Email
281
+                      FROM users_main
282
+                      WHERE ID = $UserID");
283
+                    list($Username, $Email) = $DB->next_record();
284
+                    Users::authLocation($UserID, $Username, $CurrentASN, DBCrypt::decrypt($Email));
285
+                    require('newlocation.php');
286
+                    die();
287
+                  }
288
+                }
289
+              }
290
+            }
291
+
238 292
             $SessionID = Users::make_secret(64);
239 293
             $KeepLogged = ($_POST['keeplogged'] ?? false) ? 1 : 0;
240 294
             setcookie('session', $SessionID, (time()+60*60*24*365)*$KeepLogged, '/', '', true, true);

+ 25
- 0
sections/login/newlocation.php View File

@@ -0,0 +1,25 @@
1
+<?
2
+if (!empty($LoggedUser['ID'])) {
3
+  header('Location: login.php');
4
+  die();
5
+}
6
+
7
+View::show_header('Authorize Location');
8
+
9
+if (isset($_REQUEST['act'])) {
10
+?>
11
+
12
+Your location is now authorized to access this account.<br /><br />
13
+Click <a href="login.php">here</a> to login again.
14
+
15
+<? } else { ?>
16
+
17
+This appears to be the first time you've logged in from this location.<br /><br />
18
+
19
+As a security measure to ensure that you are really the owner of this account,<br />
20
+an email has been sent to the address in your profile settings. Please<br />
21
+click the link contained in that email to allow access from<br />
22
+your location, and then log in again.
23
+
24
+<? }
25
+View::show_footer(); ?>

+ 10
- 0
templates/new_location.tpl View File

@@ -0,0 +1,10 @@
1
+A login from a new location was detected for the user {{Username}} from the IP address {{IP}}.
2
+
3
+To allow logins from this location, click the link below (you have 2 hours)
4
+
5
+https://{{SITE_DOMAIN}}/login.php?act=newlocation&key={{AuthKey}}
6
+
7
+If this login attempt was not you, change your password and contact staff immediately.
8
+
9
+Thank you,
10
+{{SITE_NAME}} Staff

Loading…
Cancel
Save