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
|
<?php
|
||||||
namespace Hanyuu\Auth;
|
namespace Hanyuu\Auth;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use Index\XString;
|
||||||
use RuntimeException;
|
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
|
<?php
|
||||||
namespace Hanyuu\Auth\Db;
|
namespace Hanyuu\Auth;
|
||||||
|
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Index\Net\IPAddress;
|
use Index\Net\IPAddress;
|
||||||
use Hanyuu\Auth\IAuthLogin;
|
|
||||||
|
|
||||||
class DbAuthLogin implements IAuthLogin {
|
class AuthLoginInfo {
|
||||||
private string $id;
|
private string $id;
|
||||||
private string $userId;
|
private string $userId;
|
||||||
private IPAddress $remoteAddr;
|
private IPAddress $remoteAddr;
|
|
@ -1,24 +1,23 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu\Auth;
|
namespace Hanyuu\Auth;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
use Index\Routing\IRouter;
|
use Index\Routing\IRouter;
|
||||||
use Syokuhou\IConfig;
|
use Syokuhou\IConfig;
|
||||||
use Hanyuu\HanyuuContext;
|
use Hanyuu\HanyuuContext;
|
||||||
use Hanyuu\Auth\Auth;
|
use Hanyuu\Auth\Auth;
|
||||||
use Hanyuu\Auth\IAuthLogin;
|
use Hanyuu\Auth\AuthLoginInfo;
|
||||||
use Hanyuu\Users\IUserInfo;
|
use Hanyuu\Users\Users;
|
||||||
use Hanyuu\Users\IUsers;
|
|
||||||
use Hanyuu\Users\UserNotFoundException;
|
|
||||||
|
|
||||||
// VERY IMPORTANT TODO: CSRF AND RATE LIMITING
|
// VERY IMPORTANT TODO: CSRF AND RATE LIMITING
|
||||||
|
|
||||||
class AuthRoutes {
|
class AuthRoutes {
|
||||||
private HanyuuContext $context;
|
private HanyuuContext $context;
|
||||||
private IUsers $users;
|
private Users $users;
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
private Auth $auth;
|
private Auth $auth;
|
||||||
|
|
||||||
private ?IAuthLogin $loginSession;
|
private ?AuthLoginInfo $loginSession;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IRouter $router,
|
IRouter $router,
|
||||||
|
@ -108,9 +107,9 @@ class AuthRoutes {
|
||||||
][$errorId] ?? '';
|
][$errorId] ?? '';
|
||||||
|
|
||||||
return $this->context->renderTemplate('auth/login', [
|
return $this->context->renderTemplate('auth/login', [
|
||||||
'userName' => $userName,
|
'user_name' => $userName,
|
||||||
'errorId' => $errorId,
|
'error_name' => $errorId,
|
||||||
'errorText' => $errorText,
|
'error_text' => $errorText,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +132,7 @@ class AuthRoutes {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$userInfo = $this->users->getUserInfoByName($userName);
|
$userInfo = $this->users->getUserInfoByName($userName);
|
||||||
} catch(UserNotFoundException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
$response->redirect('/login?error=user_not_found&username=' . rawurlencode($userName));
|
$response->redirect('/login?error=user_not_found&username=' . rawurlencode($userName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -183,10 +182,10 @@ class AuthRoutes {
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->context->renderTemplate('auth/login-tfa', [
|
return $this->context->renderTemplate('auth/login-tfa', [
|
||||||
'userInfo' => $userInfo,
|
'user_info' => $userInfo,
|
||||||
'authMethods' => $authMethods,
|
'auth_methods' => $authMethods,
|
||||||
'authMethodNames' => $authMethodNames,
|
'auth_method_names' => $authMethodNames,
|
||||||
'loginSession' => $this->loginSession,
|
'login_session' => $this->loginSession,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,8 +217,8 @@ class AuthRoutes {
|
||||||
][$errorId] ?? '';
|
][$errorId] ?? '';
|
||||||
|
|
||||||
return $this->context->renderTemplate('auth/login-totp', [
|
return $this->context->renderTemplate('auth/login-totp', [
|
||||||
'userInfo' => $userInfo,
|
'user_info' => $userInfo,
|
||||||
'loginSession' => $this->loginSession,
|
'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 Syokuhou\IConfig;
|
||||||
use Hanyuu\Auth\Auth;
|
use Hanyuu\Auth\Auth;
|
||||||
use Hanyuu\Auth\AuthRoutes;
|
use Hanyuu\Auth\AuthRoutes;
|
||||||
use Hanyuu\Auth\Db\DbAuth;
|
use Hanyuu\Users\Users;
|
||||||
use Hanyuu\Users\IUsers;
|
|
||||||
use Hanyuu\Users\Db\DbUsers;
|
|
||||||
|
|
||||||
class HanyuuContext {
|
class HanyuuContext {
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
private IDbConnection $dbConn;
|
private IDbConnection $dbConn;
|
||||||
private IUsers $users;
|
private Users $users;
|
||||||
private Auth $auth;
|
private Auth $auth;
|
||||||
private ?SasaeEnvironment $templating = null;
|
private ?SasaeEnvironment $templating = null;
|
||||||
private SiteInfo $siteInfo;
|
private SiteInfo $siteInfo;
|
||||||
|
@ -28,12 +26,12 @@ class HanyuuContext {
|
||||||
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->dbConn = $dbConn;
|
$this->dbConn = $dbConn;
|
||||||
$this->users = new DbUsers($dbConn);
|
$this->users = new Users($dbConn);
|
||||||
|
|
||||||
$this->siteInfo = new SiteInfo($config->scopeTo('site'));
|
$this->siteInfo = new SiteInfo($config->scopeTo('site'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUsers(): IUsers {
|
public function getUsers(): Users {
|
||||||
return $this->users;
|
return $this->users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +76,7 @@ class HanyuuContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUpAuth(): void {
|
public function setUpAuth(): void {
|
||||||
$this->auth = new DbAuth($this->dbConn);
|
$this->auth = new Auth($this->dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAuth(): Auth {
|
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
|
<?php
|
||||||
namespace Hanyuu\Users\Db;
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Hanyuu\Users\IUserAuthInfo;
|
|
||||||
|
|
||||||
class DbUserAuthInfo implements IUserAuthInfo {
|
class UserAuthInfo {
|
||||||
private string $userId;
|
private string $userId;
|
||||||
private string $type;
|
private string $type;
|
||||||
private DateTime $enabled;
|
private DateTime $enabled;
|
|
@ -1,11 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu\Users\Db;
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Hanyuu\Users\IUserBackupInfo;
|
|
||||||
|
|
||||||
class DbUserBackupInfo implements IUserBackupInfo {
|
class UserBackupInfo {
|
||||||
private string $userId;
|
private string $userId;
|
||||||
private string $code;
|
private string $code;
|
||||||
private DateTime $created;
|
private DateTime $created;
|
|
@ -1,11 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu\Users\Db;
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Hanyuu\Users\IUserEMailInfo;
|
|
||||||
|
|
||||||
class DbUserEMailInfo implements IUserEMailInfo {
|
class UserEMailInfo {
|
||||||
private string $userId;
|
private string $userId;
|
||||||
private string $address;
|
private string $address;
|
||||||
private DateTime $created;
|
private DateTime $created;
|
|
@ -1,14 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu\Users\Db;
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
use Index\TimeZoneInfo;
|
use Index\TimeZoneInfo;
|
||||||
use Index\Colour\Colour;
|
use Index\Colour\Colour;
|
||||||
use Index\Colour\ColourRGB;
|
use Index\Colour\ColourRGB;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Hanyuu\Users\IUserInfo;
|
|
||||||
|
|
||||||
class DbUserInfo implements IUserInfo {
|
class UserInfo {
|
||||||
private string $id;
|
private string $id;
|
||||||
private string $name;
|
private string $name;
|
||||||
private string $country;
|
private string $country;
|
|
@ -1,6 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Hanyuu\Users;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class UserNotFoundException extends RuntimeException {}
|
|
|
@ -1,11 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu\Users\Db;
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Hanyuu\Users\IUserPasswordInfo;
|
|
||||||
|
|
||||||
class DbUserPasswordInfo implements IUserPasswordInfo {
|
class UserPasswordInfo {
|
||||||
private string $userId;
|
private string $userId;
|
||||||
private string $hash;
|
private string $hash;
|
||||||
private DateTime $changed;
|
private DateTime $changed;
|
|
@ -1,12 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu\Users\Db;
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
use Index\DateTime;
|
use Index\DateTime;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Hanyuu\OTP\TOTPGenerator;
|
use Hanyuu\TOTPGenerator;
|
||||||
use Hanyuu\Users\IUserTOTPInfo;
|
|
||||||
|
|
||||||
class DbUserTOTPInfo implements IUserTOTPInfo {
|
class UserTOTPInfo {
|
||||||
private string $userId;
|
private string $userId;
|
||||||
private string $secretKey;
|
private string $secretKey;
|
||||||
private DateTime $changed;
|
private DateTime $changed;
|
||||||
|
@ -33,13 +32,7 @@ class DbUserTOTPInfo implements IUserTOTPInfo {
|
||||||
return new TOTPGenerator($this->secretKey);
|
return new TOTPGenerator($this->secretKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateValidCodes(int $offset = 0, int $timeStamp = -1): array {
|
public function generateValidCodes(int $range = 1, ?int $timecode = null): array {
|
||||||
$generator = $this->createGenerator();
|
return $this->createGenerator()->generateRange($range, $timecode);
|
||||||
|
|
||||||
return [
|
|
||||||
$generator->generate(-1 + $offset, $timeStamp),
|
|
||||||
$generator->generate($offset, $timeStamp),
|
|
||||||
$generator->generate(1 + $offset, $timeStamp),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,21 +1,18 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Hanyuu\Users\Db;
|
namespace Hanyuu\Users;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\Data\IDbConnection;
|
use Index\Data\IDbConnection;
|
||||||
use Index\Data\IDbStatement;
|
use Index\Data\IDbStatement;
|
||||||
use Index\Data\IDbResult;
|
use Index\Data\IDbResult;
|
||||||
use Index\Data\DbType;
|
|
||||||
use Hanyuu\StatementCache;
|
use Hanyuu\StatementCache;
|
||||||
use Hanyuu\Auth\IAuthMethod;
|
use Hanyuu\Auth\IAuthMethod;
|
||||||
use Hanyuu\Auth\Auth;
|
use Hanyuu\Auth\Auth;
|
||||||
use Hanyuu\Users\IUsers;
|
use Hanyuu\Users\UserInfo;
|
||||||
use Hanyuu\Users\IUserInfo;
|
use Hanyuu\Users\UserPasswordInfo;
|
||||||
use Hanyuu\Users\IUserPasswordInfo;
|
use Hanyuu\Users\UserTOTPInfo;
|
||||||
use Hanyuu\Users\IUserTOTPInfo;
|
|
||||||
use Hanyuu\Users\UserNotFoundException;
|
|
||||||
|
|
||||||
class DbUsers implements IUsers {
|
class Users {
|
||||||
private const USERS_TABLE = 'hau_users';
|
private const USERS_TABLE = 'hau_users';
|
||||||
private const TFA_TABLE = 'hau_users_tfa';
|
private const TFA_TABLE = 'hau_users_tfa';
|
||||||
private const BACKUP_TABLE = 'hau_users_backup';
|
private const BACKUP_TABLE = 'hau_users_backup';
|
||||||
|
@ -51,57 +48,57 @@ class DbUsers implements IUsers {
|
||||||
$this->stmts = new StatementCache($conn);
|
$this->stmts = new StatementCache($conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchUserInfoSingle(IDbResult $result, string $exceptionText): IUserInfo {
|
private function fetchUserInfoSingle(IDbResult $result, string $exceptionText): UserInfo {
|
||||||
if(!$result->next())
|
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 = $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();
|
$stmt->execute();
|
||||||
|
|
||||||
return $this->fetchUserInfoSingle($stmt->getResult(), 'no user with $userId found');
|
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 = $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();
|
$stmt->execute();
|
||||||
|
|
||||||
return $this->fetchUserInfoSingle($stmt->getResult(), 'no user with $userName found');
|
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 = $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();
|
$stmt->execute();
|
||||||
$result = $stmt->getResult();
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
return $result->next() ? $result->getInteger(0) : 0;
|
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 = $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();
|
$stmt->execute();
|
||||||
|
|
||||||
$result = $stmt->getResult();
|
$result = $stmt->getResult();
|
||||||
$array = [];
|
$array = [];
|
||||||
|
|
||||||
while($result->next())
|
while($result->next())
|
||||||
$array[] = new DbUserAuthInfo($result);
|
$array[] = new UserAuthInfo($result);
|
||||||
|
|
||||||
return $array;
|
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 = $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(1, $userInfo->getId());
|
||||||
$stmt->addParameter(2, $method->getName(), DbType::STRING);
|
$stmt->addParameter(2, $method->getName());
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$result = $stmt->getResult();
|
$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 = $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();
|
$stmt->execute();
|
||||||
$result = $stmt->getResult();
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
if(!$result->next())
|
if(!$result->next())
|
||||||
throw new RuntimeException('no password info for $userInfo found');
|
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 = $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();
|
$stmt->execute();
|
||||||
$result = $stmt->getResult();
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
if(!$result->next())
|
if(!$result->next())
|
||||||
throw new RuntimeException('no totp info for $userInfo found');
|
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 = $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();
|
$stmt->execute();
|
||||||
$result = $stmt->getResult();
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
$array = [];
|
$array = [];
|
||||||
|
|
||||||
while($result->next())
|
while($result->next())
|
||||||
$array[] = new DbUserEMailInfo($result);
|
$array[] = new UserEMailInfo($result);
|
||||||
|
|
||||||
return $array;
|
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 = $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();
|
$stmt->execute();
|
||||||
$result = $stmt->getResult();
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
$array = [];
|
$array = [];
|
||||||
|
|
||||||
while($result->next())
|
while($result->next())
|
||||||
$array[] = new DbUserBackupInfo($result);
|
$array[] = new UserBackupInfo($result);
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
Loading…
Reference in a new issue