Removed existing code.

This commit is contained in:
flash 2024-06-11 22:12:41 +00:00
parent 44bbdc243a
commit c52c8c00a5
13 changed files with 0 additions and 1019 deletions

View file

@ -1,104 +0,0 @@
<?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;
');
}
}

View file

@ -1,76 +0,0 @@
<?php
namespace Hanyuu\Auth;
use Index\XString;
use Index\Data\{DbStatementCache,IDbConnection,IDbResult};
use Hanyuu\Users\UserInfo;
class Auth {
private const LOGINS_TABLE = 'hau_auth_logins';
private const LOGINS_FIELDS = [
'auth_login_id', 'user_id', 'auth_login_ip', 'auth_login_country',
'auth_login_factors_required', 'auth_login_factors_done',
'UNIX_TIMESTAMP(auth_login_started)', 'UNIX_TIMESTAMP(auth_login_valid)', 'UNIX_TIMESTAMP(auth_login_completed)',
];
private DbStatementCache $cache;
public function __construct(
private IDbConnection $conn
) {
$this->cache = new DbStatementCache($conn);
}
public function createLoginSession(
UserInfo $userInfo,
string $remoteAddr,
string $countryCode,
int $factors
): string {
$loginId = XString::random(48);
$stmt = $this->cache->get('INSERT INTO ' . self::LOGINS_TABLE . ' (auth_login_id, user_id, auth_login_ip, auth_login_country, auth_login_factors_required) VALUES (?, ?, INET6_ATON(?), ?, ?)');
$stmt->addParameter(1, $loginId);
$stmt->addParameter(2, $userInfo->getId());
$stmt->addParameter(3, $remoteAddr);
$stmt->addParameter(4, $countryCode);
$stmt->addParameter(5, $factors);
$stmt->execute();
return $loginId;
}
public function destroyLoginSession(AuthLoginInfo $loginInfo): void {
$stmt = $this->cache->get('DELETE FROM ' . self::LOGINS_TABLE . ' WHERE auth_login_id = ?');
$stmt->addParameter(1, $loginInfo->getId());
$stmt->execute();
}
private function fetchLoginSingle(IDbResult $result, string $exceptionText): UserInfo {
if(!$result->next())
throw new AuthLoginNotFoundException($exceptionText);
return new UserInfo($result);
}
public function getLoginSessionById(string $loginId): ?AuthLoginInfo {
$stmt = $this->cache->get('SELECT ' . implode(',', self::LOGINS_FIELDS) . ' FROM ' . self::LOGINS_TABLE . ' WHERE auth_login_id = ?');
$stmt->addParameter(1, $loginId);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
return null;
return new AuthLoginInfo($result);
}
public function incrementLoginSessionDone(AuthLoginInfo $login): void {
$stmt = $this->cache->get('UPDATE ' . self::LOGINS_TABLE . ' SET auth_login_factors_done = auth_login_factors_done + 1 WHERE auth_login_id = ?');
$stmt->addParameter(1, $login->getId());
$stmt->execute();
}
}

View file

@ -1,76 +0,0 @@
<?php
namespace Hanyuu\Auth;
use Index\DateTime;
use Index\Data\IDbResult;
use Index\Net\IPAddress;
class AuthLoginInfo {
private string $id;
private string $userId;
private IPAddress $remoteAddr;
private string $country;
private int $factorsRequired;
private int $factorsDone;
private DateTime $started;
private DateTime $valid;
private bool $hasCompleted;
private DateTime $completed;
public function __construct(IDbResult $result) {
$this->id = $result->getString(0);
$this->userId = $result->getString(1);
$this->remoteAddr = new IPAddress($result->getString(2));
$this->country = $result->getString(3);
$this->factorsRequired = $result->getInteger(4);
$this->factorsDone = $result->getInteger(5);
$this->started = DateTime::fromUnixTimeSeconds($result->getInteger(6));
$this->valid = DateTime::fromUnixTimeSeconds($result->getInteger(7));
$this->hasCompleted = !$result->isNull(8);
$this->completed = DateTime::fromUnixTimeSeconds($result->getInteger(8));
}
public function getId(): string {
return $this->id;
}
public function getUserId(): string {
return $this->userId;
}
public function getRemoteAddress(): IPAddress {
return $this->remoteAddr;
}
public function getCountryCode(): string {
return $this->country;
}
public function getFactorsRequired(): int {
return $this->factorsRequired;
}
public function getFactorsDone(): int {
return $this->factorsDone;
}
public function getStartedTime(): DateTime {
return $this->started;
}
public function isValid(): bool {
return DateTime::utcNow()->isLessThan($this->valid);
}
public function getValidTime(): DateTime {
return $this->valid;
}
public function hasCompleted(): bool {
return $this->hasCompleted;
}
public function getCompletedTime(): DateTime {
return $this->completed;
}
}

View file

@ -1,269 +0,0 @@
<?php
namespace Hanyuu\Auth;
use RuntimeException;
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler};
use Syokuhou\IConfig;
use Hanyuu\HanyuuContext;
use Hanyuu\Auth\{Auth,AuthLoginInfo};
use Hanyuu\Users\Users;
// VERY IMPORTANT TODO: CSRF AND RATE LIMITING
class AuthRoutes extends RouteHandler {
private HanyuuContext $context;
private Users $users;
private IConfig $config;
private Auth $auth;
private ?AuthLoginInfo $loginSession;
public function __construct(
HanyuuContext $ctx,
IConfig $config
) {
$this->context = $ctx;
$this->config = $config;
$this->users = $ctx->getUsers();
$this->context->setUpAuth();
$this->auth = $ctx->getAuth();
}
private function getLoginCookieName(): string {
return $this->config->getString('login_cookie', 'hau_login');
}
private function destroyLoginSession($response): void {
if($this->loginSession !== null) {
$response->removeCookie($this->getLoginCookieName());
$this->auth->destroyLoginSession($this->loginSession);
$this->loginSession = null;
}
}
private function ensureLoginIncomplete($response): bool {
$loginSession = $this->loginSession;
if($loginSession === null) {
$response->redirect('/login');
return false;
}
if($loginSession->getFactorsDone() >= $loginSession->getFactorsRequired()) {
$response->redirect('/login/done');
return false;
}
return true;
}
#[HttpMiddleware('/login')]
public function filterLogin($response, $request) {
$loginId = (string)$request->getCookie($this->getLoginCookieName());
$this->loginSession = empty($loginId) ? null : $this->auth->getLoginSessionById($loginId);
if($this->loginSession !== null) {
if(!$this->loginSession->isValid()
|| $this->loginSession->getRemoteAddress()->getCleanAddress() !== $_SERVER['REMOTE_ADDR'])
$this->destroyLoginSession($response);
}
}
#[HttpGet('/login')]
public function getLogin($response, $request) {
$this->destroyLoginSession($response);
$userName = (string)$request->getParam('username');
$errorId = (string)$request->getParam('error');
$errorText = [
'invalid_username' => 'Either no username was provided or it was incorrectly formatted.',
'wrong_password' => 'The provided password was incorrect.',
'user_not_found' => 'No user with this name exists. NOTE: point to registration somehow',
'user_deleted' => 'Your account has been marked for deletion. Contact staff to revert this.',
][$errorId] ?? '';
return $this->context->renderTemplate('auth/login', [
'user_name' => $userName,
'error_name' => $errorId,
'error_text' => $errorText,
]);
}
#[HttpPost('/login')]
public function postLogin($response, $request) {
if(!$request->isFormContent())
return 400;
$form = $request->getContent();
// should be adjusted to respond with json to ajax shit
$this->destroyLoginSession($response);
$userName = (string)$form->getParam('username');
$password = (string)$form->getParam('password');
if(empty($userName)) {
$response->redirect('/login?error=invalid_username');
return;
}
try {
$userInfo = $this->users->getUserInfoByName($userName);
} catch(RuntimeException $ex) {
$response->redirect('/login?error=user_not_found&username=' . rawurlencode($userName));
return;
}
if($userInfo->isDeleted()) {
$response->redirect('/login?error=user_deleted');
return;
}
$passwordInfo = $this->users->getUserPasswordInfo($userInfo);
if(!$passwordInfo->verifyPassword($password)) {
$response->redirect('/login?error=wrong_password');
return;
}
$factorsRequired = $this->users->countUserTFAMethods($userInfo) > 0 ? 2 : 1;
$loginId = $this->auth->createLoginSession($userInfo, $_SERVER['REMOTE_ADDR'], 'A1', $factorsRequired);
$this->loginSession = $this->auth->getLoginSessionById($loginId);
$this->auth->incrementLoginSessionDone($this->loginSession);
// check for rehash
$response->addCookie($this->getLoginCookieName(), $loginId, strtotime('+30 minutes'), '/', '', true, true, true);
$response->redirect($factorsRequired > 1 ? '/login/tfa' : '/login/done');
}
#[HttpGet('/login/tfa')]
public function getLoginTFA($response, $request) {
if(!$this->ensureLoginIncomplete($response))
return;
$errorId = (string)$request->getParam('error');
$errorText = [
'invalid_method' => 'Either no method was provided or it was incorrectly formatted.',
][$errorId] ?? '';
$userInfo = $this->users->getUserInfoById($this->loginSession->getUserId());
$authMethods = $this->users->getUserTFAMethods($userInfo);
$authMethodNames = [];
foreach($authMethods as $authMethod)
$authMethodNames[] = $authMethod->getType();
if(count($authMethods) === 1) {
$response->redirect("/login/tfa/{$authMethods[0]->getType()}");
return;
}
return $this->context->renderTemplate('auth/login-tfa', [
'user_info' => $userInfo,
'auth_methods' => $authMethods,
'auth_method_names' => $authMethodNames,
'login_session' => $this->loginSession,
]);
}
#[HttpGet('/login/done')]
public function getLoginDone($response, $request) {
if($this->loginSession === null) {
$response->redirect('/login');
return;
}
if($this->loginSession->getFactorsDone() < $this->loginSession->getFactorsRequired()) {
$response->redirect('/login/pick');
return;
}
$this->destroyLoginSession($response);
return "you've successfully authenticated but there's no session system yet lol";
}
#[HttpGet('/login/tfa/totp')]
public function getLoginTOTP($response, $request) {
if(!$this->ensureLoginIncomplete($response))
return;
$userInfo = $this->users->getUserInfoById($this->loginSession->getUserId());
$errorId = (string)$request->getParam('error');
$errorText = [
'invalid_method' => 'Either no method was provided or it was incorrectly formatted.',
][$errorId] ?? '';
return $this->context->renderTemplate('auth/login-totp', [
'user_info' => $userInfo,
'login_session' => $this->loginSession,
]);
}
#[HttpPost('/login/tfa/totp')]
public function postLoginTOTP($response, $request) {
if(!$request->isFormContent())
return 400;
$form = $request->getContent();
if(!$this->ensureLoginIncomplete($response))
return;
$userInfo = $this->users->getUserInfoById($this->loginSession->getUserId());
$totpInfo = $this->users->getUserTOTPInfo($userInfo);
$totpValid = $totpInfo->generateValidCodes();
$totpCode = (string)$form->getParam('code');
if(!in_array($totpCode, $totpValid, true)) {
$response->redirect('/login/tfa/totp?error=wrong_totp');
return;
}
$this->auth->incrementLoginSessionDone($this->loginSession);
$response->redirect('/login/done');
}
#[HttpGet('/login/tfa/u2f')]
public function getLoginU2F($response, $request) {
return 503;
}
#[HttpPost('/login/tfa/u2f')]
public function postLoginU2F($response, $request) {
return 503;
}
#[HttpGet('/login/tfa/backup')]
public function getLoginBackup($response, $request) {
return 503;
}
#[HttpPost('/login/tfa/backup')]
public function postLoginBackup($response, $request) {
return 503;
}
#[HttpGet('/register')]
public function getRegister($response, $request) {
return 503;
}
#[HttpGet('/forgot-username')]
public function getForgotUserName($response, $request) {
return 503;
}
#[HttpGet('/forgot-password')]
public function getForgotPassword($response, $request) {
return 503;
}
#[HttpGet('/recover-password')]
public function getRecoverPassword($response, $request) {
return 503;
}
}

View file

@ -6,29 +6,20 @@ use Index\Data\IDbConnection;
use Index\Data\Migration\{IDbMigrationRepo,DbMigrationManager,FsDbMigrationRepo};
use Sasae\SasaeEnvironment;
use Syokuhou\IConfig;
use Hanyuu\Auth\{Auth,AuthRoutes};
use Hanyuu\Users\Users;
class HanyuuContext {
private IConfig $config;
private IDbConnection $dbConn;
private Users $users;
private Auth $auth;
private ?SasaeEnvironment $templating = null;
private SiteInfo $siteInfo;
public function __construct(IConfig $config, IDbConnection $dbConn) {
$this->config = $config;
$this->dbConn = $dbConn;
$this->users = new Users($dbConn);
$this->siteInfo = new SiteInfo($config->scopeTo('site'));
}
public function getUsers(): Users {
return $this->users;
}
public function getDatabase(): IDbConnection {
return $this->dbConn;
}
@ -69,14 +60,6 @@ class HanyuuContext {
return $this->getTemplating()->render(...$args);
}
public function setUpAuth(): void {
$this->auth = new Auth($this->dbConn);
}
public function getAuth(): Auth {
return $this->auth;
}
public function createRouting(): RoutingContext {
$routingCtx = new RoutingContext($this->getTemplating());
@ -84,8 +67,6 @@ class HanyuuContext {
return 503;
});
$routingCtx->register(new AuthRoutes($this, $this->config->scopeTo('auth')));
return $routingCtx;
}
}

View file

@ -1,52 +0,0 @@
<?php
namespace Hanyuu;
use InvalidArgumentException;
use Index\Serialisation\Base32;
class TOTPGenerator {
public const DIGITS = 6;
public const INTERVAL = 30000;
public function __construct(private string $secretKey) {}
public static function generateKey(): string {
return Base32::encode(random_bytes(16));
}
public static function timecode(?int $timestamp = null): int {
$timestamp ??= time();
return (int)(($timestamp * 1000) / self::INTERVAL);
}
public function generate(?int $timecode = null): string {
$timecode ??= self::timecode();
$hash = hash_hmac('sha1', pack('J', $timecode), Base32::decode($this->secretKey), true);
$offset = ord($hash[strlen($hash) - 1]) & 0x0F;
$bin = 0;
$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 = $bin % pow(10, self::DIGITS);
return str_pad((string)$otp, self::DIGITS, '0', STR_PAD_LEFT);
}
public function generateRange(int $range = 1, ?int $timecode = null): array {
if($range < 1)
throw new InvalidArgumentException('$range must be greater than 0.');
$timecode ??= self::timecode();
$tokens = [$this->generate($timecode)];
for($i = 1; $i <= $range; ++$i)
$tokens[] = $this->generate($timecode - $i);
for($i = 1; $i <= $range; ++$i)
$tokens[] = $this->generate($timecode + $i);
return $tokens;
}
}

View file

@ -1,29 +0,0 @@
<?php
namespace Hanyuu\Users;
use Index\DateTime;
use Index\Data\IDbResult;
class UserAuthInfo {
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;
}
}

View file

@ -1,41 +0,0 @@
<?php
namespace Hanyuu\Users;
use Index\DateTime;
use Index\Data\IDbResult;
class UserBackupInfo {
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;
}
}

View file

@ -1,47 +0,0 @@
<?php
namespace Hanyuu\Users;
use Index\DateTime;
use Index\Data\IDbResult;
class UserEMailInfo {
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;
}
}

View file

@ -1,74 +0,0 @@
<?php
namespace Hanyuu\Users;
use Index\DateTime;
use Index\TimeZoneInfo;
use Index\Colour\Colour;
use Index\Colour\ColourRGB;
use Index\Data\IDbResult;
class UserInfo {
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;
}
}

View file

@ -1,37 +0,0 @@
<?php
namespace Hanyuu\Users;
use Index\DateTime;
use Index\Data\IDbResult;
class UserPasswordInfo {
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);
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace Hanyuu\Users;
use Index\DateTime;
use Index\Data\IDbResult;
use Hanyuu\TOTPGenerator;
class UserTOTPInfo {
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);
}
public function generateValidCodes(int $range = 1, ?int $timecode = null): array {
return $this->createGenerator()->generateRange($range, $timecode);
}
}

View file

@ -1,157 +0,0 @@
<?php
namespace Hanyuu\Users;
use RuntimeException;
use Index\Data\{DbStatementCache,IDbConnection,IDbStatement,IDbResult};
use Hanyuu\Auth\{Auth,IAuthMethod};
use Hanyuu\Users\{UserInfo,UserPasswordInfo,UserTOTPInfo};
class Users {
private const USERS_TABLE = 'hau_users';
private const TFA_TABLE = 'hau_users_tfa';
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 TFA_FIELDS = [
'user_id', 'user_tfa_type', 'UNIX_TIMESTAMP(user_tfa_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 DbStatementCache $cache;
public function __construct(
private IDbConnection $conn
) {
$this->cache = new DbStatementCache($conn);
}
private function fetchUserInfoSingle(IDbResult $result, string $exceptionText): UserInfo {
if(!$result->next())
throw new RuntimeException($exceptionText);
return new UserInfo($result);
}
public function getUserInfoById(string $userId): UserInfo {
$stmt = $this->cache->get('SELECT ' . implode(',', self::USERS_FIELDS) . ' FROM ' . self::USERS_TABLE . ' WHERE user_id = ?');
$stmt->addParameter(1, $userId);
$stmt->execute();
return $this->fetchUserInfoSingle($stmt->getResult(), 'no user with $userId found');
}
public function getUserInfoByName(string $userName): UserInfo {
$stmt = $this->cache->get('SELECT ' . implode(',', self::USERS_FIELDS) . ' FROM ' . self::USERS_TABLE . ' WHERE user_name = ?');
$stmt->addParameter(1, $userName);
$stmt->execute();
return $this->fetchUserInfoSingle($stmt->getResult(), 'no user with $userName found');
}
public function countUserTFAMethods(UserInfo $userInfo): int {
$stmt = $this->cache->get('SELECT COUNT(*) FROM ' . self::TFA_TABLE . ' WHERE user_id = ?');
$stmt->addParameter(1, $userInfo->getId());
$stmt->execute();
$result = $stmt->getResult();
return $result->next() ? $result->getInteger(0) : 0;
}
public function getUserTFAMethods(UserInfo $userInfo): array {
$stmt = $this->cache->get('SELECT ' . implode(',', self::TFA_FIELDS) . ' FROM ' . self::TFA_TABLE . ' WHERE user_id = ?');
$stmt->addParameter(1, $userInfo->getId());
$stmt->execute();
$result = $stmt->getResult();
$array = [];
while($result->next())
$array[] = new UserAuthInfo($result);
return $array;
}
public function checkUserTFAMethod(UserInfo $userInfo, IAuthMethod $method): bool {
$stmt = $this->cache->get('SELECT COUNT(*) FROM ' . self::TFA_TABLE . ' WHERE user_id = ? AND user_tfa_type = ?');
$stmt->addParameter(1, $userInfo->getId());
$stmt->addParameter(2, $method->getName());
$stmt->execute();
$result = $stmt->getResult();
return $result->next() ? $result->getInteger(0) !== 0 : false;
}
public function getUserPasswordInfo(UserInfo $userInfo): UserPasswordInfo {
$stmt = $this->cache->get('SELECT ' . implode(',', self::PASSWORDS_FIELDS) . ' FROM ' . self::PASSWORDS_TABLE . ' WHERE user_id = ?');
$stmt->addParameter(1, $userInfo->getId());
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('no password info for $userInfo found');
return new UserPasswordInfo($result);
}
public function getUserTOTPInfo(UserInfo $userInfo): UserTOTPInfo {
$stmt = $this->cache->get('SELECT ' . implode(',', self::TOTP_FIELDS) . ' FROM ' . self::TOTP_TABLE . ' WHERE user_id = ?');
$stmt->addParameter(1, $userInfo->getId());
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('no totp info for $userInfo found');
return new UserTOTPInfo($result);
}
public function getUserEMailInfos(UserInfo $userInfo): array {
$stmt = $this->cache->get('SELECT ' . implode(',', self::EMAILS_FIELDS) . ' FROM ' . self::EMAILS_TABLE . ' WHERE user_id = ?');
$stmt->addParameter(1, $userInfo->getId());
$stmt->execute();
$result = $stmt->getResult();
$array = [];
while($result->next())
$array[] = new UserEMailInfo($result);
return $array;
}
public function getUserBackupInfos(UserInfo $userInfo): array {
$stmt = $this->cache->get('SELECT ' . implode(',', self::BACKUP_FIELDS) . ' FROM ' . self::BACKUP_TABLE . ' WHERE user_id = ?');
$stmt->addParameter(1, $userInfo->getId());
$stmt->execute();
$result = $stmt->getResult();
$array = [];
while($result->next())
$array[] = new UserBackupInfo($result);
return $array;
}
}