diff --git a/include/lucidAuth.functions.php b/include/lucidAuth.functions.php index df2aab2..bf022e8 100644 --- a/include/lucidAuth.functions.php +++ b/include/lucidAuth.functions.php @@ -35,13 +35,36 @@ function authenticateLDAP (string $username, string $password) { if (@ldap_bind($ds, $qualifiedUsername, utf8_encode($_POST['password']))) { // Successful authentication; get additional userdetails from authenticationsource $ldapSearchResults = ldap_search($ds, $settings->LDAP['BaseDN'], "sAMAccountName=$sanitizedUsername"); - $commonName = ldap_get_entries($ds, $ldapSearchResults)[0]['cn'][0]; - // Create JWT-payload + $commonName = ldap_get_entries($ds, $ldapSearchResults)[0]['cn'][0]; + + $browserDetails = get_browser(null, True); + $geoLocation = json_decode(file_get_contents("http://ip-api.com/json/{$_SERVER['HTTP_X_REAL_IP']}")); + if ($geoLocation->status === 'fail') { + switch ($geoLocation->message) { + case 'private range': + case 'reserved range': + $geoLocation = json_decode(file_get_contents("http://ip-api.com/json/" . trim(file_get_contents('https://api.ipify.org')) )); + break; + case 'invalid query': + default: + $geoLocation->city = null; + $geoLocation->countryCode = null; + break; + } + } + + // Create JWT-payload $jwtPayload = [ - 'iat' => time(), // Issued at: time when the token was generated - 'iss' => $_SERVER['SERVER_NAME'], // Issuer - 'sub' => $qualifiedUsername, // Subject (ie. username) - 'name' => $commonName // Common name (as retrieved from AD) + 'iat' => time(), // Issued at: time when the token was generated + 'iss' => $_SERVER['SERVER_NAME'], // Issuer + 'sub' => $qualifiedUsername, // Subject (ie. username) + 'name' => $commonName, // Common name (as retrieved from AD) + 'fp' => base64_encode(json_encode((object) [ // Fingerprint + 'browser' => $browserDetails['browser'], + 'platform' => $browserDetails['platform'], + 'city' => $geoLocation->city, + 'countrycode' => $geoLocation->countryCode + ])) ]; $secureToken = JWT::encode($jwtPayload, base64_decode($settings->JWT['PrivateKey_base64'])); @@ -121,8 +144,8 @@ function validateToken (string $secureToken) { WHERE LOWER(User.Username) = :username '); $pdoQuery->execute([ - ':username' => (string) strtolower($jwtPayload->sub) - ]); + ':username' => (string) strtolower($jwtPayload->sub) + ]); foreach($pdoQuery->fetchAll(PDO::FETCH_ASSOC) as $row) { try { $storedTokens[] = JWT::decode($row['Value'], base64_decode($settings->JWT['PrivateKey_base64']), $settings->JWT['Algorithm']); @@ -136,7 +159,9 @@ function validateToken (string $secureToken) { if (!empty($storedTokens) && sizeof(array_filter($storedTokens, function ($value) use ($jwtPayload) { return $value->iat === $jwtPayload->iat; })) === 1) { - return [ + purgeTokens($currentUserId, $settings->Session['Duration']); + + return [ 'status' => 'Success', 'name' => $jwtPayload->name, 'uid' => $currentUserId @@ -149,4 +174,85 @@ function validateToken (string $secureToken) { } } +function purgeTokens(int $userID, int $maximumTokenAge) { + global $settings, $pdoDB; + + $defunctTokens = []; $expiredTokens = []; + + $pdoQuery = $pdoDB->prepare(' + SELECT SecureToken.Id, SecureToken.Value + FROM SecureToken + WHERE SecureToken.UserId = :userid + '); + $pdoQuery->execute([ + ':userid' => (int) $userID + ]); + foreach($pdoQuery->fetchAll(PDO::FETCH_ASSOC) as $row) { + try { + $token = JWT::decode($row['Value'], base64_decode($settings->JWT['PrivateKey_base64']), $settings->JWT['Algorithm']); + if ($token->iat < (time() - $maximumTokenAge)) { + $expiredTokens[] = $row['Id']; + } + } catch (Exception $e) { + $defunctTokens[] = $row['Id']; + } + } + + try { + // Sadly, PDO does not support named parameters in constructions like 'IN ( :array )' + // instead, the supported syntax is unnamed placeholders like 'IN (?, ?, ?, ...)' + $pdoQuery = $pdoDB->prepare(' + DELETE FROM SecureToken + WHERE SecureToken.Id IN (' . implode( ',', array_fill(0, count(array_merge($defunctTokens, $expiredTokens)), '?')) . ') + '); + $pdoQuery->execute(array_merge($defunctTokens, $expiredTokens)); + + if ($settings->Debug['LogToFile']) { + file_put_contents('../purgeToken.log', (new DateTime())->format('Y-m-d\TH:i:s.u') . ' --- Garbage collection succeeded (' . $userID . ' => #' . $pdoQuery->rowCount() . ')' . PHP_EOL, FILE_APPEND); + } + + return [ + 'status' => 'Success', + 'amount' => $pdoQuery->rowCount() + ]; + } catch (Exception $e) { + if ($settings->Debug['LogToFile']) { + file_put_contents('../purgeToken.log', (new DateTime())->format('Y-m-d\TH:i:s.u') . ' --- Garbage collection failed (' . $userID . ' => ' . $e . ')' . PHP_EOL, FILE_APPEND); + } + + return ['status' => 'Fail', 'reason' => $e]; + } +} + +function deleteToken(array $tokenIDs, int $userID) { + try { + // Sadly, PDO does not support named parameters in constructions like 'IN ( :array )' + // instead, the supported syntax is unnamed placeholders like 'IN (?, ?, ?, ...)' + $pdoQuery = $pdoDB->prepare(' + DELETE FROM SecureToken + WHERE SecureToken.Id IN (' . implode( ',', array_fill(0, count($tokenIDs), '?')) . ') + AND SecureToken.UserId = :userid + '); + $pdoQuery->execute($tokenIDs,[ + ':userid' => (int) $userID + ]); + + if ($settings->Debug['LogToFile']) { + file_put_contents('../deleteToken.log', (new DateTime())->format('Y-m-d\TH:i:s.u') . ' --- Successfully deleted specific token(s) (' . $userID . ' => #' . $pdoQuery->rowCount() . ')' . PHP_EOL, FILE_APPEND); + } + + return [ + 'status' => 'Success', + 'amount' => $pdoQuery->rowCount() + ]; + } catch (Exception $e) { + if ($settings->Debug['LogToFile']) { + file_put_contents('../deleteToken.log', (new DateTime())->format('Y-m-d\TH:i:s.u') . ' --- Failed deleting specific token(s) (' . $userID . ' => ' . $e . ')' . PHP_EOL, FILE_APPEND); + } + + return ['status' => 'Fail', 'reason' => $e]; + } + +} + ?> \ No newline at end of file diff --git a/include/lucidAuth.template.php b/include/lucidAuth.template.php index ebc8c29..197c737 100644 --- a/include/lucidAuth.template.php +++ b/include/lucidAuth.template.php @@ -130,6 +130,22 @@ $contentLayout['manage']['section'] = <<<'MANAGE_SECTION' %1$s +
Timestamp | +<origin> | +Description | +Manage | +
---|