начало работы над сессиями и там дохуя чего ещё

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2023-02-25 15:06:59 +10:00
parent ed5756d065
commit b2a78519e4
39 changed files with 3692 additions and 3609 deletions

View File

@ -1,57 +1,56 @@
{ {
"name": "mirzaev/site-account", "name": "mirzaev/site-account",
"description": "API for intersite authentication", "description": "API for intersite authentication",
"readme": "README.md", "readme": "README.md",
"keywords": [ "keywords": [
"site", "site",
"api", "api",
"authentication", "authentication"
"auth" ],
], "type": "site",
"type": "site", "homepage": "https://git.mirzaev.sexy/mirzaev/site-account",
"homepage": "https://git.mirzaev.sexy/mirzaev/site-account", "license": "WTFPL",
"license": "WTFPL", "authors": [
"authors": [ {
{ "name": "Arsen Mirzaev Tatyano-Muradovich",
"name": "Arsen Mirzaev Tatyano-Muradovich", "email": "arsen@mirzaev.sexy",
"email": "arsen@mirzaev.sexy", "homepage": "https://mirzaev.sexy",
"homepage": "https://mirzaev.sexy", "role": "Programmer"
"role": "Programmer"
}
],
"support": {
"email": "arsen@mirzaev.sexy",
"wiki": "https://git.mirzaev.sexy/mirzaev/site-account/wiki",
"issues": "https://git.mirzaev.sexy/mirzaev/site-account/issues"
},
"funding": [
{
"type": "funding",
"url": "https://fund.mirzaev.sexy"
}
],
"require": {
"php": "~8.1",
"ext-sodium": "~8.1",
"mirzaev/minimal": "^2.0.x-dev",
"mirzaev/accounts": "~1.2.x-dev",
"mirzaev/arangodb": "^1.0.0",
"mirzaev/vk": "^5.0",
"triagens/arangodb": "~3.9.x-dev",
"twig/twig": "^3.4",
"guzzlehttp/guzzle": "^7.5"
},
"require-dev": {
"phpunit/phpunit": "~9.5"
},
"autoload": {
"psr-4": {
"mirzaev\\site\\account\\": "mirzaev/site/account/system"
}
},
"autoload-dev": {
"psr-4": {
"mirzaev\\site\\account\\tests\\": "mirzaev/site/account/tests"
}
} }
],
"support": {
"email": "arsen@mirzaev.sexy",
"wiki": "https://git.mirzaev.sexy/mirzaev/site-account/wiki",
"issues": "https://git.mirzaev.sexy/mirzaev/site-account/issues"
},
"funding": [
{
"type": "funding",
"url": "https://fund.mirzaev.sexy"
}
],
"require": {
"php": "~8.2",
"ext-sodium": "~8.2",
"mirzaev/minimal": "^2.0.x-dev",
"mirzaev/accounts": "~1.2.x-dev",
"mirzaev/arangodb": "^1.0.0",
"mirzaev/vk": "^5.0",
"triagens/arangodb": "~3.9.x-dev",
"twig/twig": "^3.4",
"guzzlehttp/guzzle": "^7.5"
},
"require-dev": {
"phpunit/phpunit": "~9.5"
},
"autoload": {
"psr-4": {
"mirzaev\\site\\account\\": "mirzaev/site/account/system"
}
},
"autoload-dev": {
"psr-4": {
"mirzaev\\site\\account\\tests\\": "mirzaev/site/account/tests"
}
}
} }

View File

