Unverified Commit 5bd6c14c authored by Stefano Borzì's avatar Stefano Borzì Committed by GitHub
Browse files

Merge pull request #3 from masterking32/master

Update fork
parents ee6add25 e8ad4d97
This diff is collapsed.
......@@ -4,6 +4,11 @@ With this script, You can make a website for your game server.
Support : [AzerothCore](http://azerothcore.org), [TrinityCore](http://TrinityCore.org), [AshamaneCore](https://github.com/ReyDonovan/AshamaneCoreLegacy/), [CMangos](https://github.com/cmangos/).
## Requirement : PHP >= 7.0
Enable gmp, gd, soap, mbstring, pdo and pdo-mysql.
# Installation
- Download project & unzip.
......@@ -11,10 +16,6 @@ Support : [AzerothCore](http://azerothcore.org), [TrinityCore](http://TrinityCor
- Open the config file and set your server data.
- Enjoy that.
## Requirement : PHP >= 7.0
Enable gd, soap, mbstring, pdo and pdo-mysql.
# Debug
If you got a blank screen, You can enable `debug_mode` in the config file.
......@@ -34,7 +35,10 @@ If you got a blank screen, You can enable `debug_mode` in the config file.
## Changelogs
**1.9.7 (7/28/2020):**
**1.9.7.5 (8/03/2020):**
1. Support SRP6.
**1.9.7 (7/28/2020):**
1. Support Two-Factor Authentication (2FA)
2. Fixed a low-level vulnerability. (UPDATE TO THIS VERSION)
3. Fixed some of the bugs.
......
......@@ -5,6 +5,7 @@
"rmccue/requests": "^1.7",
"phpmailer/phpmailer": "^6.0",
"gregwar/captcha": "^1.1",
"catfan/medoo": "^1.6"
"catfan/medoo": "^1.6",
"phpgangsta/googleauthenticator": "dev-master"
}
}
......@@ -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": "1176d28ab3f120f9387d8df9cebc8237",
"content-hash": "fdfc47f418183f38f911d2dc256490e5",
"packages": [
{
"name": "catfan/medoo",
......@@ -119,6 +119,49 @@
],
"time": "2020-01-22T14:54:02+00:00"
},
{
"name": "phpgangsta/googleauthenticator",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/PHPGangsta/GoogleAuthenticator.git",
"reference": "505c2af8337b559b33557f37cda38e5f843f3768"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPGangsta/GoogleAuthenticator/zipball/505c2af8337b559b33557f37cda38e5f843f3768",
"reference": "505c2af8337b559b33557f37cda38e5f843f3768",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"type": "library",
"autoload": {
"classmap": [
"PHPGangsta/GoogleAuthenticator.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-4-Clause"
],
"authors": [
{
"name": "Michael Kliewe",
"email": "info@phpgangsta.de",
"homepage": "http://www.phpgangsta.de/",
"role": "Developer"
}
],
"description": "Google Authenticator 2-factor authentication",
"keywords": [
"googleauthenticator",
"rfc6238",
"totp"
],
"time": "2019-03-20T00:55:58+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.1.7",
......@@ -826,7 +869,9 @@
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {
"phpgangsta/googleauthenticator": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
......
......@@ -49,6 +49,11 @@ If your server is WoD/Legion/BFA you should enable it!
=====================================================================*/
$config['battlenet_support'] = false;
/*===================================================================
If your core password encryption is SRP6, you need to enable it.
For last versions of the TrinityCore(3.3.5/master) you need to enable it. https://git.io/JJRH4 and https://git.io/JJrxq
=====================================================================*/
$config['srp6_support'] = false;
/*===================================================================
If you have an issue with top players or online players you can disable them!
disable_top_players
Disable server top players page [true: Hide top players page]
......@@ -145,10 +150,8 @@ $config['captcha_secret'] = '';
$config['captcha_language'] = 'en';
/*===================================================================
soap_for_register
Enable it if you have CMangos-Classic/MangosZero or Some of TBC servers.
Don't Enable it for AzerothCore/TrinityCore/SkyFire and AshamaneCore.
If your core database is different you can use SOAP for registration.
Servers using SRP6 for the password must enable this option and disabled change password.
If you enable that you need to disabled change password feature.
If you want to enable Two-Factor Authentication (2FA) you don't need to enable this option.
For Two-Factor Authentication (2FA) just need to config other parts of the SOAP.
......@@ -285,4 +288,4 @@ $config['realmlists'] = array( // Add your realmlist here
$config['script_version'] = '1.9.7';
$config['script_version'] = '1.9.8';
......@@ -284,4 +284,60 @@ function GetCaptchaHTML()
}
return '<div class="input-group"><span class="input-group">Captcha</span><input type="text" class="form-control" placeholder="Captcha" name="captcha"></div><p style="text-align: center;margin-top: 10px;"><img src="' . user::$captcha->inline() . '" style="border - radius: 5px;"/></p>';
}
// Its from Trinitycore/account-creator
function calculateSRP6Verifier($username, $password, $salt)
{
// algorithm constants
$g = gmp_init(7);
$N = gmp_init('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16);
// calculate first hash
$h1 = sha1(strtoupper($username . ':' . $password), TRUE);
// calculate second hash
$h2 = sha1($salt . $h1, TRUE);
// convert to integer (little-endian)
$h2 = gmp_import($h2, 1, GMP_LSW_FIRST);
// g^h2 mod N
$verifier = gmp_powm($g, $h2, $N);
// convert back to a byte array (little-endian)
$verifier = gmp_export($verifier, 1, GMP_LSW_FIRST);
// pad to 32 bytes, remember that zeros go on the end in little-endian!
$verifier = str_pad($verifier, 32, chr(0), STR_PAD_RIGHT);
// done!
return $verifier;
}
// Returns SRP6 parameters to register this username/password combination with
function getRegistrationData($username, $password)
{
// generate a random salt
$salt = random_bytes(32);
// calculate verifier using this salt
$verifier = calculateSRP6Verifier($username, $password, $salt);
// done - this is what you put in the account table!
return array($salt, $verifier);
}
//From TrinityCore/AOWOW
function verifySRP6($user, $pass, $salt, $verifier)
{
$g = gmp_init(7);
$N = gmp_init('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16);
$x = gmp_import(
sha1($salt . sha1(strtoupper($user . ':' . $pass), TRUE), TRUE),
1,
GMP_LSW_FIRST
);
$v = gmp_powm($g, $x, $N);
return ($verifier === str_pad(gmp_export($v, 1, GMP_LSW_FIRST), 32, chr(0), STR_PAD_RIGHT));
}
\ No newline at end of file
......@@ -84,6 +84,29 @@ class user
return false;
}
if (empty(get_config('srp6_support'))) {
$bnet_hashed_pass = strtoupper(bin2hex(strrev(hex2bin(strtoupper(hash('sha256', strtoupper(hash('sha256', strtoupper($_POST['email'])) . ':' . strtoupper($_POST['password']))))))));
database::$auth->insert('battlenet_accounts', [
'email' => $antiXss->xss_clean(strtoupper($_POST['email'])),
'sha_pass_hash' => $antiXss->xss_clean($bnet_hashed_pass)
]);
$bnet_account_id = database::$auth->id();
$username = $bnet_account_id . '#1';
$hashed_pass = strtoupper(sha1(strtoupper($username . ':' . $_POST['password'])));
database::$auth->insert('account', [
'username' => $antiXss->xss_clean(strtoupper($username)),
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'email' => $antiXss->xss_clean(strtoupper($_POST['email'])),
'expansion' => $antiXss->xss_clean(get_config('expansion')),
'battlenet_account' => $bnet_account_id,
'battlenet_index' => 1
]);
success_msg('Your account has been created.');
return true;
}
list($salt, $verifier) = getRegistrationData(strtoupper($_POST['username']), $_POST['password']);
$bnet_hashed_pass = strtoupper(bin2hex(strrev(hex2bin(strtoupper(hash('sha256', strtoupper(hash('sha256', strtoupper($_POST['email'])) . ':' . strtoupper($_POST['password']))))))));
database::$auth->insert('battlenet_accounts', [
'email' => $antiXss->xss_clean(strtoupper($_POST['email'])),
......@@ -92,10 +115,10 @@ class user
$bnet_account_id = database::$auth->id();
$username = $bnet_account_id . '#1';
$hashed_pass = strtoupper(sha1(strtoupper($username . ':' . $_POST['password'])));
database::$auth->insert('account', [
'username' => $antiXss->xss_clean(strtoupper($username)),
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'salt' => $salt,
'verifier' => $verifier,
'email' => $antiXss->xss_clean(strtoupper($_POST['email'])),
'expansion' => $antiXss->xss_clean(get_config('expansion')),
'battlenet_account' => $bnet_account_id,
......@@ -156,34 +179,49 @@ class user
}
if (empty(get_config('soap_for_register'))) {
$hashed_pass = strtoupper(sha1(strtoupper($_POST['username'] . ':' . $_POST['password'])));
if (empty(get_config('srp6_support'))) {
$hashed_pass = strtoupper(sha1(strtoupper($_POST['username'] . ':' . $_POST['password'])));
database::$auth->insert('account', [
'username' => $antiXss->xss_clean(strtoupper($_POST['username'])),
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'email' => $antiXss->xss_clean(strtoupper($_POST['email'])),
//'reg_mail' => $antiXss->xss_clean(strtoupper($_POST['email'])),
'expansion' => $antiXss->xss_clean(get_config('expansion'))
]);
success_msg('Your account has been created.');
return true;
}
list($salt, $verifier) = getRegistrationData(strtoupper($_POST['username']), $_POST['password']);
database::$auth->insert('account', [
'username' => $antiXss->xss_clean(strtoupper($_POST['username'])),
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'salt' => $salt,
'verifier' => $verifier,
'email' => $antiXss->xss_clean(strtoupper($_POST['email'])),
//'reg_mail' => $antiXss->xss_clean(strtoupper($_POST['email'])),
'expansion' => $antiXss->xss_clean(get_config('expansion'))
]);
success_msg('Your account has been created.');
} else {
$command = str_replace('{USERNAME}', $antiXss->xss_clean(strtoupper($_POST['username'])), get_config('soap_ca_command'));
$command = str_replace('{PASSWORD}', $antiXss->xss_clean($_POST['password']), $command);
$command = str_replace('{EMAIL}', $antiXss->xss_clean(strtoupper($_POST['email'])), $command);
if (RemoteCommandWithSOAP($command)) {
if (!empty(get_config('soap_asa_command'))) {
$command_addon = str_replace('{USERNAME}', $antiXss->xss_clean(strtoupper($_POST['username'])), get_config('soap_asa_command'));
$command_addon = str_replace('{EXPANSION}', get_config('expansion'), $command_addon);
RemoteCommandWithSOAP($command_addon);
}
database::$auth->update('account', [
'email' => $antiXss->xss_clean(strtoupper($_POST['email']))
], ['username' => Medoo::raw('UPPER(:username)', [':username' => $antiXss->xss_clean(strtoupper($_POST['username']))])]);
return true;
}
success_msg('Your account has been created.');
} else {
error_msg('ERROR!, Please try again!');
$command = str_replace('{USERNAME}', $antiXss->xss_clean(strtoupper($_POST['username'])), get_config('soap_ca_command'));
$command = str_replace('{PASSWORD}', $antiXss->xss_clean($_POST['password']), $command);
$command = str_replace('{EMAIL}', $antiXss->xss_clean(strtoupper($_POST['email'])), $command);
if (RemoteCommandWithSOAP($command)) {
if (!empty(get_config('soap_asa_command'))) {
$command_addon = str_replace('{USERNAME}', $antiXss->xss_clean(strtoupper($_POST['username'])), get_config('soap_asa_command'));
$command_addon = str_replace('{EXPANSION}', get_config('expansion'), $command_addon);
RemoteCommandWithSOAP($command_addon);
}
database::$auth->update('account', [
'email' => $antiXss->xss_clean(strtoupper($_POST['email']))
], ['username' => Medoo::raw('UPPER(:username)', [':username' => $antiXss->xss_clean(strtoupper($_POST['username']))])]);
success_msg('Your account has been created.');
} else {
error_msg('ERROR!, Please try again!');
}
return true;
......@@ -231,22 +269,40 @@ class user
return false;
}
$Old_hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $_POST['old_password'])));
$hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $_POST['password'])));
if (empty(get_config('srp6_support'))) {
$Old_hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $_POST['old_password'])));
$hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $_POST['password'])));
if (strtoupper($userinfo['sha_pass_hash']) != $Old_hashed_pass) {
error_msg('Old password is not valid.');
return false;
}
if (strtoupper($userinfo['sha_pass_hash']) != $Old_hashed_pass) {
error_msg('Old password is not valid.');
return false;
}
database::$auth->update('account', [
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'sessionkey' => '',
'v' => '',
's' => ''
], [
'id[=]' => $userinfo['id']
]);
database::$auth->update('account', [
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'sessionkey' => '',
'v' => '',
's' => ''
], [
'id[=]' => $userinfo['id']
]);
} else {
if (verifySRP6($userinfo['username'], $_POST['old_password'], $userinfo['salt'], $userinfo['verifier'])) {
error_msg('Old password is not valid.');
return false;
}
list($salt, $verifier) = getRegistrationData(strtoupper($userinfo['username']), $_POST['password']);
database::$auth->update('account', [
'salt' => $salt,
'verifier' => $verifier,
'sessionkey' => '',
'v' => '',
's' => ''
], [
'id[=]' => $userinfo['id']
]);
}
$bnet_hashed_pass = strtoupper(bin2hex(strrev(hex2bin(strtoupper(hash('sha256', strtoupper(hash('sha256', strtoupper($userinfo['email'])) . ':' . strtoupper($_POST['password']))))))));
......@@ -296,21 +352,40 @@ class user
return false;
}
$Old_hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $_POST['old_password'])));
$hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $_POST['password'])));
if (strtoupper($userinfo['sha_pass_hash']) != $Old_hashed_pass) {
error_msg('Old password is not valid.');
return false;
}
database::$auth->update('account', [
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'sessionkey' => '',
'v' => '',
's' => ''
], [
'id[=]' => $userinfo['id']
]);
if (empty(get_config('srp6_support'))) {
$Old_hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $_POST['old_password'])));
$hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $_POST['password'])));
if (strtoupper($userinfo['sha_pass_hash']) != $Old_hashed_pass) {
error_msg('Old password is not valid.');
return false;
}
database::$auth->update('account', [
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'sessionkey' => '',
'v' => '',
's' => ''
], [
'id[=]' => $userinfo['id']
]);
} else {
if (verifySRP6($userinfo['username'], $_POST['old_password'], $userinfo['salt'], $userinfo['verifier'])) {
error_msg('Old password is not valid.');
return false;
}
list($salt, $verifier) = getRegistrationData(strtoupper($userinfo['username']), $_POST['password']);
database::$auth->update('account', [
'salt' => $salt,
'verifier' => $verifier,
'sessionkey' => '',
'v' => '',
's' => ''
], [
'id[=]' => $userinfo['id']
]);
}
success_msg('Password has been changed.');
return true;
......@@ -421,26 +496,7 @@ class user
if (get_config('battlenet_support')) {
$message = 'Your new account information : <br>Email: ' . strtolower($userinfo['email']) . '<br>Password: ' . $new_password;
$hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $new_password)));
database::$auth->update('account', [
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'sessionkey' => '',
'v' => '',
's' => '',
'restore_key' => '1'
], [
'id[=]' => $userinfo['id']
]);
$bnet_hashed_pass = strtoupper(bin2hex(strrev(hex2bin(strtoupper(hash('sha256', strtoupper(hash('sha256', strtoupper($userinfo['email'])) . ':' . strtoupper($new_password))))))));
database::$auth->update('battlenet_accounts', [
'sha_pass_hash' => $antiXss->xss_clean($bnet_hashed_pass)
], [
'id[=]' => $userinfo['battlenet_account']
]);
} else {
$message = 'Your new account information : <br>Username: ' . strtolower($userinfo['username']) . '<br>Password: ' . $new_password;
if (empty(get_config('soap_for_register'))) {
if (empty(get_config('srp6_support'))) {
$hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $new_password)));
database::$auth->update('account', [
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
......@@ -451,6 +507,53 @@ class user
], [
'id[=]' => $userinfo['id']
]);
} else {
list($salt, $verifier) = getRegistrationData(strtoupper($userinfo['username']), $new_password);
database::$auth->update('account', [
'salt' => $salt,
'verifier' => $verifier,
'sessionkey' => '',
'v' => '',
's' => '',
'restore_key' => '1'
], [
'id[=]' => $userinfo['id']
]);
}
$bnet_hashed_pass = strtoupper(bin2hex(strrev(hex2bin(strtoupper(hash('sha256', strtoupper(hash('sha256', strtoupper($userinfo['email'])) . ':' . strtoupper($new_password))))))));
database::$auth->update('battlenet_accounts', [
'sha_pass_hash' => $antiXss->xss_clean($bnet_hashed_pass)
], [
'id[=]' => $userinfo['battlenet_account']
]);
} else {
$message = 'Your new account information : <br>Username: ' . strtolower($userinfo['username']) . '<br>Password: ' . $new_password;
if (empty(get_config('soap_for_register'))) {
if (empty(get_config('srp6_support'))) {
$hashed_pass = strtoupper(sha1(strtoupper($userinfo['username'] . ':' . $new_password)));
database::$auth->update('account', [
'sha_pass_hash' => $antiXss->xss_clean($hashed_pass),
'sessionkey' => '',
'v' => '',
's' => '',
'restore_key' => '1'
], [
'id[=]' => $userinfo['id']
]);
} else {
list($salt, $verifier) = getRegistrationData(strtoupper($userinfo['username']), $new_password);
database::$auth->update('account', [
'salt' => $salt,
'verifier' => $verifier,
'sessionkey' => '',
'v' => '',
's' => '',
'restore_key' => '1'
], [
'id[=]' => $userinfo['id']
]);
}
} else {
$command = str_replace('{USERNAME}', $antiXss->xss_clean(strtoupper($userinfo['username'])), get_config('soap_cp_command'));
$command = str_replace('{PASSWORD}', $antiXss->xss_clean($new_password), $command);
......@@ -648,7 +751,8 @@ class user
return false;
}
$tfa_key = strtoupper(generateRandomString(16));
$ga = new PHPGangsta_GoogleAuthenticator();
$tfa_key = $ga->createSecret();
database::$auth->update('account', [
'restore_key' => '1'
......@@ -662,12 +766,16 @@ class user
$command = str_replace('{SECRET}', $tfa_key, $command);
RemoteCommandWithSOAP($command);
$acc_name = str_replace('-', '', $acc_name);
$acc_name = str_replace('.', '', $acc_name);
$acc_name = str_replace('_', '', $acc_name);
$acc_name = str_replace('@', '', $acc_name);
$message = 'Two-Factor Authentication (2FA) enabled on your account.<br>Please scan the barcode with Google Authenticator.<BR>';
$message .= '<img src="https://api.qrserver.com/v1/create-qr-code/?data=otpauth://totp/' . get_config('page_title') . '-' . $acc_name . '?secret=' . $tfa_key . '&size=200x200&ecc=M"><BR>';
$message .= '<img src="' . $ga->getQRCodeGoogleUrl($acc_name, $tfa_key) . '"><BR>';
$message .= 'or you can add this code to Google Authenticator: <B>' . $tfa_key . '</B>.<BR>';
send_phpmailer(strtolower($userinfo['email']), 'Account 2FA enabled', $message);
success_msg('Account 2FA enabled please check your email, (Check SPAM/Junk too).');
}
}
......@@ -37,7 +37,7 @@ require_once app_path . 'include/status.php';
$antiXss = new AntiXSS();
if (!empty(get_config('script_version'))) {
/* @TODO Add online version check! */
if(version_compare(get_config('script_version'), '1.9.7', '<') )
if(version_compare(get_config('script_version'), '1.9.8', '<') )
{
echo 'Use last version of config.php file.';
exit();
......
......@@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
return array(
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PHPGangsta_GoogleAuthenticator' => $vendorDir . '/phpgangsta/googleauthenticator/PHPGangsta/GoogleAuthenticator.php',
'SebastianBergmann\\Timer\\Exception' => $vendorDir . '/phpunit/php-timer/src/Exception.php',
'SebastianBergmann\\Timer\\RuntimeException' => $vendorDir . '/phpunit/php-timer/src/RuntimeException.php',
'SebastianBergmann\\Timer\\Timer' => $vendorDir . '/phpunit/php-timer/src/Timer.php',
......
......@@ -104,6 +104,7 @@ class ComposerStaticInitde424ad7860a40a14ec11f109060d25d
public static $classMap = array (
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PHPGangsta_GoogleAuthenticator' => __DIR__ . '/..' . '/phpgangsta/googleauthenticator/PHPGangsta/GoogleAuthenticator.php',
'SebastianBergmann\\Timer\\Exception' => __DIR__ . '/..' . '/phpunit/php-timer/src/Exception.php',
'SebastianBergmann\\Timer\\RuntimeException' => __DIR__ . '/..' . '/phpunit/php-timer/src/RuntimeException.php',
'SebastianBergmann\\Timer\\Timer' => __DIR__ . '/..' . '/phpunit/php-timer/src/Timer.php',
......
......@@ -116,6 +116,51 @@
"spam"
]
},
{
"name": "phpgangsta/googleauthenticator",
"version": "dev-master",
"version_normalized": "9999999-dev",
"source": {
"type": "git",
"url": "https://github.com/PHPGangsta/GoogleAuthenticator.git",
"reference": "505c2af8337b559b33557f37cda38e5f843f3768"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPGangsta/GoogleAuthenticator/zipball/505c2af8337b559b33557f37cda38e5f843f3768",
"reference": "505c2af8337b559b33557f37cda38e5f843f3768",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"time": "2019-03-20T00:55:58+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"classmap": [
"PHPGangsta/GoogleAuthenticator.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-4-Clause"
],