Compare commits
10 Commits
eb3e5eb043
...
aebe0c7549
| Author | SHA1 | Date | |
|---|---|---|---|
| aebe0c7549 | |||
| ced7937583 | |||
| 09523a5509 | |||
| 51a94fea19 | |||
| 14359b9de1 | |||
| 90254a43bb | |||
| 7e04e37bb1 | |||
| d51eedd7bd | |||
| 4b30283697 | |||
| acafa80454 |
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="database.sqlite" uuid="b6993544-8eca-46b7-9a38-b649d2563add">
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/storage/database.sqlite</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+1
-1
@@ -33,7 +33,7 @@
|
|||||||
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
|
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
|
||||||
</include_path>
|
</include_path>
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
|
<component name="PhpProjectSharedConfiguration" php_language_level="8.5">
|
||||||
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpStanOptionsConfiguration">
|
<component name="PhpStanOptionsConfiguration">
|
||||||
|
|||||||
+64
-13
@@ -14,35 +14,86 @@ use Symfony\Component\Routing\RequestContext;
|
|||||||
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
||||||
use ComCen\Http\LoginController;
|
use ComCen\Http\LoginController;
|
||||||
use ComCen\Http\RegisterController;
|
use ComCen\Http\RegisterController;
|
||||||
|
use ComCen\Database\Handler;
|
||||||
|
use \ComCen\Security\TokenHandler;
|
||||||
|
|
||||||
class WebSocketServer implements MessageComponentInterface
|
class WebSocketServer implements MessageComponentInterface
|
||||||
{
|
{
|
||||||
private array $connectedUsers = [];
|
private $connectionsData = [];
|
||||||
|
|
||||||
|
private function isSameConnection(ConnectionInterface $connection1, ConnectionInterface $connection2): bool
|
||||||
|
{
|
||||||
|
return $connection1->resourceId === $connection2->resourceId;
|
||||||
|
}
|
||||||
|
private function getConnectionIndex(ConnectionInterface $connection): int | null
|
||||||
|
{
|
||||||
|
foreach ($this->connectionsData as $i => $connectionData) {
|
||||||
|
if ($this->isSameConnection($connection, $connectionData["connection"])) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
private function sendToAllAuthenticated(string $username, string $msg): void
|
||||||
|
{
|
||||||
|
foreach ($this->connectionsData as $connectionData) {
|
||||||
|
if ($connectionData["username"] !== $username) {
|
||||||
|
$connectionData["connection"]->send("{\"sender\": \"{$username}\",\"msg\": \"{$msg}\"}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private function deleteGivenId(int $id): void
|
private function deleteGivenId(int $id): void
|
||||||
{
|
{
|
||||||
foreach ($this->connectedUsers as $key => $conn) {
|
foreach ($this->connectionsData as $i => $connectionData) {
|
||||||
if ($conn->resourceId === $id) {
|
if ($connectionData["connection"]->resourceId === $id) {
|
||||||
unset($this->connectedUsers[$key]);
|
array_splice($this->connectionsData, $i, 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onOpen(ConnectionInterface $conn): void
|
public function onOpen(ConnectionInterface $conn): void
|
||||||
{
|
{
|
||||||
$this->connectedUsers[] = $conn;
|
$this->connectionsData[] = [
|
||||||
$conn->send("Connected users: " . count($this->connectedUsers) . " (One is you)");
|
"connection" => $conn,
|
||||||
|
"username" => null
|
||||||
|
];
|
||||||
echo "New connection: {$conn->resourceId}\n";
|
echo "New connection: {$conn->resourceId}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onMessage(ConnectionInterface $from, $msg): void
|
public function onMessage(ConnectionInterface $from, $msg): void
|
||||||
{
|
{
|
||||||
foreach ($this->connectedUsers as $conn) {
|
$decodedMsg = json_decode($msg, true);
|
||||||
if ($from->resourceId !== $conn->resourceId) {
|
if (!$decodedMsg) {
|
||||||
$conn->send("From user " . $from->resourceId . ": " . $msg);
|
$from->send("{\"error\"}: \"not or empty json\"");
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
$from->send("Message sent to others");
|
$index = $this->getConnectionIndex($from);
|
||||||
|
if ($index === null) return;
|
||||||
|
|
||||||
|
if ($this->connectionsData[$index]["username"]) {
|
||||||
|
$msgContent = $decodedMsg["message"] ?? null;
|
||||||
|
if ($msgContent) {
|
||||||
|
$this->sendToAllAuthenticated($this->connectionsData[$index]["username"], $msgContent);
|
||||||
|
$from->send("{\"success\"}: \"message send\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$from->send("{\"error\"}: \"no message\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $decodedMsg["token"] ?? null;
|
||||||
|
if ($token) {
|
||||||
|
$tokenUser = TokenHandler::getTokenOwnership($token);
|
||||||
|
if ($tokenUser) {
|
||||||
|
$this->connectionsData[$index]["username"] = $tokenUser;
|
||||||
|
$from->send("{\"success\"}: \"authenticated\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$from->send("{\"error\"}: \"invalid token\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$from->send("{\"error\"}: \"you are not authenticated\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onClose(ConnectionInterface $conn): void
|
public function onClose(ConnectionInterface $conn): void
|
||||||
@@ -54,6 +105,7 @@ class WebSocketServer implements MessageComponentInterface
|
|||||||
public function onError(ConnectionInterface $conn, \Exception $e): void
|
public function onError(ConnectionInterface $conn, \Exception $e): void
|
||||||
{
|
{
|
||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
|
$this->deleteGivenId($conn->resourceId);
|
||||||
$this->onClose($conn);
|
$this->onClose($conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,8 +117,7 @@ $routes->add("ws", new Route("/ws", ["_controller" => new WsServer(n
|
|||||||
|
|
||||||
$server = IoServer::factory(
|
$server = IoServer::factory(
|
||||||
new HttpServer(new Router(new UrlMatcher($routes, new RequestContext()))),
|
new HttpServer(new Router(new UrlMatcher($routes, new RequestContext()))),
|
||||||
8080,
|
8080
|
||||||
"0.0.0.0"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
echo "Server running on http://localhost:8080\n";
|
echo "Server running on http://localhost:8080\n";
|
||||||
|
|||||||
+110
-28
@@ -5,46 +5,128 @@ require __DIR__ . '/../vendor/autoload.php';
|
|||||||
use ComCen\Html\Html;
|
use ComCen\Html\Html;
|
||||||
|
|
||||||
$html = new Html();
|
$html = new Html();
|
||||||
$html->content .= "
|
$html->content = <<<'HTML'
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang=\"en\">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset=\"UTF-8\">
|
<meta charset="UTF-8">
|
||||||
<title>Chat</title>
|
<title>ComCen</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: monospace; max-width: 600px; margin: 40px auto; padding: 0 16px; }
|
||||||
|
#log { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 8px; margin-bottom: 8px; background: #f9f9f9; }
|
||||||
|
.log-entry { margin: 2px 0; }
|
||||||
|
.log-system { color: #888; }
|
||||||
|
.log-error { color: #c00; }
|
||||||
|
input, button { font-family: monospace; font-size: 14px; }
|
||||||
|
#token { width: 100%; box-sizing: border-box; margin-bottom: 8px; padding: 4px; }
|
||||||
|
#msg-row { display: flex; gap: 8px; }
|
||||||
|
#message { flex: 1; padding: 4px; }
|
||||||
|
button { padding: 4px 12px; cursor: pointer; }
|
||||||
|
button:disabled { cursor: not-allowed; opacity: 0.5; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>WebSocket Test</h2>
|
<h2>ComCen</h2>
|
||||||
<div id=\"log\"></div>
|
|
||||||
<input id=\"msg\" type=\"text\" placeholder=\"Type a message...\" />
|
<input id="token" type="text" placeholder="Paste token from curl login/register here" />
|
||||||
<button onclick=\"send()\">Send</button>
|
<div style="display:flex; gap:8px; margin-bottom:8px;">
|
||||||
<button onclick='closeConnection()'>Disconnect</button>
|
<button id="connect-btn" onclick="connect()">Connect</button>
|
||||||
|
<button id="disconnect-btn" onclick="disconnect()" disabled>Disconnect</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="log"></div>
|
||||||
|
|
||||||
|
<div id="msg-row">
|
||||||
|
<input id="message" type="text" placeholder="Message..." disabled onkeydown="if(event.key==='Enter') send()" />
|
||||||
|
<button id="send-btn" onclick="send()" disabled>Send</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const log = document.getElementById('log');
|
let ws = null;
|
||||||
const ws = new WebSocket('ws://localhost:8080/ws');
|
let authenticated = false;
|
||||||
|
|
||||||
ws.onopen = () => append('Connected');
|
function log(text, type = '') {
|
||||||
ws.onmessage = e => append('Server: ' + e.data);
|
const log = document.getElementById('log');
|
||||||
ws.onclose = () => append('Disconnected');
|
const div = document.createElement('div');
|
||||||
ws.onerror = e => append('Error: ' + e.message);
|
div.className = 'log-entry' + (type ? ' log-' + type : '');
|
||||||
|
div.textContent = text;
|
||||||
|
log.appendChild(div);
|
||||||
|
log.scrollTop = log.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConnected(connected) {
|
||||||
|
document.getElementById('connect-btn').disabled = connected;
|
||||||
|
document.getElementById('disconnect-btn').disabled = !connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAuthenticated(auth) {
|
||||||
|
authenticated = auth;
|
||||||
|
document.getElementById('message').disabled = !auth;
|
||||||
|
document.getElementById('send-btn').disabled = !auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
const token = document.getElementById('token').value.trim();
|
||||||
|
if (!token) {
|
||||||
|
log('Enter a token first.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws = new WebSocket('ws://localhost:8080/ws');
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
log('Connected. Authenticating...', 'system');
|
||||||
|
setConnected(true);
|
||||||
|
ws.send(JSON.stringify({ token }));
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
const text = event.data;
|
||||||
|
if (text === '{"success"}: "authenticated"') {
|
||||||
|
log('Authenticated.', 'system');
|
||||||
|
setAuthenticated(true);
|
||||||
|
} else if (text === '{"error"}: "invalid token"') {
|
||||||
|
log('Invalid token. Connection closed.', 'error');
|
||||||
|
ws.close();
|
||||||
|
} else if (text === '{"error"}: "you are not authenticated"') {
|
||||||
|
log('Server: not authenticated', 'error');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
log(`${data.sender}: ${data.msg}`);
|
||||||
|
} catch {
|
||||||
|
log(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
log('Disconnected.', 'system');
|
||||||
|
setConnected(false);
|
||||||
|
setAuthenticated(false);
|
||||||
|
ws = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = () => {
|
||||||
|
log('WebSocket error.', 'error');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnect() {
|
||||||
|
if (ws) ws.close();
|
||||||
|
}
|
||||||
|
|
||||||
function send() {
|
function send() {
|
||||||
const input = document.getElementById('msg');
|
const input = document.getElementById('message');
|
||||||
ws.send(input.value);
|
const text = input.value.trim();
|
||||||
append('You: ' + input.value);
|
if (!text || !ws || !authenticated) return;
|
||||||
|
ws.send(JSON.stringify({ message: text }));
|
||||||
|
log(`You: ${text}`);
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function append(text) {
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.textContent = text;
|
|
||||||
log.appendChild(p);
|
|
||||||
}
|
|
||||||
function closeConnection() {
|
|
||||||
ws.close();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
";
|
|
||||||
|
HTML;
|
||||||
$html->renderContent();
|
$html->renderContent();
|
||||||
|
|||||||
@@ -9,20 +9,38 @@ class Handler
|
|||||||
private static ?Handler $instance = null;
|
private static ?Handler $instance = null;
|
||||||
private PDO $pdo;
|
private PDO $pdo;
|
||||||
|
|
||||||
|
public static function getInstance(): static
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new static();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
private function __construct()
|
private function __construct()
|
||||||
{
|
{
|
||||||
$this->pdo = new PDO('sqlite:' . __DIR__ . '/../../storage/database.sqlite');
|
$this->pdo = new PDO('sqlite:' . __DIR__ . '/../../storage/database.sqlite');
|
||||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
}
|
|
||||||
|
|
||||||
public function init(): void
|
|
||||||
{
|
|
||||||
$this->pdo->exec("
|
$this->pdo->exec("
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
username VARCHAR(32) NOT NULL UNIQUE,
|
username VARCHAR(32) NOT NULL UNIQUE,
|
||||||
password CHAR(255) NOT NULL
|
password CHAR(255) NOT NULL
|
||||||
)
|
)
|
||||||
");
|
");
|
||||||
|
$this->pdo->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS chat_groups (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(64) NOT NULL UNIQUE
|
||||||
|
)
|
||||||
|
");
|
||||||
|
$this->pdo->exec("
|
||||||
|
CREATE TABLE IF NOT EXISTS group_members (
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
username VARCHAR(32) NOT NULL,
|
||||||
|
PRIMARY KEY (group_id, username),
|
||||||
|
FOREIGN KEY (username) REFERENCES users(username)
|
||||||
|
)
|
||||||
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addUser(string $username, string $password): void
|
public function addUser(string $username, string $password): void
|
||||||
@@ -42,11 +60,11 @@ class Handler
|
|||||||
}
|
}
|
||||||
public function userExists(string $username): bool
|
public function userExists(string $username): bool
|
||||||
{
|
{
|
||||||
$statement = $this->pdo->prepare("SELECT * FROM users WHERE username = :username");
|
$statement = $this->pdo->prepare("SELECT 1 FROM users WHERE username = :username");
|
||||||
$statement->execute([
|
$statement->execute([
|
||||||
$username
|
"username" => $username
|
||||||
]);
|
]);
|
||||||
return $statement->rowCount() > 0;
|
return (bool) $statement->fetchColumn();
|
||||||
}
|
}
|
||||||
public function getPasswordHash(string $username): string
|
public function getPasswordHash(string $username): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace ComCen\Http;
|
namespace ComCen\Http;
|
||||||
|
|
||||||
use ComCen\Database\Handler;
|
use ComCen\Database\Handler;
|
||||||
|
use ComCen\Security\TokenHandler;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
use Ratchet\Http\HttpServerInterface;
|
use Ratchet\Http\HttpServerInterface;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Psr\Http\Message\RequestInterface;
|
||||||
@@ -26,19 +27,25 @@ class LoginController implements HttpServerInterface
|
|||||||
$responseHead = "400";
|
$responseHead = "400";
|
||||||
$json = json_encode(["error" => "Not enough params"]);
|
$json = json_encode(["error" => "Not enough params"]);
|
||||||
}
|
}
|
||||||
else if (password_verify($password, Handler::class->getPasswordHash($username)))
|
else if (!Handler::getInstance()->userExists($username) || !password_verify($password, Handler::getInstance()->getPasswordHash($username)))
|
||||||
|
|
||||||
if (!$login)
|
|
||||||
{
|
{
|
||||||
Utils::class->responeJson($conn, $responseHead, $json);
|
$login = false;
|
||||||
|
$responseHead = "409";
|
||||||
|
$json = json_encode(["error" => "Bad"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$login) {
|
||||||
|
Utils::responeJson($conn, $responseHead, $json);
|
||||||
$conn->close();
|
$conn->close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Handler::class->addUser($username, $password);
|
if (TokenHandler::doesUserHaveToken($username)) {
|
||||||
|
TokenHandler::deleteTokensForUser($username);
|
||||||
|
}
|
||||||
|
|
||||||
$json = json_encode(["error" => "none"]);
|
$json = json_encode(["token" => TokenHandler::getNewTokenForUser($username)]);
|
||||||
Utils::class->responeJson($conn, "200", $json);
|
Utils::responeJson($conn, "200", $json);
|
||||||
$conn->close();
|
$conn->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ class RegisterController implements HttpServerInterface
|
|||||||
$responseHead = "400";
|
$responseHead = "400";
|
||||||
$json = json_encode(["error" => "Not enough params"]);
|
$json = json_encode(["error" => "Not enough params"]);
|
||||||
}
|
}
|
||||||
else if (count($password) < 5)
|
else if (strlen($password) < 5)
|
||||||
{
|
{
|
||||||
$createAccount = false;
|
$createAccount = false;
|
||||||
$responseHead = "400";
|
$responseHead = "400";
|
||||||
$json = json_encode(["error" => "Short password"]);
|
$json = json_encode(["error" => "Short password"]);
|
||||||
}
|
}
|
||||||
else if (Handler::class->userExists($username))
|
else if (Handler::getInstance()->userExists($username))
|
||||||
{
|
{
|
||||||
$createAccount = false;
|
$createAccount = false;
|
||||||
$responseHead = "409";
|
$responseHead = "409";
|
||||||
@@ -41,15 +41,15 @@ class RegisterController implements HttpServerInterface
|
|||||||
|
|
||||||
if (!$createAccount)
|
if (!$createAccount)
|
||||||
{
|
{
|
||||||
Utils::class->responeJson($conn, $responseHead, $json);
|
Utils::responeJson($conn, $responseHead, $json);
|
||||||
$conn->close();
|
$conn->close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Handler::class->addUser($username, $password);
|
Handler::getInstance()->addUser($username, $password);
|
||||||
|
|
||||||
$json = json_encode(["error" => "none"]);
|
$json = json_encode(["error" => "none"]);
|
||||||
Utils::class->responeJson($conn, "200", $json);
|
Utils::responeJson($conn, "200", $json);
|
||||||
$conn->close();
|
$conn->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ use Ratchet\ConnectionInterface;
|
|||||||
|
|
||||||
class Utils
|
class Utils
|
||||||
{
|
{
|
||||||
function responeJson(ConnectionInterface $conn, string $head, string $jsonData): void
|
static function responeJson(ConnectionInterface $conn, string $head, string $jsonData): void
|
||||||
{
|
{
|
||||||
$conn->send("HTTP/1.1 {$head}\r\nContent-Type: application/json\r\n\r\n{$jsonData}");
|
$conn->send("HTTP/1.1 {$head}\r\nContent-Type: application/json\r\n\r\n{$jsonData}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,54 @@ class TokenHandler
|
|||||||
{
|
{
|
||||||
private static ?self $instance = null;
|
private static ?self $instance = null;
|
||||||
private static $tokens = [];
|
private static $tokens = [];
|
||||||
|
private static int $iterations = 0;
|
||||||
|
|
||||||
private static function uuid_v4(): string {
|
private static function random32Characters(): string {
|
||||||
$data = random_bytes(16);
|
$data = random_bytes(16);
|
||||||
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
||||||
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
||||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
return bin2hex($data);
|
||||||
}
|
}
|
||||||
public static function getNewTokenForUser(string $username)
|
public static function doesUserHaveToken(string $username): bool
|
||||||
{
|
{
|
||||||
$uuid = sprintf(
|
return array_any(self::$tokens, fn($token) => $token[0] === $username);
|
||||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
}
|
||||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
public static function getNewTokenForUser(string $username): string
|
||||||
mt_rand(0, 0xffff),
|
{
|
||||||
mt_rand(0, 0x0fff) | 0x4000,
|
$tokenBody = self::random32Characters() . str_pad(self::$iterations++, 5, '0');
|
||||||
mt_rand(0, 0x3fff) | 0x8000,
|
if (self::$iterations >= 99999) {
|
||||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
self::$iterations = 0;
|
||||||
);
|
}
|
||||||
|
$timestamp = microtime(true) * 10000;
|
||||||
|
self::$tokens[] = [$username, $timestamp, $tokenBody];
|
||||||
|
return $timestamp . $tokenBody;
|
||||||
|
}
|
||||||
|
public static function getTokenOwnership(string $controlledToken): string | null
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < count(self::$tokens); ++$i) {
|
||||||
|
$token = self::$tokens[$i];
|
||||||
|
if ($token[1] . $token[2] === $controlledToken) {
|
||||||
|
return $token[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public static function deleteOldTokens(): void
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < count(self::$tokens); ++$i) {
|
||||||
|
$token = self::$tokens[$i];
|
||||||
|
// 1 hour
|
||||||
|
if (time() - ($token[1] / 10000) > 3600) {
|
||||||
|
array_splice(self::$tokens, $i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static function deleteTokensForUser(string $user): void
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < count(self::$tokens); ++$i) {
|
||||||
|
if (self::$tokens[$i][0] === $user) {
|
||||||
|
array_splice(self::$tokens, $i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user