@ -61,7 +61,7 @@ class core extends controller
$expires = time() + 604800; $expires = time() + 604800;
// Инициализация сессии (без журналирования) // Инициализация сессии (без журналирования)
$this->variables['session'] = session::initialization($_COOKIE["session"] ?? null, $expires) ?? header('Location: https://mirzaev.sexy/error?code=500&text=Не+удалось+инициализировать+сессию'); $this->variables['session'] = new session($_COOKIE["session"] ?? null, $expires) ?? header('Location: https://mirzaev.sexy/error?code=500&text=Не+удалось+инициализировать+сессию');
if ($_COOKIE["session"] ?? null !== $this->variables['session']->hash) { if ($_COOKIE["session"] ?? null !== $this->variables['session']->hash) {
// Изменился хеш сессии (подразумевается, что сессия устарела) // Изменился хеш сессии (подразумевается, что сессия устарела)
@ -78,7 +78,7 @@ class core extends controller
} }
// Инициализация аккаунта (без журналирования) // Инициализация аккаунта (без журналирования)
$this->variables['account'] = session::account($this->variables['session']); $this->variables['account'] = $this->variables['session']->account();
if ($this->variables['account'] instanceof _document) { if ($this->variables['account'] instanceof _document) {
// Инициализирован аккаунт // Инициализирован аккаунт

View File

@ -15,71 +15,66 @@ use mirzaev\site\account\controllers\core;
*/ */
final class index_controller extends core final class index_controller extends core
{ {
/** /**
* Главная страница * Главная страница
* *
* @param array $parameters Параметры запроса * @param array $parameters Параметры запроса
*/ */
public function index(array $parameters = []): ?string public function index(array $parameters = []): ?string
{ {
// Инициализация загружаемых категорий // Инициализация загружаемых категорий
$this->variables['include'] = [ $this->variables['include'] = [
'head' => ['self'], 'head' => ['self'],
'body' => ['self'] 'body' => ['self']
]; ];
// Инициализация бегущей строки // Инициализация бегущей строки
$this->variables['hotline'] = [ $this->variables['hotline'] = [
'id' => $this->variables['request']['id'] ?? 'hotline' 'id' => $this->variables['request']['id'] ?? 'hotline'
]; ];
// Инициализация параметров бегущей строки // Инициализация параметров бегущей строки
$this->variables['hotline']['parameters'] = [ $this->variables['hotline']['parameters'] = [
// 'step' => 2 // 'step' => 2
]; ];
// Инициализация аттрибутов бегущей строки // Инициализация аттрибутов бегущей строки
$this->variables['hotline']['attributes'] = [ $this->variables['hotline']['attributes'] = [];
]; // Инициализация элементов бегущей строки
$this->variables['hotline']['elements'] = [
['content' => '1'],
[
'tag' => 'article',
'content' => '2'
],
['content' => '3'],
['content' => '4'],
['content' => '5'],
['content' => '6'],
['content' => '7'],
['content' => '8'],
['content' => '9'],
['content' => '10'],
['content' => '11'],
['content' => '12'],
['content' => '13'],
['content' => '14'],
['content' => '15']
];
// Инициализация элементов бегущей строки // Инициализация бегущей строки
$this->variables['hotline']['elements'] = [ $this->variables['graph'] = [
['content' => '1'], 'id' => $this->variables['request']['id'] ?? 'graph'
[ ];
'tag' => 'article',
'content' => '2'
],
['content' => '3'],
['content' => '4'],
['content' => '5'],
['content' => '6'],
['content' => '7'],
['content' => '8'],
['content' => '9'],
['content' => '10'],
['content' => '11'],
['content' => '12'],
['content' => '13'],
['content' => '14'],
['content' => '15']
];
// Инициализация бегущей строки // Инициализация аттрибутов бегущей строки
$this->variables['graph'] = [ $this->variables['graph']['attributes'] = [];
'id' => $this->variables['request']['id'] ?? 'graph'
];
// Инициализация аттрибутов бегущей строки // Инициализация элементов бегущей строки
$this->variables['graph']['attributes'] = [ $this->variables['graph']['elements'] = [];
]; // Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', $this->variables);
// Инициализация элементов бегущей строки }
$this->variables['graph']['elements'] = [
];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', $this->variables);
}
} }

View File

@ -9,7 +9,7 @@ use mirzaev\site\account\models\account_model as account;
// Фреймворк ArangoDB // Фреймворк ArangoDB
use mirzaev\arangodb\collection, use mirzaev\arangodb\collection,
mirzaev\arangodb\document; mirzaev\arangodb\document;
// Библиотека для ArangoDB // Библиотека для ArangoDB
use ArangoDBClient\Document as _document; use ArangoDBClient\Document as _document;
@ -25,189 +25,225 @@ use exception;
*/ */
final class session_model extends core final class session_model extends core
{ {
/** /**
* Коллекция * Коллекция
*/ */
public const COLLECTION = 'session'; public const COLLECTION = 'session';
/** /**
* Инициализация * Данные сессии из базы данных
* */
* @param ?string $hash Хеш сессии в базе данных public _document $document;
* @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии)
* @param array &$errors Журнал ошибок
*
* @return ?_document Инстанция сессии, если удалось найти или создать
*/
public static function initialization(?string $hash = null, ?int $expires = null, array &$errors = []): ?_document
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
if (isset($hash) && $session = collection::search(static::$db->session, sprintf( /**
<<<AQL * Конструктор
FOR d IN %s *
FILTER d.hash == '$hash' && d.expires > %d * Инициализация сессии и запись в свойство $this->document
RETURN d *
AQL, * @param ?string $hash Хеш сессии в базе данных
self::COLLECTION, * @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии)
time() * @param array &$errors Журнал ошибок
))) { *
// Найдена сессия по хешу * @return static Инстанция сессии
*/
public function __construct(?string $hash = null, ?int $expires = null, array &$errors = [])
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Возврат сессии if (isset($hash) && $session = collection::search(static::$db->session, sprintf(
return $session; <<<AQL
} else if ($session = collection::search(static::$db->session, sprintf( FOR d IN %s
<<<AQL FILTER d.hash == '$hash' && d.expires > %d
FOR d IN %s RETURN d
FILTER d.ip == '%s' && d.expires > %d AQL,
RETURN d self::COLLECTION,
AQL, time()
self::COLLECTION, ))) {
$_SERVER['REMOTE_ADDR'], // Найдена сессия по хешу
time()
))) {
// Найдена сессия по данным пользователя
// Возврат сессии // Запись в свойство
return $session; $this->document = $session;
} else { } else if ($session = collection::search(static::$db->session, sprintf(
// Не найдена сессия <<<AQL
FOR d IN %s
FILTER d.ip == '%s' && d.expires > %d
RETURN d
AQL,
self::COLLECTION,
$_SERVER['REMOTE_ADDR'],
time()
))) {
// Найдена сессия по данным пользователя
// Запись сессии в базу данных // Запись в свойство
$_id = document::write(static::$db->session, self::COLLECTION, [ $this->document = $session;
'ip' => $_SERVER['REMOTE_ADDR'], } else {
'expires' => $expires ?? time() + 604800 // Не найдена сессия
]);
if ($session = collection::search(static::$db->session, sprintf( // Запись сессии в базу данных
<<<AQL $_id = document::write(static::$db->session, self::COLLECTION, [
FOR d IN %s 'ip' => $_SERVER['REMOTE_ADDR'],
FILTER d._id == '$_id' && d.expires > %d 'expires' => $expires ?? time() + 604800
RETURN d ]);
AQL,
self::COLLECTION,
time()
))) {
// Найдена созданная сессия
// Запись хеша if ($session = collection::search(static::$db->session, sprintf(
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id)); <<<AQL
FOR d IN %s
FILTER d._id == '$_id' && d.expires > %d
RETURN d
AQL,
self::COLLECTION,
time()
))) {
// Найдена созданная сессия
if (document::update(static::$db->session, $session)) { // Запись хеша
// Записано обновление $session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
return $session; if (document::update(static::$db->session, $session)) {
} else throw new exception('Не удалось записать данные сессии'); // Записано обновление
} else throw new exception('Не удалось создать или найти созданную сессию');
} // Запись в свойство
} else throw new exception('Не удалось инициализировать коллекцию'); $this->document = $session;
} catch (exception $e) { } else throw new exception('Не удалось записать данные сессии');
// Запись в журнал ошибок } else throw new exception('Не удалось создать или найти созданную сессию');
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
} }
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
}
return null; /**
* Связь сессии с аккаунтом
*
* @param _document $account Инстанция аккаунта
* @param array &$errors Журнал ошибок
*
* @return bool Статус выполнения
*/
public function connect(_document $account, array &$errors = []): bool
{
try {
if (
collection::init(static::$db->session, self::COLLECTION)
&& collection::init(static::$db->session, account::COLLECTION)
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
) {
// Инициализирована коллекция
if (document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
'_from' => $this->document->getId(),
'_to' => $account->getId()
])) {
// Создано ребро: session -> account
return true;
} else throw new exception('Не удалось создать ребро: session -> account');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
} }
/** return false;
* Связь сессии с аккаунтом }
*
* @param _document $session Инстанция сессии
* @param _document $account Инстанция аккаунта
* @param array &$errors Журнал ошибок
*
* @return bool Статус выполнения
*/
public static function connect(_document $session, _document $account, array &$errors = []): bool
{
try {
if (
collection::init(static::$db->session, self::COLLECTION)
&& collection::init(static::$db->session, account::COLLECTION)
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
) {
// Инициализирована коллекция
if (document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [ /**
'_from' => $session->getId(), * Поиск связанного аккаунта
'_to' => $account->getId() *
])) { * @param array &$errors Журнал ошибок
// Создано ребро: session -> account *
* @return ?_document Инстанция аккаунта, если удалось найти
*/
public function account(array &$errors = []): ?_document
{
try {
if (
collection::init(static::$db->session, self::COLLECTION)
&& collection::init(static::$db->session, account::COLLECTION)
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
) {
// Инициализированы коллекции
return true; if ($account = collection::search(static::$db->session, sprintf(
} else throw new exception('Не удалось создать ребро: session -> account'); <<<AQL
} else throw new exception('Не удалось инициализировать коллекцию'); FOR document IN %s
} catch (exception $e) { LET edge = (
// Запись в журнал ошибок FOR edge IN %s
$errors[] = [ FILTER edge._from == '%s'
'text' => $e->getMessage(), SORT edge._key DESC
'file' => $e->getFile(), LIMIT 1
'line' => $e->getLine(), RETURN edge
'stack' => $e->getTrace() )
]; FILTER document._id == edge[0]._to
} LIMIT 1
RETURN document
AQL,
account::COLLECTION,
self::COLLECTION . '_edge_' . account::COLLECTION,
$this->document->getId()
))) {
// Найден аккаунт
return false; return $account;
} else throw new exception('Не удалось найти аккаунт');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
} }
/** return null;
* Поиск связанного аккаунта }
*
* @param _document $session Инстанция сессии
* @param array &$errors Журнал ошибок
*
* @return ?_document Инстанция аккаунта, если удалось найти
*/
public static function account(_document $session, array &$errors = []): ?_document
{
try {
if (
collection::init(static::$db->session, self::COLLECTION)
&& collection::init(static::$db->session, account::COLLECTION)
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
) {
// Инициализированы коллекции
if ($account = collection::search(static::$db->session, sprintf( /**
<<<AQL * Записать
FOR document IN %s *
LET edge = ( * Ищет свойство, если не находит, то ищет его в инстанции документа сессии из базы данных,
FOR edge IN %s * затем записывает в него переданные данные. Инициализация новых свойств происходит в инстанции
FILTER edge._from == '%s' * документа сессии из базы данных
SORT edge._key DESC *
LIMIT 1 * @param string $name Название
RETURN edge * @param mixed $value Содержимое
) *
FILTER document._id == edge[0]._to * @return void
LIMIT 1 */
RETURN document public function __set(string $name, mixed $value = null): void
AQL, {
account::COLLECTION, if (isset($this->{$name})) $this->{$name} = $value;
self::COLLECTION . '_edge_' . account::COLLECTION, else $this->document->{$name} = $value;
$session->getId() }
))) {
// Найден аккаунт
return $account; /**
} else throw new exception('Не удалось найти аккаунт'); * Прочитать
} else throw new exception('Не удалось инициализировать коллекцию'); *
} catch (exception $e) { * Ищет свойство, если не находит, то ищет его в инстанции документа сессии из базы данных
// Запись в журнал ошибок *
$errors[] = [ * @param string $name Название
'text' => $e->getMessage(), *
'file' => $e->getFile(), * @return mixed Данные свойства инстанции сессии или инстанции документа сессии из базы данных
'line' => $e->getLine(), */
'stack' => $e->getTrace() public function __get(string $name): mixed
]; {
} return $this->{$name} ?? $this->document->{$name};
}
return null;
}
} }

View File

@ -1,4 +1,4 @@
@import url('/fonts/comissioner.ttf'); @import url('/fonts/commissioner.ttf');
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {

View File

@ -1,53 +0,0 @@
{% block css %}
<link type="text/css" rel="stylesheet" href="/css/account.css">
<link type="text/css" rel="stylesheet" href="/css/gradient.css">
{% endblock %}
{% block body %}
<section id="authentication">
{% if account %}
{{ account.getKey() }}
{% if vk %}
{{ vk.mail }}
{% endif %}
{% else %}
<section class="header gradient unselectable">
<div class="glare"></div>
<img class="avatar unselectable" src="/images/what.png" alt="Пользователь" draggable="false">
<a href="https://mirzaev.sexy">Нейрожурнал Мирзаева</a>
<div class="red"></div>
<div class="green"></div>
<div class="blue"></div>
<img class="cover unselectable" src="/images/heh.gif" alt="Нейрожурнал Мирзаева" draggable="false"></img>
</section>
<section class="body">
<ul>
<li>Подпункт 2.1.</li>
<li>Подпункт 2.2.
<ul>
<li>Подпункт 2.2.1.</li>
<li>Подпункт 2.2.2.</li>
</ul>
</li>
<li>Подпункт 2.3.</li>
</ul>
<div class="buttons">
<button class="accept">Разрешить</button>
<button>Запретить</button>
</div>
</section>
{% endif %}
<svg width="0" height="0">
<defs>
<clipPath id="authentication-header-mask">
<path
d="M50,160 L50,130 C22,130 0,107.612 0,80 C0,52 22,30 50,30 L50,3 C50,1.3 51.3,0 53,0 L447,0 C448,0 450,1.5 450,3 L450,160 L50,160 Z" />
</clipPath>
</defs>
</svg>
</section>
{% endblock %}
{% block js %}
<script type="text/javascript" src="/js/account.js"></script>
{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "core.html" %} {% extends "core.html" %}
{% use 'account/element.html' with css as account_css, body as account_body, js as account_js %} {% use 'nodes/account.html' with css as account_css, body as account_body, js as account_js %}
{% use "core.html" with css as core_css, body as core_body, js as core_js, js_init as core_js_init %} {% use "core.html" with css as core_css, body as core_body, js as core_js, js_init as core_js_init %}
{% use "header.html" with css as header_css, body as header_body, js as header_js, js_init as header_js_init %} {% use "header.html" with css as header_css, body as header_body, js as header_js, js_init as header_js_init %}
{% use "aside.html" with css as aside_css, body as aside_body, js as aside_js, js_init as aside_js_init %} {% use "aside.html" with css as aside_css, body as aside_body, js as aside_js, js_init as aside_js_init %}

View File

@ -0,0 +1,53 @@
{% block css %}
<link type="text/css" rel="stylesheet" href="/css/account.css">
<link type="text/css" rel="stylesheet" href="/css/gradient.css">
{% endblock %}
{% block body %}
<section id="authentication">
{% if account %}
{{ account.getKey() }}
{% if vk %}
{{ vk.mail }}
{% endif %}
{% else %}
<section class="header gradient unselectable">
<div class="glare"></div>
<img class="avatar unselectable" src="/images/what.png" alt="Пользователь" draggable="false">
<a href="https://mirzaev.sexy">{{ name ?? session.ip ?? session.hash ?? 'Ты кто?'}}</a>
<div class="red"></div>
<div class="green"></div>
<div class="blue"></div>
<img class="cover unselectable" src="/images/heh.gif" alt="Нейрожурнал Мирзаева" draggable="false"></img>
</section>
<section class="body">
<ul>
<li>Подпункт 2.1.</li>
<li>Подпункт 2.2.
<ul>
<li>Подпункт 2.2.1.</li>
<li>Подпункт 2.2.2.</li>
</ul>
</li>
<li>Подпункт 2.3.</li>
</ul>
<div class="buttons">
<button class="accept">Разрешить</button>
<button>Запретить</button>
</div>
</section>
{% endif %}
<svg width="0" height="0">
<defs>
<clipPath id="authentication-header-mask">
<path
d="M50,160 L50,130 C22,130 0,107.612 0,80 C0,52 22,30 50,30 L50,3 C50,1.3 51.3,0 53,0 L447,0 C448,0 450,1.5 450,3 L450,160 L50,160 Z" />
</clipPath>
</defs>
</svg>
</section>
{% endblock %}
{% block js %}
<script type="text/javascript" src="/js/account.js"></script>
{% endblock %}

View File

@ -0,0 +1,53 @@
{% block css %}
<link type="text/css" rel="stylesheet" href="/css/account.css">
<link type="text/css" rel="stylesheet" href="/css/gradient.css">
{% endblock %}
{% block body %}
<section id="authentication">
{% if account %}
{{ account.getKey() }}
{% if vk %}
{{ vk.mail }}
{% endif %}
{% else %}
<section class="header gradient unselectable">
<div class="glare"></div>
<img class="avatar unselectable" src="/images/what.png" alt="Пользователь" draggable="false">
<a href="https://mirzaev.sexy">Нейрожурнал Мирзаева</a>
<div class="red"></div>
<div class="green"></div>
<div class="blue"></div>
<img class="cover unselectable" src="/images/heh.gif" alt="Нейрожурнал Мирзаева" draggable="false"></img>
</section>
<section class="body">
<ul>
<li>Подпункт 2.1.</li>
<li>Подпункт 2.2.
<ul>
<li>Подпункт 2.2.1.</li>
<li>Подпункт 2.2.2.</li>
</ul>
</li>
<li>Подпункт 2.3.</li>
</ul>
<div class="buttons">
<button class="accept">Разрешить</button>
<button>Запретить</button>
</div>
</section>
{% endif %}
<svg width="0" height="0">
<defs>
<clipPath id="authentication-header-mask">
<path
d="M50,160 L50,130 C22,130 0,107.612 0,80 C0,52 22,30 50,30 L50,3 C50,1.3 51.3,0 53,0 L447,0 C448,0 450,1.5 450,3 L450,160 L50,160 Z" />
</clipPath>
</defs>
</svg>
</section>
{% endblock %}
{% block js %}
<script type="text/javascript" src="/js/account.js"></script>
{% endblock %}