2023-07-22 21:20:03 +00:00
|
|
|
<?php
|
|
|
|
namespace Misuzu\Auth;
|
|
|
|
|
|
|
|
use InvalidArgumentException;
|
|
|
|
use RuntimeException;
|
|
|
|
use Index\Data\DbStatementCache;
|
|
|
|
use Index\Data\IDbConnection;
|
|
|
|
use Index\Net\IPAddress;
|
|
|
|
use Index\Serialisation\Base32;
|
|
|
|
use Misuzu\ClientInfo;
|
|
|
|
use Misuzu\Pagination;
|
2023-08-02 22:12:47 +00:00
|
|
|
use Misuzu\Users\UserInfo;
|
2023-07-22 21:20:03 +00:00
|
|
|
|
|
|
|
class RecoveryTokens {
|
|
|
|
private DbStatementCache $cache;
|
|
|
|
|
|
|
|
public function __construct(IDbConnection $dbConn) {
|
|
|
|
$this->cache = new DbStatementCache($dbConn);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function generateCode(): string {
|
|
|
|
// results in a 12 char code
|
|
|
|
return Base32::encode(random_bytes(7));
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getToken(
|
2023-08-02 22:12:47 +00:00
|
|
|
UserInfo|string|null $userInfo = null,
|
2023-07-22 21:20:03 +00:00
|
|
|
IPAddress|string|null $remoteAddr = null,
|
|
|
|
?string $verifyCode = null,
|
|
|
|
?bool $isUnused = null
|
|
|
|
): RecoveryTokenInfo {
|
2023-08-02 22:12:47 +00:00
|
|
|
if($userInfo instanceof UserInfo)
|
|
|
|
$userInfo = $userInfo->getId();
|
2023-07-22 21:20:03 +00:00
|
|
|
if($remoteAddr instanceof IPAddress)
|
|
|
|
$remoteAddr = (string)$remoteAddr;
|
|
|
|
|
|
|
|
$hasUserInfo = $userInfo !== null;
|
|
|
|
$hasRemoteAddr = $remoteAddr !== null;
|
|
|
|
$hasVerifyCode = $verifyCode !== null;
|
|
|
|
$hasIsUnused = $isUnused !== null;
|
|
|
|
|
|
|
|
if(!$hasUserInfo && !$hasRemoteAddr && !$hasVerifyCode)
|
|
|
|
throw new InvalidArgumentException('At least one argument must be correctly specified.');
|
|
|
|
if($hasIsUnused && $hasVerifyCode)
|
|
|
|
throw new InvalidArgumentException('You may not specify $isUnused and $verifyCode at the same time.');
|
|
|
|
|
|
|
|
$args = 0;
|
|
|
|
$query = 'SELECT user_id, INET6_NTOA(reset_ip), UNIX_TIMESTAMP(reset_requested), verification_code FROM msz_users_password_resets';
|
|
|
|
if($hasUserInfo) {
|
|
|
|
++$args;
|
|
|
|
$query .= ' WHERE user_id = ?';
|
|
|
|
}
|
|
|
|
if($hasRemoteAddr)
|
|
|
|
$query .= sprintf(' %s reset_ip = INET6_ATON(?)', ++$args > 1 ? 'AND' : 'WHERE');
|
|
|
|
if($hasIsUnused)
|
|
|
|
$query .= sprintf(' %s verification_code %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $isUnused ? 'IS NOT' : 'IS');
|
|
|
|
elseif($hasVerifyCode)
|
|
|
|
$query .= sprintf(' %s verification_code = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
|
|
|
|
|
|
|
$args = 0;
|
|
|
|
$stmt = $this->cache->get($query);
|
|
|
|
if($hasUserInfo)
|
|
|
|
$stmt->addParameter(++$args, $userInfo);
|
|
|
|
if($hasRemoteAddr)
|
|
|
|
$stmt->addParameter(++$args, $remoteAddr);
|
|
|
|
if($hasVerifyCode)
|
|
|
|
$stmt->addParameter(++$args, $verifyCode);
|
|
|
|
$stmt->execute();
|
|
|
|
$result = $stmt->getResult();
|
|
|
|
|
|
|
|
if(!$result->next())
|
|
|
|
throw new RuntimeException('Recovery token not found.');
|
|
|
|
|
|
|
|
return new RecoveryTokenInfo($result);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function createToken(
|
2023-08-02 22:12:47 +00:00
|
|
|
UserInfo|string $userInfo,
|
2023-07-22 21:20:03 +00:00
|
|
|
IPAddress|string $remoteAddr
|
|
|
|
): RecoveryTokenInfo {
|
2023-08-02 22:12:47 +00:00
|
|
|
if($userInfo instanceof UserInfo)
|
|
|
|
$userInfo = $userInfo->getId();
|
2023-07-22 21:20:03 +00:00
|
|
|
if($remoteAddr instanceof IPAddress)
|
|
|
|
$remoteAddr = (string)$remoteAddr;
|
|
|
|
$verifyCode = self::generateCode();
|
|
|
|
|
|
|
|
$stmt = $this->cache->get('INSERT INTO msz_users_password_resets (user_id, reset_ip, verification_code) VALUES (?, INET6_ATON(?), ?)');
|
|
|
|
$stmt->addParameter(1, $userInfo);
|
|
|
|
$stmt->addParameter(2, $remoteAddr);
|
|
|
|
$stmt->addParameter(3, $verifyCode);
|
|
|
|
$stmt->execute();
|
|
|
|
|
|
|
|
return $this->getToken(verifyCode: $verifyCode);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function invalidateToken(RecoveryTokenInfo $tokenInfo): void {
|
|
|
|
if(!$tokenInfo->hasCode())
|
|
|
|
return;
|
|
|
|
|
|
|
|
$stmt = $this->cache->get('UPDATE msz_users_password_resets SET verification_code = NULL WHERE verification_code = ? AND user_id = ?');
|
|
|
|
$stmt->addParameter(1, $tokenInfo->getCode());
|
|
|
|
$stmt->addParameter(2, $tokenInfo->getUserId());
|
|
|
|
$stmt->execute();
|
|
|
|
}
|
|
|
|
}
|