From f025ee13d08e290f73dd974e06a5b6cbe45279e8 Mon Sep 17 00:00:00 2001 From: flashwave Date: Tue, 11 Jul 2023 00:25:43 +0000 Subject: [PATCH] Rely on NGINX GeoIP2 module for country code lookup. --- composer.json | 1 - composer.lock | 351 +++-------------------- misuzu.php | 3 - public/auth/login.php | 11 +- public/auth/password.php | 2 +- public/auth/register.php | 4 +- public/auth/twofactor.php | 7 +- public/comments.php | 6 +- public/forum/post.php | 6 +- public/forum/topic.php | 12 +- public/manage/changelog/change.php | 1 - public/manage/changelog/tag.php | 1 - public/manage/forum/redirs.php | 4 +- public/manage/general/setting-delete.php | 2 +- public/manage/general/setting.php | 2 +- public/manage/news/category.php | 1 - public/manage/news/post.php | 1 - public/settings/account.php | 4 +- public/settings/data.php | 2 +- public/settings/sessions.php | 4 +- src/AuditLog.php | 10 +- src/GeoIP/GeoIPHelper.php | 76 ----- src/MisuzuContext.php | 8 - src/Net/IPAddress.php | 19 -- src/Users/User.php | 6 +- src/Users/UserLoginAttempt.php | 12 +- src/Users/UserRecoveryToken.php | 1 - src/Users/UserSession.php | 5 +- 28 files changed, 101 insertions(+), 461 deletions(-) delete mode 100644 src/GeoIP/GeoIPHelper.php delete mode 100644 src/Net/IPAddress.php diff --git a/composer.json b/composer.json index 76ac35a..9ed0d78 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,6 @@ "require": { "twig/twig": "^3.0", "erusev/parsedown": "~1.6", - "geoip2/geoip2": "~2.0", "chillerlan/php-qrcode": "^4.3", "whichbrowser/parser": "^2.0", "symfony/mailer": "^6.0" diff --git a/composer.lock b/composer.lock index 9032fc4..1b1d51f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b3b6cf189969ecb1dd838c23a4fe96e1", + "content-hash": "26152622b2776a8d945647d5c1cfc48c", "packages": [ { "name": "chillerlan/php-qrcode", @@ -148,82 +148,6 @@ ], "time": "2022-07-05T22:32:14+00:00" }, - { - "name": "composer/ca-bundle", - "version": "1.3.5", - "source": { - "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "74780ccf8c19d6acb8d65c5f39cd72110e132bbd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/74780ccf8c19d6acb8d65c5f39cd72110e132bbd", - "reference": "74780ccf8c19d6acb8d65c5f39cd72110e132bbd", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\CaBundle\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-01-11T08:27:00+00:00" - }, { "name": "doctrine/lexer", "version": "3.0.0", @@ -418,176 +342,6 @@ }, "time": "2019-12-30T22:54:17+00:00" }, - { - "name": "geoip2/geoip2", - "version": "v2.13.0", - "source": { - "type": "git", - "url": "git@github.com:maxmind/GeoIP2-php.git", - "reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/6a41d8fbd6b90052bc34dff3b4252d0f88067b23", - "reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23", - "shasum": "" - }, - "require": { - "ext-json": "*", - "maxmind-db/reader": "~1.8", - "maxmind/web-service-common": "~0.8", - "php": ">=7.2" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "3.*", - "phpstan/phpstan": "*", - "phpunit/phpunit": "^8.0 || ^9.0", - "squizlabs/php_codesniffer": "3.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "GeoIp2\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Gregory J. Oschwald", - "email": "goschwald@maxmind.com", - "homepage": "https://www.maxmind.com/" - } - ], - "description": "MaxMind GeoIP2 PHP API", - "homepage": "https://github.com/maxmind/GeoIP2-php", - "keywords": [ - "IP", - "geoip", - "geoip2", - "geolocation", - "maxmind" - ], - "time": "2022-08-05T20:32:58+00:00" - }, - { - "name": "maxmind-db/reader", - "version": "v1.11.0", - "source": { - "type": "git", - "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", - "reference": "b1f3c0699525336d09cc5161a2861268d9f2ae5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/b1f3c0699525336d09cc5161a2861268d9f2ae5b", - "reference": "b1f3c0699525336d09cc5161a2861268d9f2ae5b", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "conflict": { - "ext-maxminddb": "<1.10.1,>=2.0.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "3.*", - "php-coveralls/php-coveralls": "^2.1", - "phpstan/phpstan": "*", - "phpunit/phpcov": ">=6.0.0", - "phpunit/phpunit": ">=8.0.0,<10.0.0", - "squizlabs/php_codesniffer": "3.*" - }, - "suggest": { - "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", - "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", - "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" - }, - "type": "library", - "autoload": { - "psr-4": { - "MaxMind\\Db\\": "src/MaxMind/Db" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Gregory J. Oschwald", - "email": "goschwald@maxmind.com", - "homepage": "https://www.maxmind.com/" - } - ], - "description": "MaxMind DB Reader API", - "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php", - "keywords": [ - "database", - "geoip", - "geoip2", - "geolocation", - "maxmind" - ], - "support": { - "issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues", - "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.11.0" - }, - "time": "2021-10-18T15:23:10+00:00" - }, - { - "name": "maxmind/web-service-common", - "version": "v0.9.0", - "source": { - "type": "git", - "url": "https://github.com/maxmind/web-service-common-php.git", - "reference": "4dc5a3e8df38aea4ca3b1096cee3a038094e9b53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/4dc5a3e8df38aea4ca3b1096cee3a038094e9b53", - "reference": "4dc5a3e8df38aea4ca3b1096cee3a038094e9b53", - "shasum": "" - }, - "require": { - "composer/ca-bundle": "^1.0.3", - "ext-curl": "*", - "ext-json": "*", - "php": ">=7.2" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "3.*", - "phpstan/phpstan": "*", - "phpunit/phpunit": "^8.0 || ^9.0", - "squizlabs/php_codesniffer": "3.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "MaxMind\\Exception\\": "src/Exception", - "MaxMind\\WebService\\": "src/WebService" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Gregory Oschwald", - "email": "goschwald@maxmind.com" - } - ], - "description": "Internal MaxMind Web Service API", - "homepage": "https://github.com/maxmind/web-service-common-php", - "support": { - "issues": "https://github.com/maxmind/web-service-common-php/issues", - "source": "https://github.com/maxmind/web-service-common-php/tree/v0.9.0" - }, - "time": "2022-03-28T17:43:20+00:00" - }, { "name": "psr/cache", "version": "3.0.0", @@ -792,24 +546,25 @@ }, { "name": "symfony/event-dispatcher", - "version": "v6.2.7", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "404b307de426c1c488e5afad64403e5f145e82a5" + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/404b307de426c1c488e5afad64403e5f145e82a5", - "reference": "404b307de426c1c488e5afad64403e5f145e82a5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2|^3" + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", @@ -822,13 +577,9 @@ "symfony/error-handler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^5.4|^6.0" }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, "type": "library", "autoload": { "psr-4": { @@ -855,7 +606,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.7" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0" }, "funding": [ { @@ -871,33 +622,30 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:44:56+00:00" + "time": "2023-04-21T14:41:17+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd" + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", - "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", "shasum": "" }, "require": { "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -934,7 +682,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" }, "funding": [ { @@ -950,20 +698,20 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/mailer", - "version": "v6.2.7", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "e4f84c633b72ec70efc50b8016871c3bc43e691e" + "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/e4f84c633b72ec70efc50b8016871c3bc43e691e", - "reference": "e4f84c633b72ec70efc50b8016871c3bc43e691e", + "url": "https://api.github.com/repos/symfony/mailer/zipball/7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435", "shasum": "" }, "require": { @@ -973,9 +721,10 @@ "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/mime": "^6.2", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "conflict": { + "symfony/http-client-contracts": "<2.5", "symfony/http-kernel": "<5.4", "symfony/messenger": "<6.2", "symfony/mime": "<6.2", @@ -1013,7 +762,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.2.7" + "source": "https://github.com/symfony/mailer/tree/v6.3.0" }, "funding": [ { @@ -1029,20 +778,20 @@ "type": "tidelift" } ], - "time": "2023-02-21T10:35:38+00:00" + "time": "2023-05-29T12:49:39+00:00" }, { "name": "symfony/mime", - "version": "v6.2.7", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "62e341f80699badb0ad70b31149c8df89a2d778e" + "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/62e341f80699badb0ad70b31149c8df89a2d778e", - "reference": "62e341f80699badb0ad70b31149c8df89a2d778e", + "url": "https://api.github.com/repos/symfony/mime/zipball/7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", + "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", "shasum": "" }, "require": { @@ -1096,7 +845,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.2.7" + "source": "https://github.com/symfony/mime/tree/v6.3.0" }, "funding": [ { @@ -1112,7 +861,7 @@ "type": "tidelift" } ], - "time": "2023-02-24T10:42:00+00:00" + "time": "2023-04-28T15:57:00+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1528,16 +1277,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a8c9cedf55f314f3a186041d19537303766df09a" + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", - "reference": "a8c9cedf55f314f3a186041d19537303766df09a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", "shasum": "" }, "require": { @@ -1547,13 +1296,10 @@ "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -1593,7 +1339,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" }, "funding": [ { @@ -1609,20 +1355,20 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "twig/twig", - "version": "v3.5.1", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15" + "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15", - "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", + "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", "shasum": "" }, "require": { @@ -1631,15 +1377,10 @@ "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "psr/container": "^1.0", + "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } - }, "autoload": { "psr-4": { "Twig\\": "src/" @@ -1673,7 +1414,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.5.1" + "source": "https://github.com/twigphp/Twig/tree/v3.6.1" }, "funding": [ { @@ -1685,7 +1426,7 @@ "type": "tidelift" } ], - "time": "2023-02-08T07:49:20+00:00" + "time": "2023-06-08T12:52:13+00:00" }, { "name": "whichbrowser/parser", diff --git a/misuzu.php b/misuzu.php index 44837ca..a339501 100644 --- a/misuzu.php +++ b/misuzu.php @@ -7,7 +7,6 @@ use Index\Data\ConnectionFailedException; use Index\Data\DbTools; use Misuzu\Config\IConfig; use Misuzu\Config\DbConfig; -use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; use Misuzu\Users\UserSession; @@ -127,8 +126,6 @@ if(!is_readable(MSZ_STORAGE) || !is_writable(MSZ_STORAGE)) { exit; } -IPAddress::init($msz); - if(!MSZ_DEBUG) { $twigCacheDirSfx = GitInfo::hash(true); if(empty($twigCacheDirSfx)) diff --git a/public/auth/login.php b/public/auth/login.php index cc5e33f..25278bc 100644 --- a/public/auth/login.php +++ b/public/auth/login.php @@ -42,6 +42,7 @@ if(!empty($_GET['resolve'])) { $notices = []; $ipAddress = $_SERVER['REMOTE_ADDR']; +$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX'; $siteIsPrivate = $cfg->getValue('private.enable', IConfig::T_BOOL); $loginPermCat = $siteIsPrivate ? $cfg->getValue('private.perm.cat', IConfig::T_STR) : ''; $loginPermVal = $siteIsPrivate ? $cfg->getValue('private.perm.val', IConfig::T_INT) : 0; @@ -76,7 +77,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) { try { $userInfo = User::byUsernameOrEMailAddress($_POST['login']['username']); } catch(UserNotFoundException $ex) { - UserLoginAttempt::create($ipAddress, false); + UserLoginAttempt::create($ipAddress, $countryCode, false); $notices[] = $loginFailedError; break; } @@ -87,7 +88,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) { } if($userInfo->isDeleted() || !$userInfo->checkPassword($_POST['login']['password'])) { - UserLoginAttempt::create($ipAddress, false, $userInfo); + UserLoginAttempt::create($ipAddress, $countryCode, false, $userInfo); $notices[] = $loginFailedError; break; } @@ -97,7 +98,7 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) { if(!empty($loginPermCat) && $loginPermVal > 0 && !perms_check_user($loginPermCat, $userInfo->getId(), $loginPermVal)) { $notices[] = "Login succeeded, but you're not allowed to browse the site right now."; - UserLoginAttempt::create($ipAddress, true, $userInfo); + UserLoginAttempt::create($ipAddress, $countryCode, true, $userInfo); break; } @@ -108,10 +109,10 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) { return; } - UserLoginAttempt::create($ipAddress, true, $userInfo); + UserLoginAttempt::create($ipAddress, $countryCode, true, $userInfo); try { - $sessionInfo = UserSession::create($userInfo, $ipAddress); + $sessionInfo = UserSession::create($userInfo, $ipAddress, $countryCode); $sessionInfo->setCurrent(); } catch(UserSessionCreationFailedException $ex) { $notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!"; diff --git a/public/auth/password.php b/public/auth/password.php index c681e9a..a293cf1 100644 --- a/public/auth/password.php +++ b/public/auth/password.php @@ -79,7 +79,7 @@ while($canResetPassword) { ->removeTOTPKey() ->save(); - AuditLog::create($ipAddress, AuditLog::PASSWORD_RESET, [], $userInfo); + AuditLog::create(AuditLog::PASSWORD_RESET, [], $userInfo); $tokenInfo->invalidate(); diff --git a/public/auth/register.php b/public/auth/register.php index c79756a..6dc0337 100644 --- a/public/auth/register.php +++ b/public/auth/register.php @@ -18,6 +18,7 @@ if(UserSession::hasCurrent()) { $register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : []; $notices = []; $ipAddress = $_SERVER['REMOTE_ADDR']; +$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX'; $remainingAttempts = UserLoginAttempt::remaining($_SERVER['REMOTE_ADDR']); $restricted = UserWarning::countByRemoteAddress($ipAddress) > 0 ? 'ban' : ''; @@ -78,7 +79,8 @@ while(!$restricted && !empty($register)) { $register['username'], $register['password'], $register['email'], - $ipAddress + $ipAddress, + $countryCode ); } catch(UserCreationFailedException $ex) { $notices[] = 'Something went wrong while creating your account, please alert an administrator or a developer about this!'; diff --git a/public/auth/twofactor.php b/public/auth/twofactor.php index b80c83c..2a149fc 100644 --- a/public/auth/twofactor.php +++ b/public/auth/twofactor.php @@ -16,6 +16,7 @@ if(UserSession::hasCurrent()) { } $ipAddress = $_SERVER['REMOTE_ADDR']; +$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX'; $twofactor = !empty($_POST['twofactor']) && is_array($_POST['twofactor']) ? $_POST['twofactor'] : []; $notices = []; $remainingAttempts = UserLoginAttempt::remaining($ipAddress); @@ -67,15 +68,15 @@ while(!empty($twofactor)) { $remainingAttempts - 1, $remainingAttempts === 2 ? '' : 's' ); - UserLoginAttempt::create($ipAddress, false, $userInfo); + UserLoginAttempt::create($ipAddress, $countryCode, false, $userInfo); break; } - UserLoginAttempt::create($ipAddress, true, $userInfo); + UserLoginAttempt::create($ipAddress, $countryCode, true, $userInfo); $tokenInfo->delete(); try { - $sessionInfo = UserSession::create($userInfo, $ipAddress); + $sessionInfo = UserSession::create($userInfo, $ipAddress, $countryCode); $sessionInfo->setCurrent(); } catch(UserSessionCreationFailedException $ex) { $notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!"; diff --git a/public/comments.php b/public/comments.php index 06dab2a..a550836 100644 --- a/public/comments.php +++ b/public/comments.php @@ -138,13 +138,13 @@ switch($commentMode) { $commentInfo2->save(); if($isModAction) { - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::COMMENT_ENTRY_DELETE_MOD, [ + AuditLog::create(AuditLog::COMMENT_ENTRY_DELETE_MOD, [ $commentInfo2->getId(), $commentUserId = $commentInfo2->getUserId(), ($commentUserId < 1 ? '(Deleted User)' : $commentInfo2->getUser()->getUsername()), ]); } else { - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::COMMENT_ENTRY_DELETE, [$commentInfo2->getId()]); + AuditLog::create(AuditLog::COMMENT_ENTRY_DELETE, [$commentInfo2->getId()]); } redirect($redirect); @@ -164,7 +164,7 @@ switch($commentMode) { $commentInfo2->setDeleted(false); $commentInfo2->save(); - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::COMMENT_ENTRY_RESTORE, [ + AuditLog::create(AuditLog::COMMENT_ENTRY_RESTORE, [ $commentInfo2->getId(), $commentUserId = $commentInfo2->getUserId(), ($commentUserId < 1 ? '(Deleted User)' : $commentInfo2->getUser()->getUsername()), diff --git a/public/forum/post.php b/public/forum/post.php index c4a0f09..0fedca1 100644 --- a/public/forum/post.php +++ b/public/forum/post.php @@ -104,7 +104,7 @@ switch($postMode) { $deletePost = forum_post_delete($postInfo['post_id']); if($deletePost) { - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_POST_DELETE, [$postInfo['post_id']]); + AuditLog::create(AuditLog::FORUM_POST_DELETE, [$postInfo['post_id']]); } if(!$deletePost) { @@ -147,7 +147,7 @@ switch($postMode) { break; } - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_POST_NUKE, [$postInfo['post_id']]); + AuditLog::create(AuditLog::FORUM_POST_NUKE, [$postInfo['post_id']]); url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]); break; @@ -184,7 +184,7 @@ switch($postMode) { break; } - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_POST_RESTORE, [$postInfo['post_id']]); + AuditLog::create(AuditLog::FORUM_POST_RESTORE, [$postInfo['post_id']]); url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]); break; diff --git a/public/forum/topic.php b/public/forum/topic.php index f729689..88b4322 100644 --- a/public/forum/topic.php +++ b/public/forum/topic.php @@ -166,7 +166,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { $deleteTopic = forum_topic_delete($topic['topic_id']); if($deleteTopic) - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_TOPIC_DELETE, [$topic['topic_id']]); + AuditLog::create(AuditLog::FORUM_TOPIC_DELETE, [$topic['topic_id']]); if(!$deleteTopic) { echo render_error(500); @@ -209,7 +209,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { break; } - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_TOPIC_RESTORE, [$topic['topic_id']]); + AuditLog::create(AuditLog::FORUM_TOPIC_RESTORE, [$topic['topic_id']]); url_redirect('forum-category', [ 'forum' => $topic['forum_id'], @@ -247,7 +247,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { break; } - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_TOPIC_NUKE, [$topic['topic_id']]); + AuditLog::create(AuditLog::FORUM_TOPIC_NUKE, [$topic['topic_id']]); url_redirect('forum-category', [ 'forum' => $topic['forum_id'], @@ -256,7 +256,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { case 'bump': if($canBumpTopic && forum_topic_bump($topic['topic_id'])) { - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_TOPIC_BUMP, [$topic['topic_id']]); + AuditLog::create(AuditLog::FORUM_TOPIC_BUMP, [$topic['topic_id']]); } url_redirect('forum-topic', [ @@ -266,7 +266,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { case 'lock': if($canLockTopic && !$topicIsLocked && forum_topic_lock($topic['topic_id'])) { - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_TOPIC_LOCK, [$topic['topic_id']]); + AuditLog::create(AuditLog::FORUM_TOPIC_LOCK, [$topic['topic_id']]); } url_redirect('forum-topic', [ @@ -276,7 +276,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { case 'unlock': if($canLockTopic && $topicIsLocked && forum_topic_unlock($topic['topic_id'])) { - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_TOPIC_UNLOCK, [$topic['topic_id']]); + AuditLog::create(AuditLog::FORUM_TOPIC_UNLOCK, [$topic['topic_id']]); } url_redirect('forum-topic', [ diff --git a/public/manage/changelog/change.php b/public/manage/changelog/change.php index faaee59..69f2299 100644 --- a/public/manage/changelog/change.php +++ b/public/manage/changelog/change.php @@ -60,7 +60,6 @@ if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { ->save(); AuditLog::create( - $_SERVER['REMOTE_ADDR'], empty($isNew) ? AuditLog::CHANGELOG_ENTRY_EDIT : AuditLog::CHANGELOG_ENTRY_CREATE, diff --git a/public/manage/changelog/tag.php b/public/manage/changelog/tag.php index aa49ee2..497cba0 100644 --- a/public/manage/changelog/tag.php +++ b/public/manage/changelog/tag.php @@ -35,7 +35,6 @@ if(!empty($_POST['tag']) && is_array($_POST['tag']) && CSRF::validateRequest()) ->save(); AuditLog::create( - $_SERVER['REMOTE_ADDR'], empty($isNew) ? AuditLog::CHANGELOG_TAG_EDIT : AuditLog::CHANGELOG_TAG_CREATE, diff --git a/public/manage/forum/redirs.php b/public/manage/forum/redirs.php index 5fa62fa..e9422d9 100644 --- a/public/manage/forum/redirs.php +++ b/public/manage/forum/redirs.php @@ -20,7 +20,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { if($rTopicId < 1) throw new \Exception("Invalid topic id."); - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_TOPIC_REDIR_CREATE, [$rTopicId]); + AuditLog::create(AuditLog::FORUM_TOPIC_REDIR_CREATE, [$rTopicId]); forum_topic_redir_create($rTopicId, User::getCurrent()->getId(), $rTopicURL); url_redirect('manage-forum-topic-redirs'); return; @@ -31,7 +31,7 @@ if(filter_input(INPUT_GET, 'm') === 'explode') { throw new \Exception("Request verification failed."); $rTopicId = (int)filter_input(INPUT_GET, 't'); - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::FORUM_TOPIC_REDIR_REMOVE, [$rTopicId]); + AuditLog::create(AuditLog::FORUM_TOPIC_REDIR_REMOVE, [$rTopicId]); forum_topic_redir_remove($rTopicId); url_redirect('manage-forum-topic-redirs'); return; diff --git a/public/manage/general/setting-delete.php b/public/manage/general/setting-delete.php index 3b0bcfe..0db5a70 100644 --- a/public/manage/general/setting-delete.php +++ b/public/manage/general/setting-delete.php @@ -22,7 +22,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { if(!CSRF::validateRequest()) throw new \Exception("Request verification failed."); - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::CONFIG_DELETE, [$sName]); + AuditLog::create(AuditLog::CONFIG_DELETE, [$sName]); $cfg->removeValue($sName); url_redirect('manage-general-settings'); } else { diff --git a/public/manage/general/setting.php b/public/manage/general/setting.php index be1e766..4d07be3 100644 --- a/public/manage/general/setting.php +++ b/public/manage/general/setting.php @@ -95,7 +95,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { $sVar['value'] = $sValue; - AuditLog::create($_SERVER['REMOTE_ADDR'], $sLogAction, [$sName]); + AuditLog::create($sLogAction, [$sName]); $cfg->setValue($sName, $sValue); url_redirect('manage-general-settings'); return; diff --git a/public/manage/news/category.php b/public/manage/news/category.php index fc50733..12f34f3 100644 --- a/public/manage/news/category.php +++ b/public/manage/news/category.php @@ -36,7 +36,6 @@ if(!empty($_POST['category']) && CSRF::validateRequest()) { ->save(); AuditLog::create( - $_SERVER['REMOTE_ADDR'], empty($isNew) ? AuditLog::NEWS_CATEGORY_EDIT : AuditLog::NEWS_CATEGORY_CREATE, diff --git a/public/manage/news/post.php b/public/manage/news/post.php index 0d6efad..19f5cf0 100644 --- a/public/manage/news/post.php +++ b/public/manage/news/post.php @@ -44,7 +44,6 @@ if(!empty($_POST['post']) && CSRF::validateRequest()) { $postInfo->save(); AuditLog::create( - $_SERVER['REMOTE_ADDR'], empty($isNew) ? AuditLog::NEWS_POST_EDIT : AuditLog::NEWS_POST_CREATE, diff --git a/public/settings/account.php b/public/settings/account.php index b6c3a36..b8bd75f 100644 --- a/public/settings/account.php +++ b/public/settings/account.php @@ -103,7 +103,7 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) { } } else { $currentUser->setEMailAddress($_POST['email']['new']); - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::PERSONAL_EMAIL_CHANGE, [ + AuditLog::create(AuditLog::PERSONAL_EMAIL_CHANGE, [ $_POST['email']['new'], ]); } @@ -121,7 +121,7 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) { $errors[] = 'The given passwords was too weak.'; } else { $currentUser->setPassword($_POST['password']['new']); - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::PERSONAL_PASSWORD_CHANGE); + AuditLog::create(AuditLog::PERSONAL_PASSWORD_CHANGE); } } } diff --git a/public/settings/data.php b/public/settings/data.php index fcb49c9..a6e9ca7 100644 --- a/public/settings/data.php +++ b/public/settings/data.php @@ -36,7 +36,7 @@ if(isset($_POST['action']) && is_string($_POST['action'])) { && $currentUser->checkPassword($_POST['password'] ?? '')) { switch($_POST['action']) { case 'data': - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::PERSONAL_DATA_DOWNLOAD); + AuditLog::create(AuditLog::PERSONAL_DATA_DOWNLOAD); $timeStamp = floor(time() / 3600) * 3600; $fileName = sprintf('msz-user-data-%d-%d.zip', $currentUserId, $timeStamp); diff --git a/public/settings/sessions.php b/public/settings/sessions.php index e906296..cdbd32d 100644 --- a/public/settings/sessions.php +++ b/public/settings/sessions.php @@ -38,12 +38,12 @@ if(!empty($_POST['session']) && CSRF::validateRequest()) { } $sessionInfo->delete(); - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::PERSONAL_SESSION_DESTROY, [$sessionInfo->getId()]); + AuditLog::create(AuditLog::PERSONAL_SESSION_DESTROY, [$sessionInfo->getId()]); } } elseif($_POST['session'] === 'all') { $currentSessionKilled = true; UserSession::purgeUser($currentUser); - AuditLog::create($_SERVER['REMOTE_ADDR'], AuditLog::PERSONAL_SESSION_DESTROY_ALL); + AuditLog::create(AuditLog::PERSONAL_SESSION_DESTROY_ALL); } if($currentSessionKilled) { diff --git a/src/AuditLog.php b/src/AuditLog.php index ffaa2f0..3388580 100644 --- a/src/AuditLog.php +++ b/src/AuditLog.php @@ -3,7 +3,6 @@ namespace Misuzu; use Misuzu\DB; use Misuzu\Pagination; -use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; @@ -159,16 +158,19 @@ class AuditLog { return vsprintf(self::FORMATS[$this->getAction()], $this->getParams()); } - public static function create(string $remoteAddr, string $action, array $params = [], ?User $user = null): void { + public static function create(string $action, array $params = [], ?User $user = null): void { $user = $user ?? User::getCurrent(); + $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '::1'; + $countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX'; + $createLog = DB::prepare( 'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`log_action`, `user_id`, `log_params`, `log_ip`, `log_country`)' . ' VALUES (:action, :user, :params, INET6_ATON(:ip), :country)' ) ->bind('action', $action) - ->bind('user', $user === null ? null : $user->getId()) // this null situation should never ever happen but better safe than sorry ! + ->bind('user', $user === null ? null : $user->getId()) ->bind('params', json_encode($params)) ->bind('ip', $remoteAddr) - ->bind('country', IPAddress::country($remoteAddr)) + ->bind('country', $countryCode) ->execute(); } diff --git a/src/GeoIP/GeoIPHelper.php b/src/GeoIP/GeoIPHelper.php deleted file mode 100644 index de9a85d..0000000 --- a/src/GeoIP/GeoIPHelper.php +++ /dev/null @@ -1,76 +0,0 @@ -config = $config; - } - - public function hasDbPath(string $type): bool { - return array_key_exists($type, $this->readers) - || $this->config->hasValue('db.' . $type); - } - - public function getDbPath(string $type): string { - $type = 'db.' . $type; - if(!$this->config->hasValue($type)) - throw new RuntimeException('Not database path has been configured for $type.'); - return $this->config->getValue($type, IConfig::T_STR); - } - - public function getReader(string $type): Reader { - if(!array_key_exists($type, $this->readers)) - return $this->readers[$type] = new Reader($this->getDbPath($type)); - return $this->readers[$type]; - } - - public function getASN(IPAddress|string $ipAddress): Asn { - if($ipAddress instanceof IPAddress) - $ipAddress = $ipAddress->getCleanAddress(); - - if($this->hasDbPath(self::ASN)) - return $this->getReader(self::ASN)->asn($ipAddress); - - throw new RuntimeException('There was no database available that could satisfy this request.'); - } - - public function getCityOrCountry(IPAddress|string $ipAddress): City|Country { - if($ipAddress instanceof IPAddress) - $ipAddress = $ipAddress->getCleanAddress(); - - if($this->hasDbPath(self::CITY)) - return $this->getReader(self::CITY)->city($ipAddress); - if($this->hasDbPath(self::COUNTRY)) - return $this->getReader(self::COUNTRY)->country($ipAddress); - - throw new RuntimeException('There was no database available that could satisfy this request.'); - } - - public function getIsoCountryCode(IPAddress|string $ipAddress, string $fallback = 'XX'): string { - try { - return $this->getCityOrCountry($ipAddress)->country->isoCode ?? $fallback; - } catch(AddressNotFoundException $ex) { - return $fallback; - } catch(RuntimeException $ex) { - return $fallback; - } - } -} diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index b2eebf5..2174018 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -3,7 +3,6 @@ namespace Misuzu; use Misuzu\Template; use Misuzu\Config\IConfig; -use Misuzu\GeoIP\GeoIPHelper; use Misuzu\SharpChat\SharpChatRoutes; use Misuzu\Users\Users; use Index\Data\IDbConnection; @@ -22,7 +21,6 @@ class MisuzuContext { private IConfig $config; private Users $users; private HttpFx $router; - private ?GeoIPHelper $geoIP = null; public function __construct(IDbConnection $dbConn, IConfig $config) { $this->dbConn = $dbConn; @@ -59,12 +57,6 @@ class MisuzuContext { return $this->users; }*/ - public function getGeoIP(): GeoIPHelper { - if($this->geoIP === null) - return $this->geoIP = new GeoIPHelper($this->config->scopeTo('geoip')); - return $this->geoIP; - } - public function setUpHttp(bool $legacy = false): void { $this->router = new HttpFx; $this->router->use('/', function($response) { diff --git a/src/Net/IPAddress.php b/src/Net/IPAddress.php deleted file mode 100644 index d40220a..0000000 --- a/src/Net/IPAddress.php +++ /dev/null @@ -1,19 +0,0 @@ -getGeoIP()->getIsoCountryCode($address, $fallback); - } -} diff --git a/src/Users/User.php b/src/Users/User.php index 6d66897..5adfe03 100644 --- a/src/Users/User.php +++ b/src/Users/User.php @@ -10,7 +10,6 @@ use Misuzu\HasRankInterface; use Misuzu\Memoizer; use Misuzu\Pagination; use Misuzu\TOTP; -use Misuzu\Net\IPAddress; use Misuzu\Parsers\Parser; use Misuzu\Users\Assets\UserAvatarAsset; use Misuzu\Users\Assets\UserBackgroundAsset; @@ -732,7 +731,8 @@ class User implements HasRankInterface { string $username, string $password, string $email, - string $ipAddress + string $ipAddress, + string $countryCode = 'XX' ): self { $createUser = DB::prepare( 'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`username`, `password`, `email`, `register_ip`, `last_ip`, `user_country`, `display_role`)' @@ -742,7 +742,7 @@ class User implements HasRankInterface { ->bind('register_ip', $ipAddress) ->bind('last_ip', $ipAddress) ->bind('password', self::hashPassword($password)) - ->bind('user_country', IPAddress::country($ipAddress)) + ->bind('user_country', $countryCode) ->executeGetId(); if($createUser < 1) diff --git a/src/Users/UserLoginAttempt.php b/src/Users/UserLoginAttempt.php index 7c3aca8..679e1d0 100644 --- a/src/Users/UserLoginAttempt.php +++ b/src/Users/UserLoginAttempt.php @@ -3,7 +3,6 @@ namespace Misuzu\Users; use Misuzu\DB; use Misuzu\Pagination; -use Misuzu\Net\IPAddress; use WhichBrowser\Parser as UserAgentParser; class UserLoginAttempt { @@ -77,15 +76,22 @@ class UserLoginAttempt { ->fetchColumn(); } - public static function create(string $remoteAddr, bool $success, ?User $user = null, string $userAgent = null): void { + public static function create( + string $remoteAddr, + string $countryCode, + bool $success, + ?User $user = null, + string $userAgent = null + ): void { $userAgent = $userAgent ?? filter_input(INPUT_SERVER, 'HTTP_USER_AGENT') ?? ''; + $createLog = DB::prepare( 'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`user_id`, `attempt_success`, `attempt_ip`, `attempt_country`, `attempt_user_agent`)' . ' VALUES (:user, :success, INET6_ATON(:ip), :country, :user_agent)' ) ->bind('user', $user === null ? null : $user->getId()) // this null situation should never ever happen but better safe than sorry ! ->bind('success', $success ? 1 : 0) ->bind('ip', $remoteAddr) - ->bind('country', IPAddress::country($remoteAddr)) + ->bind('country', $countryCode) ->bind('user_agent', $userAgent) ->execute(); } diff --git a/src/Users/UserRecoveryToken.php b/src/Users/UserRecoveryToken.php index f79ca4a..29458ac 100644 --- a/src/Users/UserRecoveryToken.php +++ b/src/Users/UserRecoveryToken.php @@ -2,7 +2,6 @@ namespace Misuzu\Users; use Misuzu\DB; -use Misuzu\Net\IPAddress; class UserRecoveryTokenException extends UsersException {} class UserRecoveryTokenNotFoundException extends UserRecoveryTokenException {} diff --git a/src/Users/UserSession.php b/src/Users/UserSession.php index d5b2c25..160dc15 100644 --- a/src/Users/UserSession.php +++ b/src/Users/UserSession.php @@ -3,7 +3,6 @@ namespace Misuzu\Users; use Misuzu\DB; use Misuzu\Pagination; -use Misuzu\Net\IPAddress; use WhichBrowser\Parser as UserAgentParser; class UserSessionException extends UsersException {} @@ -174,7 +173,7 @@ class UserSession { ->execute(); } - public static function create(User $user, string $remoteAddr, ?string $userAgent = null, ?string $token = null): self { + public static function create(User $user, string $remoteAddr, string $countryCode, ?string $userAgent = null, ?string $token = null): self { $userAgent = $userAgent ?? filter_input(INPUT_SERVER, 'HTTP_USER_AGENT') ?? ''; $token = $token ?? self::generateToken(); @@ -184,7 +183,7 @@ class UserSession { . ' VALUES (:user, INET6_ATON(:remote_addr), :country, :user_agent, :token, NOW(), NOW() + INTERVAL :expires SECOND)' ) ->bind('user', $user->getId()) ->bind('remote_addr', $remoteAddr) - ->bind('country', IPAddress::country($remoteAddr)) + ->bind('country', $countryCode) ->bind('user_agent', $userAgent) ->bind('token', $token) ->bind('expires', self::LIFETIME)