Moved passwords out of the users table.
This commit is contained in:
parent
7f85abba6e
commit
f39e1230c5
13 changed files with 202 additions and 97 deletions
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
use Index\Db\DbConnection;
|
||||
use Index\Db\Migration\DbMigration;
|
||||
|
||||
final class MovedPasswordsOutOfTheUserTable_20250208_235046 implements DbMigration {
|
||||
public function migrate(DbConnection $conn): void {
|
||||
$conn->execute(<<<SQL
|
||||
CREATE TABLE msz_users_passwords (
|
||||
user_id INT(10) UNSIGNED NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL COLLATE 'ascii_bin',
|
||||
password_created TIMESTAMP NOT NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (user_id),
|
||||
CONSTRAINT users_passwords_users_foreign
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES msz_users (user_id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
) COLLATE='utf8mb4_bin';
|
||||
SQL);
|
||||
|
||||
$conn->execute(<<<SQL
|
||||
INSERT msz_users_passwords
|
||||
SELECT user_id, user_password, NOW()
|
||||
FROM msz_users
|
||||
WHERE user_password IS NOT NULL AND TRIM(user_password) <> ''
|
||||
SQL);
|
||||
|
||||
$conn->execute(<<<SQL
|
||||
ALTER TABLE msz_users
|
||||
DROP COLUMN user_password;
|
||||
SQL);
|
||||
}
|
||||
}
|
|
@ -98,19 +98,26 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
|
|||
break;
|
||||
}
|
||||
|
||||
if(!$userInfo->hasPasswordHash) {
|
||||
$notices[] = 'Your password has been invalidated, please reset it.';
|
||||
break;
|
||||
}
|
||||
|
||||
if($userInfo->deleted || !$userInfo->verifyPassword($_POST['login']['password'])) {
|
||||
if($userInfo->deleted) {
|
||||
$msz->authCtx->loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
|
||||
$notices[] = $loginFailedError;
|
||||
break;
|
||||
}
|
||||
|
||||
if($userInfo->passwordNeedsRehash)
|
||||
$msz->usersCtx->users->updateUser($userInfo, password: $_POST['login']['password']);
|
||||
$pwInfo = $msz->usersCtx->passwords->getUserPassword($userInfo);
|
||||
if($pwInfo === null) {
|
||||
$notices[] = 'Your password has been invalidated, please reset it.';
|
||||
break;
|
||||
}
|
||||
|
||||
if(!$pwInfo->verifyPassword($_POST['login']['password'])) {
|
||||
$msz->authCtx->loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo);
|
||||
$notices[] = $loginFailedError;
|
||||
break;
|
||||
}
|
||||
|
||||
if($pwInfo->needsRehash)
|
||||
$msz->usersCtx->passwords->updateUserPassword($userInfo, $_POST['login']['password']);
|
||||
|
||||
if(!empty($loginPermCat) && $loginPermVal > 0 && !$msz->perms->checkPermissions($loginPermCat, $loginPermVal, $userInfo)) {
|
||||
$notices[] = "Login succeeded, but you're not allowed to browse the site right now.";
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\{User,UserPasswordsData};
|
||||
|
||||
if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
|
||||
die('Script must be called through the Misuzu route dispatcher.');
|
||||
|
@ -63,15 +63,15 @@ while($canResetPassword) {
|
|||
break;
|
||||
}
|
||||
|
||||
$passwordValidation = $msz->usersCtx->users->validatePassword($passwordNew);
|
||||
$passwordValidation = UserPasswordsData::validateUserPassword($passwordNew);
|
||||
if($passwordValidation !== '') {
|
||||
$notices[] = $msz->usersCtx->users->validatePasswordText($passwordValidation);
|
||||
$notices[] = UserPasswordsData::validateUserPasswordText($passwordValidation);
|
||||
break;
|
||||
}
|
||||
|
||||
// also disables two factor auth to prevent getting locked out of account entirely
|
||||
// this behaviour should really be replaced with recovery keys...
|
||||
$msz->usersCtx->users->updateUser($userInfo, password: $passwordNew);
|
||||
$msz->usersCtx->passwords->updateUserPassword($userInfo, $passwordNew);
|
||||
$msz->usersCtx->totps->deleteUserTotp($userInfo);
|
||||
|
||||
$msz->createAuditLog('PASSWORD_RESET', [], $userInfo);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\{User,UserPasswordsData};
|
||||
|
||||
if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
|
||||
die('Script must be called through the Misuzu route dispatcher.');
|
||||
|
@ -61,11 +61,11 @@ while(!empty($register)) {
|
|||
$notices[] = $msz->usersCtx->users->validateEMailAddressText($emailValidation);
|
||||
|
||||
if($register['password_confirm'] !== $register['password'])
|
||||
$notices[] = 'The given passwords don\'t match.';
|
||||
$notices[] = "The given passwords don't match.";
|
||||
|
||||
$passwordValidation = $msz->usersCtx->users->validatePassword($register['password']);
|
||||
$passwordValidation = UserPasswordsData::validateUserPassword($register['password']);
|
||||
if($passwordValidation !== '')
|
||||
$notices[] = $msz->usersCtx->users->validatePasswordText($passwordValidation);
|
||||
$notices[] = UserPasswordsData::validateUserPasswordText($passwordValidation);
|
||||
|
||||
if(!empty($notices))
|
||||
break;
|
||||
|
@ -75,12 +75,12 @@ while(!empty($register)) {
|
|||
try {
|
||||
$userInfo = $msz->usersCtx->users->createUser(
|
||||
$register['username'],
|
||||
$register['password'],
|
||||
$register['email'],
|
||||
$ipAddress,
|
||||
$countryCode,
|
||||
$defaultRoleInfo
|
||||
);
|
||||
$msz->usersCtx->passwords->updateUserPassword($userInfo, $register['password']);
|
||||
} catch(RuntimeException $ex) {
|
||||
$notices[] = 'Something went wrong while creating your account, please alert an administrator or a developer about this!';
|
||||
break;
|
||||
|
|
|
@ -5,7 +5,7 @@ use RuntimeException;
|
|||
use Index\Colour\Colour;
|
||||
use Misuzu\Perm;
|
||||
use Misuzu\Auth\AuthTokenCookie;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\{User,UserPasswordsData};
|
||||
|
||||
if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
|
||||
die('Script must be called through the Misuzu route dispatcher.');
|
||||
|
@ -190,13 +190,13 @@ if(CSRF::validateRequest() && $canEdit) {
|
|||
if($passwordNewValue !== $passwordConfirmValue)
|
||||
$notices[] = 'Confirm password does not match.';
|
||||
else {
|
||||
$passwordValidation = $msz->usersCtx->users->validatePassword($passwordNewValue);
|
||||
$passwordValidation = UserPasswordsData::validateUserPassword($passwordNewValue);
|
||||
if($passwordValidation !== '')
|
||||
$notices[] = $msz->usersCtx->users->validatePasswordText($passwordValidation);
|
||||
$notices[] = UserPasswordsData::validateUserPasswordText($passwordValidation);
|
||||
}
|
||||
|
||||
if(empty($notices))
|
||||
$msz->usersCtx->users->updateUser(userInfo: $userInfo, password: $passwordNewValue);
|
||||
$msz->usersCtx->passwords->updateUserPassword($userInfo, $passwordNewValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\{User,UserPasswordsData};
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
|
||||
|
@ -50,7 +50,7 @@ if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
|
|||
|
||||
if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $msz->usersCtx->totps->hasUserTotp($userInfo) !== (bool)$_POST['tfa']['enable']) {
|
||||
if((bool)$_POST['tfa']['enable']) {
|
||||
$totpInfo = $msz->usersCtx->totps->createUserTotp($userInfo);
|
||||
$totpInfo = $msz->usersCtx->totps->updateUserTotp($userInfo);
|
||||
$totpSecret = $totpInfo->encodedSecret;
|
||||
$totpIssuer = $msz->siteInfo->name;
|
||||
$totpQrcode = (new QRCode(new QROptions([
|
||||
|
@ -74,7 +74,7 @@ if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $msz->usersCtx->totps
|
|||
}
|
||||
|
||||
if($isVerifiedRequest && !empty($_POST['current_password'])) {
|
||||
if(!$userInfo->verifyPassword($_POST['current_password'] ?? '')) {
|
||||
if(!$msz->usersCtx->passwords->getUserPassword($userInfo)?->verifyPassword($_POST['current_password'] ?? '')) {
|
||||
$errors[] = 'Your password was incorrect.';
|
||||
} else {
|
||||
// Changing e-mail
|
||||
|
@ -100,12 +100,12 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) {
|
|||
if(empty($_POST['password']['confirm']) || $_POST['password']['new'] !== $_POST['password']['confirm']) {
|
||||
$errors[] = 'The new passwords you entered did not match each other.';
|
||||
} else {
|
||||
$checkPassword = $msz->usersCtx->users->validatePassword($_POST['password']['new']);
|
||||
$checkPassword = UserPasswordsData::validateUserPassword($_POST['password']['new']);
|
||||
|
||||
if($checkPassword !== '') {
|
||||
$errors[] = $msz->usersCtx->users->validatePasswordText($checkPassword);
|
||||
$errors[] = UserPasswordsData::validateUserPasswordText($checkPassword);
|
||||
} else {
|
||||
$msz->usersCtx->users->updateUser(userInfo: $userInfo, password: $_POST['password']['new']);
|
||||
$msz->usersCtx->passwords->updateUserPassword($userInfo, $_POST['password']['new']);
|
||||
$msz->createAuditLog('PERSONAL_PASSWORD_CHANGE');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ $userInfo = $msz->authInfo->userInfo;
|
|||
|
||||
if(isset($_POST['action']) && is_string($_POST['action'])) {
|
||||
if(isset($_POST['password']) && is_string($_POST['password'])
|
||||
&& ($userInfo->verifyPassword($_POST['password'] ?? ''))) {
|
||||
&& ($msz->usersCtx->passwords->getUserPassword($userInfo)?->verifyPassword($_POST['password'] ?? ''))) {
|
||||
switch($_POST['action']) {
|
||||
case 'data':
|
||||
$msz->createAuditLog('PERSONAL_DATA_DOWNLOAD');
|
||||
|
@ -151,9 +151,10 @@ if(isset($_POST['action']) && is_string($_POST['action'])) {
|
|||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'profile_backgrounds', ['user_id:s', 'bg_attach:s', 'bg_blend:i', 'bg_slide:i']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'profile_fields_values', ['field_id:s', 'user_id:s', 'format_id:s', 'field_value:s']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'sessions', ['session_id:s', 'user_id:s', 'session_key:n', 'session_remote_addr_first:a', 'session_remote_addr_last:a:n', 'session_user_agent:s', 'session_country:s', 'session_expires:t', 'session_expires_bump:b', 'session_created:t', 'session_active:t:n']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users', ['user_id:s', 'user_name:s', 'user_password:n', 'user_email:s', 'user_remote_addr_first:a', 'user_remote_addr_last:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_title:s:n', 'user_display_role_id:s:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users', ['user_id:s', 'user_name:s', 'user_email:s', 'user_remote_addr_first:a', 'user_remote_addr_last:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_title:s:n', 'user_display_role_id:s:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_bans', ['ban_id:s', 'user_id:s', 'mod_id:n', 'ban_severity:i', 'ban_reason_public:s', 'ban_reason_private:s', 'ban_created:t', 'ban_expires:t:n']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_birthdates', ['user_id:s', 'birth_year:i:n', 'birth_month:i', 'birth_day:i']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_passwords', ['user_id:s', 'password_hash:n', 'password_created:t']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_password_resets', ['reset_id:s', 'user_id:s', 'reset_remote_addr:a', 'reset_requested:t', 'reset_code:n']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_roles', ['user_id:s', 'role_id:s']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_totp', ['user_id:s', 'totp_secret:n', 'totp_created:t']);
|
||||
|
|
|
@ -9,7 +9,6 @@ class UserInfo {
|
|||
public function __construct(
|
||||
public private(set) string $id,
|
||||
public private(set) string $name,
|
||||
#[\SensitiveParameter] private ?string $passwordHash,
|
||||
#[\SensitiveParameter] public private(set) string $emailAddress,
|
||||
public private(set) string $registerRemoteAddress,
|
||||
public private(set) string $lastRemoteAddress,
|
||||
|
@ -27,33 +26,20 @@ class UserInfo {
|
|||
return new UserInfo(
|
||||
id: $result->getString(0),
|
||||
name: $result->getString(1),
|
||||
passwordHash: $result->getStringOrNull(2),
|
||||
emailAddress: $result->getString(3),
|
||||
registerRemoteAddress: $result->getString(4),
|
||||
lastRemoteAddress: $result->getStringOrNull(5),
|
||||
super: $result->getBoolean(6),
|
||||
countryCode: $result->getString(7),
|
||||
colourRaw: $result->getIntegerOrNull(8),
|
||||
title: $result->getString(9),
|
||||
displayRoleId: $result->getStringOrNull(10),
|
||||
createdTime: $result->getInteger(11),
|
||||
lastActiveTime: $result->getIntegerOrNull(12),
|
||||
deletedTime: $result->getIntegerOrNull(13),
|
||||
emailAddress: $result->getString(2),
|
||||
registerRemoteAddress: $result->getString(3),
|
||||
lastRemoteAddress: $result->getStringOrNull(4),
|
||||
super: $result->getBoolean(5),
|
||||
countryCode: $result->getString(6),
|
||||
colourRaw: $result->getIntegerOrNull(7),
|
||||
title: $result->getString(8),
|
||||
displayRoleId: $result->getStringOrNull(9),
|
||||
createdTime: $result->getInteger(10),
|
||||
lastActiveTime: $result->getIntegerOrNull(11),
|
||||
deletedTime: $result->getIntegerOrNull(12),
|
||||
);
|
||||
}
|
||||
|
||||
public bool $hasPasswordHash {
|
||||
get => $this->passwordHash !== null && $this->passwordHash !== '';
|
||||
}
|
||||
|
||||
public bool $passwordNeedsRehash {
|
||||
get => $this->hasPasswordHash && UsersData::passwordNeedsRehash($this->passwordHash);
|
||||
}
|
||||
|
||||
public function verifyPassword(string $password): bool {
|
||||
return $this->hasPasswordHash && password_verify($password, $this->passwordHash);
|
||||
}
|
||||
|
||||
public bool $hasColour {
|
||||
get => $this->colourRaw !== null && ($this->colourRaw & 0x40000000) === 0;
|
||||
}
|
||||
|
|
33
src/Users/UserPasswordInfo.php
Normal file
33
src/Users/UserPasswordInfo.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
namespace Misuzu\Users;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Index\Db\DbResult;
|
||||
|
||||
class UserPasswordInfo {
|
||||
public function __construct(
|
||||
public private(set) string $userId,
|
||||
#[\SensitiveParameter] private string $hash,
|
||||
public private(set) int $createdTime
|
||||
) {}
|
||||
|
||||
public static function fromResult(DbResult $result): UserPasswordInfo {
|
||||
return new UserPasswordInfo(
|
||||
userId: $result->getString(0),
|
||||
hash: $result->getString(1),
|
||||
createdTime: $result->getInteger(2),
|
||||
);
|
||||
}
|
||||
|
||||
public bool $needsRehash {
|
||||
get => UserPasswordsData::passwordNeedsRehash($this->hash);
|
||||
}
|
||||
|
||||
public CarbonImmutable $createdAt {
|
||||
get => CarbonImmutable::createFromTimestampUTC($this->createdTime);
|
||||
}
|
||||
|
||||
public function verifyPassword(#[\SensitiveParameter] string $password): bool {
|
||||
return password_verify($password, $this->hash);
|
||||
}
|
||||
}
|
80
src/Users/UserPasswordsData.php
Normal file
80
src/Users/UserPasswordsData.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
namespace Misuzu\Users;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\XString;
|
||||
use Index\Db\{DbConnection,DbStatementCache};
|
||||
|
||||
class UserPasswordsData {
|
||||
public const string PASSWORD_ALGO = PASSWORD_ARGON2ID;
|
||||
public const array PASSWORD_OPTS = [];
|
||||
public const int PASSWORD_UNIQUE = 6;
|
||||
|
||||
private DbStatementCache $cache;
|
||||
|
||||
public function __construct(DbConnection $dbConn) {
|
||||
$this->cache = new DbStatementCache($dbConn);
|
||||
}
|
||||
|
||||
public static function passwordHash(#[\SensitiveParameter] string $password): string {
|
||||
return password_hash($password, self::PASSWORD_ALGO, self::PASSWORD_OPTS);
|
||||
}
|
||||
|
||||
public static function passwordNeedsRehash(#[\SensitiveParameter] string $passwordHash): bool {
|
||||
return password_needs_rehash($passwordHash, self::PASSWORD_ALGO, self::PASSWORD_OPTS);
|
||||
}
|
||||
|
||||
public function getUserPassword(UserInfo|string $userInfo): ?UserPasswordInfo {
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
SELECT user_id, password_hash, UNIX_TIMESTAMP(password_created)
|
||||
FROM msz_users_passwords
|
||||
WHERE user_id = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
return $result->next() ? UserPasswordInfo::fromResult($result) : null;
|
||||
}
|
||||
|
||||
public function deleteUserPassword(UserInfo|string $userInfo): void {
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
DELETE FROM msz_users_passwords
|
||||
WHERE user_id = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function updateUserPassword(UserInfo|string $userInfo, #[\SensitiveParameter] string $password): UserPasswordInfo {
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
REPLACE INTO msz_users_passwords (
|
||||
user_id, password_hash
|
||||
) VALUES (?, ?)
|
||||
SQL);
|
||||
$stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
|
||||
$stmt->nextParameter(self::passwordHash($password));
|
||||
$stmt->execute();
|
||||
|
||||
$pwInfo = $this->getUserPassword($userInfo);
|
||||
if($pwInfo === null)
|
||||
throw new RuntimeException('failed to create password');
|
||||
|
||||
return $pwInfo;
|
||||
}
|
||||
|
||||
public static function validateUserPassword(string $password): string {
|
||||
if(XString::countUnique($password) < self::PASSWORD_UNIQUE)
|
||||
return 'weak';
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function validateUserPasswordText(string $error): string {
|
||||
return match($error) {
|
||||
'weak' => sprintf("Your password is too weak, it must contain at least %d unique characters.", self::PASSWORD_UNIQUE),
|
||||
'' => 'Your password is strong enough, why are you seeing this?',
|
||||
default => 'Your password is not acceptable.',
|
||||
};
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ class UserTotpsData {
|
|||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function createUserTotp(UserInfo|string $userInfo): UserTotpInfo {
|
||||
public function updateUserTotp(UserInfo|string $userInfo): UserTotpInfo {
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
REPLACE INTO msz_users_totp (
|
||||
user_id, totp_secret
|
||||
|
|
|
@ -10,6 +10,7 @@ class UsersContext {
|
|||
public private(set) BansData $bans;
|
||||
public private(set) WarningsData $warnings;
|
||||
public private(set) ModNotesData $modNotes;
|
||||
public private(set) UserPasswordsData $passwords;
|
||||
public private(set) UserTotpsData $totps;
|
||||
public private(set) UserBirthdatesData $birthdates;
|
||||
|
||||
|
@ -31,6 +32,7 @@ class UsersContext {
|
|||
$this->bans = new BansData($dbConn);
|
||||
$this->warnings = new WarningsData($dbConn);
|
||||
$this->modNotes = new ModNotesData($dbConn);
|
||||
$this->passwords = new UserPasswordsData($dbConn);
|
||||
$this->totps = new UserTotpsData($dbConn);
|
||||
$this->birthdates = new UserBirthdatesData($dbConn);
|
||||
}
|
||||
|
|
|
@ -16,20 +16,8 @@ class UsersData {
|
|||
$this->cache = new DbStatementCache($dbConn);
|
||||
}
|
||||
|
||||
public const NAME_MIN_LENGTH = 3;
|
||||
public const NAME_MAX_LENGTH = 16;
|
||||
|
||||
public const PASSWORD_ALGO = PASSWORD_ARGON2ID;
|
||||
public const PASSWORD_OPTS = [];
|
||||
public const PASSWORD_UNIQUE = 6;
|
||||
|
||||
public static function passwordHash(string $password): string {
|
||||
return password_hash($password, self::PASSWORD_ALGO, self::PASSWORD_OPTS);
|
||||
}
|
||||
|
||||
public static function passwordNeedsRehash(string $passwordHash): bool {
|
||||
return password_needs_rehash($passwordHash, self::PASSWORD_ALGO, self::PASSWORD_OPTS);
|
||||
}
|
||||
public const int NAME_MIN_LENGTH = 3;
|
||||
public const int NAME_MAX_LENGTH = 16;
|
||||
|
||||
public function countUsers(
|
||||
RoleInfo|string|null $roleInfo = null,
|
||||
|
@ -165,7 +153,7 @@ class UsersData {
|
|||
|
||||
$args = 0;
|
||||
$query = <<<SQL
|
||||
SELECT u.user_id, u.user_name, u.user_password, u.user_email,
|
||||
SELECT u.user_id, u.user_name, u.user_email,
|
||||
INET6_NTOA(u.user_remote_addr_first), INET6_NTOA(u.user_remote_addr_last),
|
||||
u.user_super, u.user_country, u.user_colour,
|
||||
u.user_title, u.user_display_role_id,
|
||||
|
@ -271,7 +259,7 @@ class UsersData {
|
|||
|
||||
$args = 0;
|
||||
$query = <<<SQL
|
||||
SELECT user_id, user_name, user_password, user_email,
|
||||
SELECT user_id, user_name, user_email,
|
||||
INET6_NTOA(user_remote_addr_first), INET6_NTOA(user_remote_addr_last),
|
||||
user_super, user_country, user_colour,
|
||||
user_title, user_display_role_id,
|
||||
|
@ -307,7 +295,6 @@ class UsersData {
|
|||
|
||||
public function createUser(
|
||||
string $name,
|
||||
string $password,
|
||||
string $email,
|
||||
string $remoteAddr,
|
||||
string $countryCode,
|
||||
|
@ -318,16 +305,13 @@ class UsersData {
|
|||
elseif($displayRoleInfo === null)
|
||||
$displayRoleInfo = RolesData::DEFAULT_ROLE;
|
||||
|
||||
$password = self::passwordHash($password);
|
||||
|
||||
if(self::validateName($name, true) !== '')
|
||||
throw new InvalidArgumentException('$name is not a valid user name.');
|
||||
if(self::validateEMailAddress($email, true) !== '')
|
||||
throw new InvalidArgumentException('$email is not a valid e-mail address.');
|
||||
|
||||
$stmt = $this->cache->get('INSERT INTO msz_users (user_name, user_password, user_email, user_remote_addr_first, user_remote_addr_last, user_country, user_display_role_id) VALUES (?, ?, ?, INET6_ATON(?), INET6_ATON(?), ?, ?)');
|
||||
$stmt = $this->cache->get('INSERT INTO msz_users (user_name, user_email, user_remote_addr_first, user_remote_addr_last, user_country, user_display_role_id) VALUES (?, ?, INET6_ATON(?), INET6_ATON(?), ?, ?)');
|
||||
$stmt->nextParameter($name);
|
||||
$stmt->nextParameter($password);
|
||||
$stmt->nextParameter($email);
|
||||
$stmt->nextParameter($remoteAddr);
|
||||
$stmt->nextParameter($remoteAddr);
|
||||
|
@ -342,7 +326,6 @@ class UsersData {
|
|||
UserInfo|string $userInfo,
|
||||
?string $name = null,
|
||||
?string $emailAddr = null,
|
||||
?string $password = null,
|
||||
?string $countryCode = null,
|
||||
?Colour $colour = null,
|
||||
RoleInfo|string|null $displayRoleInfo = null,
|
||||
|
@ -372,11 +355,6 @@ class UsersData {
|
|||
$values[] = $emailAddr;
|
||||
}
|
||||
|
||||
if($password !== null) {
|
||||
$fields[] = 'password = ?';
|
||||
$values[] = $password === '' ? null : self::passwordHash($password);
|
||||
}
|
||||
|
||||
if($countryCode !== null) {
|
||||
$fields[] = 'user_country = ?';
|
||||
$values[] = $countryCode;
|
||||
|
@ -643,19 +621,4 @@ class UsersData {
|
|||
default => 'Your e-mail address is not correctly formatted.',
|
||||
};
|
||||
}
|
||||
|
||||
public static function validatePassword(string $password): string {
|
||||
if(XString::countUnique($password) < self::PASSWORD_UNIQUE)
|
||||
return 'weak';
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function validatePasswordText(string $error): string {
|
||||
return match($error) {
|
||||
'weak' => sprintf("Your password is too weak, it must contain at least %d unique characters.", self::PASSWORD_UNIQUE),
|
||||
'' => 'Your password is strong enough, why are you seeing this?',
|
||||
default => 'Your password is not acceptable.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue