Project structure cleanup.
This commit is contained in:
parent
7e0218cf6b
commit
7d5f4a90e9
24 changed files with 199 additions and 367 deletions
|
@ -1,7 +1,82 @@
|
|||
<?php
|
||||
namespace Hanyuu\Auth;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\XString;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbResult;
|
||||
use Hanyuu\StatementCache;
|
||||
use Hanyuu\Users\UserInfo;
|
||||
|
||||
abstract class Auth {}
|
||||
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 StatementCache $stmts;
|
||||
|
||||
public function __construct(
|
||||
private IDbConnection $conn
|
||||
) {
|
||||
$this->stmts = new StatementCache($conn);
|
||||
}
|
||||
|
||||
|
||||
public function createLoginSession(
|
||||
UserInfo $userInfo,
|
||||
string $remoteAddr,
|
||||
string $countryCode,
|
||||
int $factors
|
||||
): string {
|
||||
$loginId = XString::random(48);
|
||||
|
||||
$stmt = $this->stmts->getStatement('create login', function() {
|
||||
return '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->stmts->getStatement('destroy login', fn() => ('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->stmts->getStatement('get login by id', fn() => ('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->stmts->getStatement('increment login done', fn() => ('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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<?php
|
||||
namespace Hanyuu\Auth\Db;
|
||||
namespace Hanyuu\Auth;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\Net\IPAddress;
|
||||
use Hanyuu\Auth\IAuthLogin;
|
||||
|
||||
class DbAuthLogin implements IAuthLogin {
|
||||
class AuthLoginInfo {
|
||||
private string $id;
|
||||
private string $userId;
|
||||
private IPAddress $remoteAddr;
|
|
@ -1,24 +1,23 @@
|
|||
<?php
|
||||
namespace Hanyuu\Auth;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\Routing\IRouter;
|
||||
use Syokuhou\IConfig;
|
||||
use Hanyuu\HanyuuContext;
|
||||
use Hanyuu\Auth\Auth;
|
||||
use Hanyuu\Auth\IAuthLogin;
|
||||
use Hanyuu\Users\IUserInfo;
|
||||
use Hanyuu\Users\IUsers;
|
||||
use Hanyuu\Users\UserNotFoundException;
|
||||
use Hanyuu\Auth\AuthLoginInfo;
|
||||
use Hanyuu\Users\Users;
|
||||
|
||||
// VERY IMPORTANT TODO: CSRF AND RATE LIMITING
|
||||
|
||||
class AuthRoutes {
|
||||
private HanyuuContext $context;
|
||||
private IUsers $users;
|
||||
private Users $users;
|
||||
private IConfig $config;
|
||||
private Auth $auth;
|
||||
|
||||
private ?IAuthLogin $loginSession;
|
||||
private ?AuthLoginInfo $loginSession;
|
||||
|
||||
public function __construct(
|
||||
IRouter $router,
|
||||
|
@ -108,9 +107,9 @@ class AuthRoutes {
|
|||
][$errorId] ?? '';
|
||||
|
||||
return $this->context->renderTemplate('auth/login', [
|
||||
'userName' => $userName,
|
||||
'errorId' => $errorId,
|
||||
'errorText' => $errorText,
|
||||
'user_name' => $userName,
|
||||
'error_name' => $errorId,
|
||||
'error_text' => $errorText,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -133,7 +132,7 @@ class AuthRoutes {
|
|||
|
||||
try {
|
||||
$userInfo = $this->users->getUserInfoByName($userName);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->redirect('/login?error=user_not_found&username=' . rawurlencode($userName));
|
||||
return;
|
||||
}
|
||||
|
@ -183,10 +182,10 @@ class AuthRoutes {
|
|||
}
|
||||
|
||||
return $this->context->renderTemplate('auth/login-tfa', [
|
||||
'userInfo' => $userInfo,
|
||||
'authMethods' => $authMethods,
|
||||
'authMethodNames' => $authMethodNames,
|
||||
'loginSession' => $this->loginSession,
|
||||
'user_info' => $userInfo,
|
||||
'auth_methods' => $authMethods,
|
||||
'auth_method_names' => $authMethodNames,
|
||||
'login_session' => $this->loginSession,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -218,8 +217,8 @@ class AuthRoutes {
|
|||
][$errorId] ?? '';
|
||||
|
||||
return $this->context->renderTemplate('auth/login-totp', [
|
||||
'userInfo' => $userInfo,
|
||||
'loginSession' => $this->loginSession,
|
||||
'user_info' => $userInfo,
|
||||
'login_session' => $this->loginSession,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
<?php
|
||||
namespace Hanyuu\Auth\Db;
|
||||
|
||||
use Index\XString;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\Data\DbType;
|
||||
use Hanyuu\StatementCache;
|
||||
use Hanyuu\Auth\IAuthLogin;
|
||||
use Hanyuu\Auth\IAuthMethod;
|
||||
use Hanyuu\Auth\Auth;
|
||||
use Hanyuu\Users\IUserInfo;
|
||||
|
||||
class DbAuth extends 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 StatementCache $stmts;
|
||||
|
||||
public function __construct(
|
||||
private IDbConnection $conn
|
||||
) {
|
||||
$this->stmts = new StatementCache($conn);
|
||||
}
|
||||
|
||||
|
||||
public function createLoginSession(
|
||||
IUserInfo $userInfo,
|
||||
string $remoteAddr,
|
||||
string $countryCode,
|
||||
int $factors
|
||||
): string {
|
||||
$loginId = XString::random(48);
|
||||
|
||||
$stmt = $this->stmts->getStatement('create login', function() {
|
||||
return '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, DbType::STRING);
|
||||
$stmt->addParameter(2, $userInfo->getId(), DbType::STRING);
|
||||
$stmt->addParameter(3, $remoteAddr, DbType::STRING);
|
||||
$stmt->addParameter(4, $countryCode, DbType::STRING);
|
||||
$stmt->addParameter(5, $factors, DbType::INTEGER);
|
||||
$stmt->execute();
|
||||
|
||||
return $loginId;
|
||||
}
|
||||
|
||||
public function destroyLoginSession(IAuthLogin $loginInfo): void {
|
||||
$stmt = $this->stmts->getStatement('destroy login', fn() => ('DELETE FROM ' . self::LOGINS_TABLE . ' WHERE auth_login_id = ?'));
|
||||
$stmt->addParameter(1, $loginInfo->getId(), DbType::STRING);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
private function fetchLoginSingle(IDbResult $result, string $exceptionText): IUserInfo {
|
||||
if(!$result->next())
|
||||
throw new AuthLoginNotFoundException($exceptionText);
|
||||
|
||||
return new DbUserInfo($result);
|
||||
}
|
||||
|
||||
public function getLoginSessionById(string $loginId): ?IAuthLogin {
|
||||
$stmt = $this->stmts->getStatement('get login by id', fn() => ('SELECT ' . implode(',', self::LOGINS_FIELDS) . ' FROM ' . self::LOGINS_TABLE . ' WHERE auth_login_id = ?'));
|
||||
$stmt->addParameter(1, $loginId, DbType::STRING);
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
||||
if(!$result->next())
|
||||
return null;
|
||||
|
||||
return new DbAuthLogin($result);
|
||||
}
|
||||
|
||||
public function incrementLoginSessionDone(IAuthLogin $login): void {
|
||||
$stmt = $this->stmts->getStatement('increment login done', fn() => ('UPDATE ' . self::LOGINS_TABLE . ' SET auth_login_factors_done = auth_login_factors_done + 1 WHERE auth_login_id = ?'));
|
||||
$stmt->addParameter(1, $login->getId(), DbType::STRING);
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
namespace Hanyuu\Auth;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Net\IPAddress;
|
||||
|
||||
interface IAuthLogin {
|
||||
public function getId(): string;
|
||||
public function getUserId(): string;
|
||||
public function getRemoteAddress(): IPAddress;
|
||||
public function getCountryCode(): string;
|
||||
public function getFactorsRequired(): int;
|
||||
public function getFactorsDone(): int;
|
||||
public function getStartedTime(): DateTime;
|
||||
public function isValid(): bool;
|
||||
public function getValidTime(): DateTime;
|
||||
public function hasCompleted(): bool;
|
||||
public function getCompletedTime(): DateTime;
|
||||
}
|
|
@ -13,14 +13,12 @@ use Sasae\SasaeEnvironment;
|
|||
use Syokuhou\IConfig;
|
||||
use Hanyuu\Auth\Auth;
|
||||
use Hanyuu\Auth\AuthRoutes;
|
||||
use Hanyuu\Auth\Db\DbAuth;
|
||||
use Hanyuu\Users\IUsers;
|
||||
use Hanyuu\Users\Db\DbUsers;
|
||||
use Hanyuu\Users\Users;
|
||||
|
||||
class HanyuuContext {
|
||||
private IConfig $config;
|
||||
private IDbConnection $dbConn;
|
||||
private IUsers $users;
|
||||
private Users $users;
|
||||
private Auth $auth;
|
||||
private ?SasaeEnvironment $templating = null;
|
||||
private SiteInfo $siteInfo;
|
||||
|
@ -28,12 +26,12 @@ class HanyuuContext {
|
|||
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||
$this->config = $config;
|
||||
$this->dbConn = $dbConn;
|
||||
$this->users = new DbUsers($dbConn);
|
||||
$this->users = new Users($dbConn);
|
||||
|
||||
$this->siteInfo = new SiteInfo($config->scopeTo('site'));
|
||||
}
|
||||
|
||||
public function getUsers(): IUsers {
|
||||
public function getUsers(): Users {
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
|
@ -78,7 +76,7 @@ class HanyuuContext {
|
|||
}
|
||||
|
||||
public function setUpAuth(): void {
|
||||
$this->auth = new DbAuth($this->dbConn);
|
||||
$this->auth = new Auth($this->dbConn);
|
||||
}
|
||||
|
||||
public function getAuth(): Auth {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
namespace Hanyuu\OTP;
|
||||
|
||||
interface IOTPGenerator {
|
||||
public function generate(): string;
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
<?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));
|
||||
}
|
||||
}
|
52
src/TOTPGenerator.php
Normal file
52
src/TOTPGenerator.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
use Index\DateTime;
|
||||
|
||||
interface IUserAuthInfo {
|
||||
public function getUserId(): string;
|
||||
public function getType(): string;
|
||||
public function getEnabledTime(): DateTime;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<?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;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?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;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?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;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<?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;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?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;
|
||||
public function generateValidCodes(int $offset = 0, int $timeStamp = -1): array;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
interface IUsers {
|
||||
//
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users\Db;
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Hanyuu\Users\IUserAuthInfo;
|
||||
|
||||
class DbUserAuthInfo implements IUserAuthInfo {
|
||||
class UserAuthInfo {
|
||||
private string $userId;
|
||||
private string $type;
|
||||
private DateTime $enabled;
|
|
@ -1,11 +1,10 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users\Db;
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Hanyuu\Users\IUserBackupInfo;
|
||||
|
||||
class DbUserBackupInfo implements IUserBackupInfo {
|
||||
class UserBackupInfo {
|
||||
private string $userId;
|
||||
private string $code;
|
||||
private DateTime $created;
|
|
@ -1,11 +1,10 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users\Db;
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Hanyuu\Users\IUserEMailInfo;
|
||||
|
||||
class DbUserEMailInfo implements IUserEMailInfo {
|
||||
class UserEMailInfo {
|
||||
private string $userId;
|
||||
private string $address;
|
||||
private DateTime $created;
|
|
@ -1,14 +1,13 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users\Db;
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
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 {
|
||||
class UserInfo {
|
||||
private string $id;
|
||||
private string $name;
|
||||
private string $country;
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class UserNotFoundException extends RuntimeException {}
|
|
@ -1,11 +1,10 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users\Db;
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Hanyuu\Users\IUserPasswordInfo;
|
||||
|
||||
class DbUserPasswordInfo implements IUserPasswordInfo {
|
||||
class UserPasswordInfo {
|
||||
private string $userId;
|
||||
private string $hash;
|
||||
private DateTime $changed;
|
|
@ -1,12 +1,11 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users\Db;
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Hanyuu\OTP\TOTPGenerator;
|
||||
use Hanyuu\Users\IUserTOTPInfo;
|
||||
use Hanyuu\TOTPGenerator;
|
||||
|
||||
class DbUserTOTPInfo implements IUserTOTPInfo {
|
||||
class UserTOTPInfo {
|
||||
private string $userId;
|
||||
private string $secretKey;
|
||||
private DateTime $changed;
|
||||
|
@ -33,13 +32,7 @@ class DbUserTOTPInfo implements IUserTOTPInfo {
|
|||
return new TOTPGenerator($this->secretKey);
|
||||
}
|
||||
|
||||
public function generateValidCodes(int $offset = 0, int $timeStamp = -1): array {
|
||||
$generator = $this->createGenerator();
|
||||
|
||||
return [
|
||||
$generator->generate(-1 + $offset, $timeStamp),
|
||||
$generator->generate($offset, $timeStamp),
|
||||
$generator->generate(1 + $offset, $timeStamp),
|
||||
];
|
||||
public function generateValidCodes(int $range = 1, ?int $timecode = null): array {
|
||||
return $this->createGenerator()->generateRange($range, $timecode);
|
||||
}
|
||||
}
|
|
@ -1,21 +1,18 @@
|
|||
<?php
|
||||
namespace Hanyuu\Users\Db;
|
||||
namespace Hanyuu\Users;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbStatement;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\Data\DbType;
|
||||
use Hanyuu\StatementCache;
|
||||
use Hanyuu\Auth\IAuthMethod;
|
||||
use Hanyuu\Auth\Auth;
|
||||
use Hanyuu\Users\IUsers;
|
||||
use Hanyuu\Users\IUserInfo;
|
||||
use Hanyuu\Users\IUserPasswordInfo;
|
||||
use Hanyuu\Users\IUserTOTPInfo;
|
||||
use Hanyuu\Users\UserNotFoundException;
|
||||
use Hanyuu\Users\UserInfo;
|
||||
use Hanyuu\Users\UserPasswordInfo;
|
||||
use Hanyuu\Users\UserTOTPInfo;
|
||||
|
||||
class DbUsers implements IUsers {
|
||||
class Users {
|
||||
private const USERS_TABLE = 'hau_users';
|
||||
private const TFA_TABLE = 'hau_users_tfa';
|
||||
private const BACKUP_TABLE = 'hau_users_backup';
|
||||
|
@ -51,57 +48,57 @@ class DbUsers implements IUsers {
|
|||
$this->stmts = new StatementCache($conn);
|
||||
}
|
||||
|
||||
private function fetchUserInfoSingle(IDbResult $result, string $exceptionText): IUserInfo {
|
||||
private function fetchUserInfoSingle(IDbResult $result, string $exceptionText): UserInfo {
|
||||
if(!$result->next())
|
||||
throw new UserNotFoundException($exceptionText);
|
||||
throw new RuntimeException($exceptionText);
|
||||
|
||||
return new DbUserInfo($result);
|
||||
return new UserInfo($result);
|
||||
}
|
||||
|
||||
public function getUserInfoById(string $userId): IUserInfo {
|
||||
public function getUserInfoById(string $userId): UserInfo {
|
||||
$stmt = $this->stmts->getStatement('get info by id', fn() => ('SELECT ' . implode(',', self::USERS_FIELDS) . ' FROM ' . self::USERS_TABLE . ' WHERE user_id = ?'));
|
||||
$stmt->addParameter(1, $userId, DbType::STRING);
|
||||
$stmt->addParameter(1, $userId);
|
||||
$stmt->execute();
|
||||
|
||||
return $this->fetchUserInfoSingle($stmt->getResult(), 'no user with $userId found');
|
||||
}
|
||||
|
||||
public function getUserInfoByName(string $userName): IUserInfo {
|
||||
public function getUserInfoByName(string $userName): UserInfo {
|
||||
$stmt = $this->stmts->getStatement('get info by name', fn() => ('SELECT ' . implode(',', self::USERS_FIELDS) . ' FROM ' . self::USERS_TABLE . ' WHERE user_name = ?'));
|
||||
$stmt->addParameter(1, $userName, DbType::STRING);
|
||||
$stmt->addParameter(1, $userName);
|
||||
$stmt->execute();
|
||||
|
||||
return $this->fetchUserInfoSingle($stmt->getResult(), 'no user with $userName found');
|
||||
}
|
||||
|
||||
|
||||
public function countUserTFAMethods(IUserInfo $userInfo): int {
|
||||
public function countUserTFAMethods(UserInfo $userInfo): int {
|
||||
$stmt = $this->stmts->getStatement('count user tfa methods', fn() => ('SELECT COUNT(*) FROM ' . self::TFA_TABLE . ' WHERE user_id = ?'));
|
||||
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||
$stmt->addParameter(1, $userInfo->getId());
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
||||
return $result->next() ? $result->getInteger(0) : 0;
|
||||
}
|
||||
|
||||
public function getUserTFAMethods(IUserInfo $userInfo): array {
|
||||
public function getUserTFAMethods(UserInfo $userInfo): array {
|
||||
$stmt = $this->stmts->getStatement('get user tfa methods', fn() => ('SELECT ' . implode(',', self::TFA_FIELDS) . ' FROM ' . self::TFA_TABLE . ' WHERE user_id = ?'));
|
||||
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||
$stmt->addParameter(1, $userInfo->getId());
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$array = [];
|
||||
|
||||
while($result->next())
|
||||
$array[] = new DbUserAuthInfo($result);
|
||||
$array[] = new UserAuthInfo($result);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function checkUserTFAMethod(IUserInfo $userInfo, IAuthMethod $method): bool {
|
||||
public function checkUserTFAMethod(UserInfo $userInfo, IAuthMethod $method): bool {
|
||||
$stmt = $this->stmts->getStatement('check user auth method', fn() => ('SELECT COUNT(*) FROM ' . self::TFA_TABLE . ' WHERE user_id = ? AND user_tfa_type = ?'));
|
||||
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||
$stmt->addParameter(2, $method->getName(), DbType::STRING);
|
||||
$stmt->addParameter(1, $userInfo->getId());
|
||||
$stmt->addParameter(2, $method->getName());
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
||||
|
@ -109,57 +106,57 @@ class DbUsers implements IUsers {
|
|||
}
|
||||
|
||||
|
||||
public function getUserPasswordInfo(IUserInfo $userInfo): IUserPasswordInfo {
|
||||
public function getUserPasswordInfo(UserInfo $userInfo): UserPasswordInfo {
|
||||
$stmt = $this->stmts->getStatement('get pwd', fn() => ('SELECT ' . implode(',', self::PASSWORDS_FIELDS) . ' FROM ' . self::PASSWORDS_TABLE . ' WHERE user_id = ?'));
|
||||
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||
$stmt->addParameter(1, $userInfo->getId());
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
||||
if(!$result->next())
|
||||
throw new RuntimeException('no password info for $userInfo found');
|
||||
|
||||
return new DbUserPasswordInfo($result);
|
||||
return new UserPasswordInfo($result);
|
||||
}
|
||||
|
||||
|
||||
public function getUserTOTPInfo(IUserInfo $userInfo): IUserTOTPInfo {
|
||||
public function getUserTOTPInfo(UserInfo $userInfo): UserTOTPInfo {
|
||||
$stmt = $this->stmts->getStatement('get totp', fn() => ('SELECT ' . implode(',', self::TOTP_FIELDS) . ' FROM ' . self::TOTP_TABLE . ' WHERE user_id = ?'));
|
||||
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||
$stmt->addParameter(1, $userInfo->getId());
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
||||
if(!$result->next())
|
||||
throw new RuntimeException('no totp info for $userInfo found');
|
||||
|
||||
return new DbUserTOTPInfo($result);
|
||||
return new UserTOTPInfo($result);
|
||||
}
|
||||
|
||||
|
||||
public function getUserEMailInfos(IUserInfo $userInfo): array {
|
||||
public function getUserEMailInfos(UserInfo $userInfo): array {
|
||||
$stmt = $this->stmts->getStatement('get emails', fn() => ('SELECT ' . implode(',', self::EMAILS_FIELDS) . ' FROM ' . self::EMAILS_TABLE . ' WHERE user_id = ?'));
|
||||
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||
$stmt->addParameter(1, $userInfo->getId());
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
||||
$array = [];
|
||||
|
||||
while($result->next())
|
||||
$array[] = new DbUserEMailInfo($result);
|
||||
$array[] = new UserEMailInfo($result);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
public function getUserBackupInfos(IUserInfo $userInfo): array {
|
||||
public function getUserBackupInfos(UserInfo $userInfo): array {
|
||||
$stmt = $this->stmts->getStatement('get backups', fn() => ('SELECT ' . implode(',', self::BACKUP_FIELDS) . ' FROM ' . self::BACKUP_TABLE . ' WHERE user_id = ?'));
|
||||
$stmt->addParameter(1, $userInfo->getId(), DbType::STRING);
|
||||
$stmt->addParameter(1, $userInfo->getId());
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
||||
$array = [];
|
||||
|
||||
while($result->next())
|
||||
$array[] = new DbUserBackupInfo($result);
|
||||
$array[] = new UserBackupInfo($result);
|
||||
|
||||
return $array;
|
||||
}
|
Loading…
Reference in a new issue