Basic structure for user tables.
This commit is contained in:
parent
5e35ccaf55
commit
d9e175699e
21 changed files with 747 additions and 11 deletions
104
database/2023_01_09_213225_create_users_tables.php
Normal file
104
database/2023_01_09_213225_create_users_tables.php
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
use Index\Data\Migration\IDbMigration;
|
||||||
|
|
||||||
|
final class CreateUsersTables_20230109_213225 implements IDbMigration {
|
||||||
|
public function migrate(IDbConnection $conn): void {
|
||||||
|
$conn->execute('
|
||||||
|
CREATE TABLE hau_users (
|
||||||
|
user_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
user_name VARCHAR(255) NOT NULL COLLATE \'ascii_general_ci\',
|
||||||
|
user_country CHAR(2) NOT NULL DEFAULT \'XX\' COLLATE \'ascii_general_ci\',
|
||||||
|
user_colour INT(10) UNSIGNED NULL DEFAULT NULL,
|
||||||
|
user_super TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',
|
||||||
|
user_time_zone VARBINARY(255) NOT NULL DEFAULT \'UTC\',
|
||||||
|
user_created TIMESTAMP NOT NULL DEFAULT current_timestamp(),
|
||||||
|
user_updated TIMESTAMP NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
user_deleted TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (user_id) USING BTREE,
|
||||||
|
UNIQUE INDEX hau_users_name_unique (user_name) USING BTREE,
|
||||||
|
INDEX hau_users_created_index (user_created) USING BTREE,
|
||||||
|
INDEX hau_users_deleted_index (user_deleted) USING BTREE
|
||||||
|
) ENGINE=InnoDB COLLATE=utf8mb4_bin;
|
||||||
|
');
|
||||||
|
|
||||||
|
$conn->execute('
|
||||||
|
CREATE TABLE hau_users_auth (
|
||||||
|
user_id INT(10) UNSIGNED NOT NULL,
|
||||||
|
user_auth_type VARCHAR(32) NOT NULL COLLATE \'ascii_general_ci\',
|
||||||
|
user_auth_enabled TIMESTAMP NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (user_id, user_auth_type) USING BTREE,
|
||||||
|
INDEX hau_users_auth_user_foreign (user_id) USING BTREE,
|
||||||
|
CONSTRAINT hau_users_auth_user_foreign
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES hau_users (user_id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB COLLATE=utf8mb4_bin;
|
||||||
|
');
|
||||||
|
|
||||||
|
$conn->execute('
|
||||||
|
CREATE TABLE hau_users_backup (
|
||||||
|
user_id INT(10) UNSIGNED NOT NULL,
|
||||||
|
user_backup_code BINARY(8) NOT NULL,
|
||||||
|
user_backup_created TIMESTAMP NOT NULL DEFAULT current_timestamp(),
|
||||||
|
user_backup_used TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (user_id, user_backup_code) USING BTREE,
|
||||||
|
INDEX hau_users_backup_used_index (user_backup_used) USING BTREE,
|
||||||
|
CONSTRAINT hau_users_backup_user_foreign
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES hau_users (user_id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB COLLATE=utf8mb4_bin;
|
||||||
|
');
|
||||||
|
|
||||||
|
$conn->execute('
|
||||||
|
CREATE TABLE hau_users_emails (
|
||||||
|
user_email_address VARCHAR(255) NOT NULL COLLATE \'ascii_general_ci\',
|
||||||
|
user_id INT(10) UNSIGNED NOT NULL,
|
||||||
|
user_email_created TIMESTAMP NOT NULL DEFAULT current_timestamp(),
|
||||||
|
user_email_verified TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
user_email_recovery TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',
|
||||||
|
PRIMARY KEY (user_email_address) USING BTREE,
|
||||||
|
UNIQUE INDEX hau_users_emails_user_foreign (user_id) USING BTREE,
|
||||||
|
INDEX hau_users_emails_created_index (user_email_created) USING BTREE,
|
||||||
|
INDEX hau_users_emails_recovery_index (user_email_recovery) USING BTREE,
|
||||||
|
INDEX hau_users_emails_verified_index (user_email_verified) USING BTREE,
|
||||||
|
CONSTRAINT hau_users_emails_user_foreign
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES hau_users (user_id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB COLLATE=utf8mb4_bin;
|
||||||
|
');
|
||||||
|
|
||||||
|
$conn->execute('
|
||||||
|
CREATE TABLE hau_users_passwords (
|
||||||
|
user_id INT(10) UNSIGNED NOT NULL,
|
||||||
|
user_password_hash VARBINARY(255) NOT NULL,
|
||||||
|
user_password_changed TIMESTAMP NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
UNIQUE INDEX hau_users_passwords_user_foreign (user_id) USING BTREE,
|
||||||
|
CONSTRAINT hau_users_passwords_user_foreign
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES hau_users (user_id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB COLLATE=utf8mb4_bin;
|
||||||
|
');
|
||||||
|
|
||||||
|
$conn->execute('
|
||||||
|
CREATE TABLE hau_users_totp (
|
||||||
|
user_id INT(10) UNSIGNED NOT NULL,
|
||||||
|
user_totp_key BINARY(26) NOT NULL,
|
||||||
|
user_totp_changed TIMESTAMP NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
UNIQUE INDEX hau_users_totp_user_foreign (user_id) USING BTREE,
|
||||||
|
CONSTRAINT hau_users_totp_user_foreign
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES hau_users (user_id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB COLLATE=utf8mb4_bin;
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
10
hanyuu.php
10
hanyuu.php
|
@ -3,6 +3,8 @@ namespace Hanyuu;
|
||||||
|
|
||||||
use Index\Autoloader;
|
use Index\Autoloader;
|
||||||
use Index\Environment;
|
use Index\Environment;
|
||||||
|
use Index\Data\DbTools;
|
||||||
|
use Hanyuu\Config\IConfig;
|
||||||
use Hanyuu\Config\ArrayConfig;
|
use Hanyuu\Config\ArrayConfig;
|
||||||
|
|
||||||
define('HAU_STARTUP', microtime(true));
|
define('HAU_STARTUP', microtime(true));
|
||||||
|
@ -44,5 +46,9 @@ set_exception_handler(function(\Throwable $ex) {
|
||||||
die('<h2>Hanyuu is sad.</h2>');
|
die('<h2>Hanyuu is sad.</h2>');
|
||||||
});
|
});
|
||||||
|
|
||||||
$hau = new HanyuuContext(ArrayConfig::open(HAU_DIR_CONFIG . '/config.ini'));
|
$cfg = ArrayConfig::open(HAU_DIR_CONFIG . '/config.ini');
|
||||||
$hau->connectDb();
|
|
||||||
|
$dbc = DbTools::create($cfg->getValue('database:dsn', IConfig::T_STR, 'null'));
|
||||||
|
$dbc->execute('SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';');
|
||||||
|
|
||||||
|
$hau = new HanyuuContext($cfg, $dbc);
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
namespace Hanyuu;
|
namespace Hanyuu;
|
||||||
|
|
||||||
use Index\Data\IDbConnection;
|
use Index\Data\IDbConnection;
|
||||||
use Index\Data\DbTools;
|
|
||||||
use Index\Data\Migration\IDbMigrationRepo;
|
use Index\Data\Migration\IDbMigrationRepo;
|
||||||
use Index\Data\Migration\DbMigrationManager;
|
use Index\Data\Migration\DbMigrationManager;
|
||||||
use Index\Data\Migration\FsDbMigrationRepo;
|
use Index\Data\Migration\FsDbMigrationRepo;
|
||||||
|
@ -11,29 +10,30 @@ use Index\Http\HttpRequest;
|
||||||
use Index\Routing\IRouter;
|
use Index\Routing\IRouter;
|
||||||
use Hanyuu\Config\IConfig;
|
use Hanyuu\Config\IConfig;
|
||||||
use Hanyuu\Templating\TemplateContext;
|
use Hanyuu\Templating\TemplateContext;
|
||||||
|
use Hanyuu\Users\IUsers;
|
||||||
|
use Hanyuu\Users\Db\DbUsers;
|
||||||
|
|
||||||
class HanyuuContext {
|
class HanyuuContext {
|
||||||
private const DB_INIT = 'SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';';
|
|
||||||
|
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
private IDbConnection $dbConn;
|
private IDbConnection $dbConn;
|
||||||
|
private IUsers $users;
|
||||||
private ?TemplateContext $tpl = null;
|
private ?TemplateContext $tpl = null;
|
||||||
|
|
||||||
public function __construct(IConfig $config) {
|
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
$this->dbConn = $dbConn;
|
||||||
|
$this->users = new DbUsers($dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSiteName(): string {
|
public function getSiteName(): string {
|
||||||
return $this->config->getValue('site:name', IConfig::T_STR, 'Hanyuu');
|
return $this->config->getValue('site:name', IConfig::T_STR, 'Hanyuu');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connectDb(?IDbConnection $dbConn = null): void {
|
public function getUsers(): IUsers {
|
||||||
$dbConn ??= DbTools::create($this->config->getValue('database:dsn', IConfig::T_STR, 'null'));
|
return $this->users;
|
||||||
$dbConn->execute(self::DB_INIT);
|
|
||||||
$this->dbConn = $dbConn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDb(): IDbConnection {
|
public function getDatabase(): IDbConnection {
|
||||||
return $this->dbConn;
|
return $this->dbConn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,5 +106,26 @@ class HanyuuContext {
|
||||||
$this->router->get('/', function($response, $request) {
|
$this->router->get('/', function($response, $request) {
|
||||||
return 503;
|
return 503;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(!HAU_DEBUG)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$this->router->get('/test', function() {
|
||||||
|
$users = $this->getUsers();
|
||||||
|
$userInfo = $users->getUserInfoByName('flAsH');
|
||||||
|
|
||||||
|
$ffa = $users->getUserFirstFactorAuthInfos($userInfo);
|
||||||
|
$tfa = $users->getUserSecondFactorAuthInfos($userInfo);
|
||||||
|
|
||||||
|
$pwdInfo = $users->getUserPasswordInfo($userInfo);
|
||||||
|
|
||||||
|
$totpInfo = $users->getUserTOTPInfo($userInfo);
|
||||||
|
$totpGen = $totpInfo->createGenerator();
|
||||||
|
|
||||||
|
$emailInfos = $users->getUserEMailInfos($userInfo);
|
||||||
|
|
||||||
|
$backupInfos = $users->getUserBackupInfos($userInfo);
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
src/OTP/IOTPGenerator.php
Normal file
6
src/OTP/IOTPGenerator.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\OTP;
|
||||||
|
|
||||||
|
interface IOTPGenerator {
|
||||||
|
public function generate(): string;
|
||||||
|
}
|
74
src/OTP/TOTPGenerator.php
Normal file
74
src/OTP/TOTPGenerator.php
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\OTP;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Index\Serialisation\Serialiser;
|
||||||
|
|
||||||
|
class TOTPGenerator implements IOTPGenerator {
|
||||||
|
private const DIGITS = 6;
|
||||||
|
private const INTERVAL = 30;
|
||||||
|
private const HASH_ALGO = 'sha1';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private string $secretKey,
|
||||||
|
private int $digits = self::DIGITS,
|
||||||
|
private int $interval = self::INTERVAL,
|
||||||
|
private string $hashAlgo = self::HASH_ALGO
|
||||||
|
) {
|
||||||
|
if(empty($this->secretKey))
|
||||||
|
throw new InvalidArgumentException('$secretKey may not be empty');
|
||||||
|
if($this->digits < 1)
|
||||||
|
throw new InvalidArgumentException('$digits must be a positive integer');
|
||||||
|
if($this->interval < 1)
|
||||||
|
throw new InvalidArgumentException('$interval must be a positive integer');
|
||||||
|
if(!in_array($this->hashAlgo, hash_hmac_algos(), true))
|
||||||
|
throw new InvalidArgumentException('$hashAlgo must be a hashing algorithm suitable for hmac');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDigits(): int {
|
||||||
|
return $this->digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInterval(): int {
|
||||||
|
return $this->interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHashAlgo(): string {
|
||||||
|
return $this->hashAlgo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimeCode(int $offset = 0, int $timeStamp = -1): int {
|
||||||
|
if($timeStamp < 0)
|
||||||
|
$timeStamp = time();
|
||||||
|
|
||||||
|
// use -1 and 1 to get the previous and next token for user convenience
|
||||||
|
if($offset !== 0)
|
||||||
|
$timeStamp += $this->interval * $offset;
|
||||||
|
|
||||||
|
return (int)(($timeStamp * 1000) / ($this->interval * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(int $offset = 0, int $timeStamp = -1): string {
|
||||||
|
$timeCode = pack('J', $this->getTimeCode($offset, $timeStamp));
|
||||||
|
$secretKey = Serialiser::base32()->deserialise($this->secretKey);
|
||||||
|
$hash = hash_hmac($this->hashAlgo, $timeCode, $secretKey, true);
|
||||||
|
|
||||||
|
$offset = ord($hash[strlen($hash) - 1]) & 0x0F;
|
||||||
|
|
||||||
|
$bin = (ord($hash[$offset]) & 0x7F) << 24;
|
||||||
|
$bin |= (ord($hash[$offset + 1]) & 0xFF) << 16;
|
||||||
|
$bin |= (ord($hash[$offset + 2]) & 0xFF) << 8;
|
||||||
|
$bin |= ord($hash[$offset + 3]) & 0xFF;
|
||||||
|
|
||||||
|
$otp = (string)($bin % pow(10, $this->digits));
|
||||||
|
|
||||||
|
return str_pad($otp, $this->digits, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateKey(int $bytes = 16): string {
|
||||||
|
if($length < 1)
|
||||||
|
throw new InvalidArgumentException('$bytes must be a positive integer');
|
||||||
|
|
||||||
|
return Serialiser::base32()->serialise(random_bytes($bytes));
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,9 @@ namespace Hanyuu\Templating;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
|
// this entire thing needs to be redone and integrated into Index
|
||||||
|
// take this project as an opportunity to do that
|
||||||
|
|
||||||
class Template {
|
class Template {
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private TemplateContext $context,
|
private TemplateContext $context,
|
||||||
|
|
30
src/Users/Db/DbUserAuthInfo.php
Normal file
30
src/Users/Db/DbUserAuthInfo.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users\Db;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Hanyuu\Users\IUserAuthInfo;
|
||||||
|
|
||||||
|
class DbUserAuthInfo implements IUserAuthInfo {
|
||||||
|
private string $userId;
|
||||||
|
private string $type;
|
||||||
|
private DateTime $enabled;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->userId = $result->getString(0);
|
||||||
|
$this->type = $result->getString(1);
|
||||||
|
$this->enabled = DateTime::fromUnixTimeSeconds($result->getInteger(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string {
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEnabledTime(): DateTime {
|
||||||
|
return $this->enabled;
|
||||||
|
}
|
||||||
|
}
|
42
src/Users/Db/DbUserBackupInfo.php
Normal file
42
src/Users/Db/DbUserBackupInfo.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users\Db;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Hanyuu\Users\IUserBackupInfo;
|
||||||
|
|
||||||
|
class DbUserBackupInfo implements IUserBackupInfo {
|
||||||
|
private string $userId;
|
||||||
|
private string $code;
|
||||||
|
private DateTime $created;
|
||||||
|
private bool $isUsed;
|
||||||
|
private DateTime $used;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->userId = $result->getString(0);
|
||||||
|
$this->code = $result->getString(1);
|
||||||
|
$this->created = DateTime::fromUnixTimeSeconds($result->getInteger(2));
|
||||||
|
$this->isUsed = !$result->isNull(3);
|
||||||
|
$this->used = DateTime::fromUnixTimeSeconds($result->isNull(3) ? 0 : $result->getInteger(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): string {
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): DateTime {
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUsed(): bool {
|
||||||
|
return $this->isUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsedTime(): DateTime {
|
||||||
|
return $this->used;
|
||||||
|
}
|
||||||
|
}
|
48
src/Users/Db/DbUserEMailInfo.php
Normal file
48
src/Users/Db/DbUserEMailInfo.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users\Db;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Hanyuu\Users\IUserEMailInfo;
|
||||||
|
|
||||||
|
class DbUserEMailInfo implements IUserEMailInfo {
|
||||||
|
private string $userId;
|
||||||
|
private string $address;
|
||||||
|
private DateTime $created;
|
||||||
|
private bool $isVerified;
|
||||||
|
private DateTime $verified;
|
||||||
|
private bool $isRecovery;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->userId = $result->getString(0);
|
||||||
|
$this->address = $result->getString(1);
|
||||||
|
$this->created = DateTime::fromUnixTimeSeconds($result->getInteger(2));
|
||||||
|
$this->isVerified = !$result->isNull(3);
|
||||||
|
$this->verified = DateTime::fromUnixTimeSeconds($result->isNull(3) ? 0 : $result->getInteger(3));
|
||||||
|
$this->isRecovery = $result->getInteger(4) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAddress(): string {
|
||||||
|
return $this->address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): DateTime {
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isVerified(): bool {
|
||||||
|
return $this->isVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVerifiedTime(): DateTime {
|
||||||
|
return $this->verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRecovery(): bool {
|
||||||
|
return $this->isRecovery;
|
||||||
|
}
|
||||||
|
}
|
75
src/Users/Db/DbUserInfo.php
Normal file
75
src/Users/Db/DbUserInfo.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users\Db;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\TimeZoneInfo;
|
||||||
|
use Index\Colour\Colour;
|
||||||
|
use Index\Colour\ColourRGB;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Hanyuu\Users\IUserInfo;
|
||||||
|
|
||||||
|
class DbUserInfo implements IUserInfo {
|
||||||
|
private string $id;
|
||||||
|
private string $name;
|
||||||
|
private string $country;
|
||||||
|
private Colour $colour;
|
||||||
|
private bool $isSuper;
|
||||||
|
private TimeZoneInfo $timeZone;
|
||||||
|
private DateTime $created;
|
||||||
|
private DateTime $updated;
|
||||||
|
private bool $isDeleted;
|
||||||
|
private DateTime $deleted;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->id = $result->getString(0);
|
||||||
|
$this->name = $result->getString(1);
|
||||||
|
$this->country = $result->getString(2);
|
||||||
|
$this->colour = $result->isNull(3) ? Colour::none() : ColourRGB::fromRawRGB($result->getInteger(3));
|
||||||
|
$this->isSuper = $result->getInteger(4) !== 0;
|
||||||
|
$this->timeZone = new TimeZoneInfo($result->getString(5));
|
||||||
|
$this->created = DateTime::fromUnixTimeSeconds($result->getInteger(6));
|
||||||
|
$this->updated = DateTime::fromUnixTimeSeconds($result->getInteger(7));
|
||||||
|
$this->isDeleted = !$result->isNull(8);
|
||||||
|
$this->deleted = DateTime::fromUnixTimeSeconds($result->isNull(8) ? 0 : $result->getInteger(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCountryCode(): string {
|
||||||
|
return $this->country;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColour(): Colour {
|
||||||
|
return $this->colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSuper(): bool {
|
||||||
|
return $this->isSuper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimeZone(): TimeZoneInfo {
|
||||||
|
return $this->timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): DateTime {
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedTime(): DateTime {
|
||||||
|
return $this->updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDeleted(): bool {
|
||||||
|
return $this->isDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeletedTime(): DateTime {
|
||||||
|
return $this->deleted;
|
||||||
|
}
|
||||||
|
}
|
38
src/Users/Db/DbUserPasswordInfo.php
Normal file
38
src/Users/Db/DbUserPasswordInfo.php
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users\Db;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Hanyuu\Users\IUserPasswordInfo;
|
||||||
|
|
||||||
|
class DbUserPasswordInfo implements IUserPasswordInfo {
|
||||||
|
private string $userId;
|
||||||
|
private string $hash;
|
||||||
|
private DateTime $changed;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->userId = $result->getString(0);
|
||||||
|
$this->hash = $result->getString(1);
|
||||||
|
$this->changed = DateTime::fromUnixTimeSeconds($result->getInteger(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHash(): string {
|
||||||
|
return $this->hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChangedTime(): DateTime {
|
||||||
|
return $this->changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyPassword(string $password): bool {
|
||||||
|
return password_verify($password, $this->hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function needsRehash(string|int|null $algo, array $options = []): bool {
|
||||||
|
return password_needs_rehash($this->hash, $algo, $options);
|
||||||
|
}
|
||||||
|
}
|
35
src/Users/Db/DbUserTOTPInfo.php
Normal file
35
src/Users/Db/DbUserTOTPInfo.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users\Db;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Hanyuu\OTP\TOTPGenerator;
|
||||||
|
use Hanyuu\Users\IUserTOTPInfo;
|
||||||
|
|
||||||
|
class DbUserTOTPInfo implements IUserTOTPInfo {
|
||||||
|
private string $userId;
|
||||||
|
private string $secretKey;
|
||||||
|
private DateTime $changed;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->userId = $result->getString(0);
|
||||||
|
$this->secretKey = $result->getString(1);
|
||||||
|
$this->changed = DateTime::fromUnixTimeSeconds($result->getInteger(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): string {
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSecretKey(): string {
|
||||||
|
return $this->secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChangedTime(): DateTime {
|
||||||
|
return $this->changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createGenerator(): TOTPGenerator {
|
||||||
|
return new TOTPGenerator($this->secretKey);
|
||||||
|
}
|
||||||
|
}
|
170
src/Users/Db/DbUsers.php
Normal file
170
src/Users/Db/DbUsers.php
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users\Db;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
use Index\Data\IDbStatement;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
use Index\Data\DbType;
|
||||||
|
use Hanyuu\Users\IUsers;
|
||||||
|
use Hanyuu\Users\IUserInfo;
|
||||||
|
use Hanyuu\Users\IUserPasswordInfo;
|
||||||
|
use Hanyuu\Users\IUserTOTPInfo;
|
||||||
|
|
||||||
|
class DbUsers implements IUsers {
|
||||||
|
private const USERS_TABLE = 'hau_users';
|
||||||
|
private const AUTH_TABLE = 'hau_users_auth';
|
||||||
|
private const BACKUP_TABLE = 'hau_users_backup';
|
||||||
|
private const EMAILS_TABLE = 'hau_users_emails';
|
||||||
|
private const PASSWORDS_TABLE = 'hau_users_passwords';
|
||||||
|
private const TOTP_TABLE = 'hau_users_totp';
|
||||||
|
|
||||||
|
private const USERS_FIELDS = [
|
||||||
|
'user_id', 'user_name', 'user_country', 'user_colour', 'user_super', 'user_time_zone',
|
||||||
|
'UNIX_TIMESTAMP(user_created)', 'UNIX_TIMESTAMP(user_updated)', 'UNIX_TIMESTAMP(user_deleted)',
|
||||||
|
];
|
||||||
|
private const AUTH_FIELDS = [
|
||||||
|
'user_id', 'user_auth_type', 'UNIX_TIMESTAMP(user_auth_enabled)',
|
||||||
|
];
|
||||||
|
private const PASSWORDS_FIELDS = [
|
||||||
|
'user_id', 'user_password_hash', 'UNIX_TIMESTAMP(user_password_changed)',
|
||||||
|
];
|
||||||
|
private const TOTP_FIELDS = [
|
||||||
|
'user_id', 'user_totp_key', 'UNIX_TIMESTAMP(user_totp_changed)',
|
||||||
|
];
|
||||||
|
private const EMAILS_FIELDS = [
|
||||||
|
'user_id', 'user_email_address', 'UNIX_TIMESTAMP(user_email_created)', 'UNIX_TIMESTAMP(user_email_verified)', 'user_email_recovery',
|
||||||
|
];
|
||||||
|
private const BACKUP_FIELDS = [
|
||||||
|
'user_id', 'user_backup_code', 'UNIX_TIMESTAMP(user_backup_created)', 'UNIX_TIMESTAMP(user_backup_used)',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const FIRST_FACTOR_AUTH = ['pwd'];
|
||||||
|
private const SECOND_FACTOR_AUTH = ['totp'];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private IDbConnection $conn
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private array $statements = [];
|
||||||
|
|
||||||
|
private function getStatement(string $name, callable $query): IDbStatement {
|
||||||
|
if(array_key_exists($name, $this->statements))
|
||||||
|
return $this->statements[$name];
|
||||||
|
return $this->statements[$name] = $this->conn->prepare($query());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function fetchUserInfoSingle(IDbResult $result, string $exceptionText): IUserInfo {
|
||||||
|
if(!$result->next())
|
||||||
|
throw new RuntimeException($exceptionText);
|
||||||
|
|
||||||
|
return new DbUserInfo($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserInfoById(string $userId): IUserInfo {
|
||||||
|
$stmt = $this->getStatement('get info by id', fn() => ('SELECT ' . implode(',', self::USERS_FIELDS) . ' FROM ' . self::USERS_TABLE . ' WHERE user_id = ?'));
|
||||||
|
$stmt->reset();
|
||||||
|
$stmt->addParameter(1, $userId, DbType::STRING);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $this->fetchUserInfoSingle($stmt->getResult(), 'no user with $userId found');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserInfoByName(string $userName): IUserInfo {
|
||||||
|
$stmt = $this->getStatement('get info by name', fn() => ('SELECT ' . implode(',', self::USERS_FIELDS) . ' FROM ' . self::USERS_TABLE . ' WHERE user_name = ?'));
|
||||||
|
$stmt->reset();
|
||||||
|
$stmt->addParameter(1, $userName, DbType::STRING);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $this->fetchUserInfoSingle($stmt->getResult(), 'no user with $userName found');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function fetchAuthInfoMultiple(IDbResult $result): array {
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
while($result->next())
|
||||||
|
$array[] = new DbUserAuthInfo($result);
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserFirstFactorAuthInfos(IUserInfo $userInfo): array {
|
||||||
|
$stmt = $this->getStatement('get auth first', fn() => ('SELECT ' . implode(',', self::AUTH_FIELDS) . ' FROM ' . self::AUTH_TABLE . ' WHERE user_id = ? AND user_auth_type IN ("' . implode('", "', self::FIRST_FACTOR_AUTH) . '")'));
|
||||||
|
$stmt->reset();
|
||||||
|
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $this->fetchAuthInfoMultiple($stmt->getResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserSecondFactorAuthInfos(IUserInfo $userInfo): array {
|
||||||
|
$stmt = $this->getStatement('get auth second', fn() => ('SELECT ' . implode(',', self::AUTH_FIELDS) . ' FROM ' . self::AUTH_TABLE . ' WHERE user_id = ? AND user_auth_type IN ("' . implode('", "', self::SECOND_FACTOR_AUTH) . '")'));
|
||||||
|
$stmt->reset();
|
||||||
|
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $this->fetchAuthInfoMultiple($stmt->getResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getUserPasswordInfo(IUserInfo $userInfo): IUserPasswordInfo {
|
||||||
|
$stmt = $this->getStatement('get pwd', fn() => ('SELECT ' . implode(',', self::PASSWORDS_FIELDS) . ' FROM ' . self::PASSWORDS_TABLE . ' WHERE user_id = ?'));
|
||||||
|
$stmt->reset();
|
||||||
|
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
|
if(!$result->next())
|
||||||
|
throw new RuntimeException('no password info for $userInfo found');
|
||||||
|
|
||||||
|
return new DbUserPasswordInfo($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getUserTOTPInfo(IUserInfo $userInfo): IUserTOTPInfo {
|
||||||
|
$stmt = $this->getStatement('get totp', fn() => ('SELECT ' . implode(',', self::TOTP_FIELDS) . ' FROM ' . self::TOTP_TABLE . ' WHERE user_id = ?'));
|
||||||
|
$stmt->reset();
|
||||||
|
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
|
if(!$result->next())
|
||||||
|
throw new RuntimeException('no totp info for $userInfo found');
|
||||||
|
|
||||||
|
return new DbUserTOTPInfo($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getUserEMailInfos(IUserInfo $userInfo): array {
|
||||||
|
$stmt = $this->getStatement('get emails', fn() => ('SELECT ' . implode(',', self::EMAILS_FIELDS) . ' FROM ' . self::EMAILS_TABLE . ' WHERE user_id = ?'));
|
||||||
|
$stmt->reset();
|
||||||
|
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
while($result->next())
|
||||||
|
$array[] = new DbUserEMailInfo($result);
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getUserBackupInfos(IUserInfo $userInfo): array {
|
||||||
|
$stmt = $this->getStatement('get backups', fn() => ('SELECT ' . implode(',', self::BACKUP_FIELDS) . ' FROM ' . self::BACKUP_TABLE . ' WHERE user_id = ?'));
|
||||||
|
$stmt->reset();
|
||||||
|
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
while($result->next())
|
||||||
|
$array[] = new DbUserBackupInfo($result);
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
}
|
10
src/Users/IUserAuthInfo.php
Normal file
10
src/Users/IUserAuthInfo.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
|
||||||
|
interface IUserAuthInfo {
|
||||||
|
public function getUserId(): string;
|
||||||
|
public function getType(): string;
|
||||||
|
public function getEnabledTime(): DateTime;
|
||||||
|
}
|
12
src/Users/IUserBackupInfo.php
Normal file
12
src/Users/IUserBackupInfo.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
|
||||||
|
interface IUserBackupInfo {
|
||||||
|
public function getUserId(): string;
|
||||||
|
public function getCode(): string;
|
||||||
|
public function getCreatedTime(): DateTime;
|
||||||
|
public function isUsed(): bool;
|
||||||
|
public function getUsedTime(): DateTime;
|
||||||
|
}
|
13
src/Users/IUserEMailInfo.php
Normal file
13
src/Users/IUserEMailInfo.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
|
||||||
|
interface IUserEMailInfo {
|
||||||
|
public function getUserId(): string;
|
||||||
|
public function getAddress(): string;
|
||||||
|
public function getCreatedTime(): DateTime;
|
||||||
|
public function isVerified(): bool;
|
||||||
|
public function getVerifiedTime(): DateTime;
|
||||||
|
public function isRecovery(): bool;
|
||||||
|
}
|
19
src/Users/IUserInfo.php
Normal file
19
src/Users/IUserInfo.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\TimeZoneInfo;
|
||||||
|
use Index\Colour\Colour;
|
||||||
|
|
||||||
|
interface IUserInfo {
|
||||||
|
public function getId(): string;
|
||||||
|
public function getName(): string;
|
||||||
|
public function getCountryCode(): string;
|
||||||
|
public function getColour(): Colour;
|
||||||
|
public function isSuper(): bool;
|
||||||
|
public function getTimeZone(): TimeZoneInfo;
|
||||||
|
public function getCreatedTime(): DateTime;
|
||||||
|
public function getUpdatedTime(): DateTime;
|
||||||
|
public function isDeleted(): bool;
|
||||||
|
public function getDeletedTime(): DateTime;
|
||||||
|
}
|
12
src/Users/IUserPasswordInfo.php
Normal file
12
src/Users/IUserPasswordInfo.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
|
||||||
|
interface IUserPasswordInfo {
|
||||||
|
public function getUserId(): string;
|
||||||
|
public function getHash(): string;
|
||||||
|
public function getChangedTime(): DateTime;
|
||||||
|
public function verifyPassword(string $password): bool;
|
||||||
|
public function needsRehash(string|int|null $algo, array $options = []): bool;
|
||||||
|
}
|
12
src/Users/IUserTOTPInfo.php
Normal file
12
src/Users/IUserTOTPInfo.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
|
use Index\DateTime;
|
||||||
|
use Hanyuu\OTP\TOTPGenerator;
|
||||||
|
|
||||||
|
interface IUserTOTPInfo {
|
||||||
|
public function getUserId(): string;
|
||||||
|
public function getSecretKey(): string;
|
||||||
|
public function getChangedTime(): DateTime;
|
||||||
|
public function createGenerator(): TOTPGenerator;
|
||||||
|
}
|
6
src/Users/IUsers.php
Normal file
6
src/Users/IUsers.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
|
interface IUsers {
|
||||||
|
//
|
||||||
|
}
|
Loading…
Reference in a new issue