Инициализация
This commit is contained in:
621
mirzaev/surikovlib/system/models/accounts_model.php
Normal file
621
mirzaev/surikovlib/system/models/accounts_model.php
Normal file
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\surikovlib\models;
|
||||
|
||||
use pdo;
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Модель регистрации, аутентификации и авторизации
|
||||
*
|
||||
* @package mirzaev\surikovlib\models
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class accounts_model extends core
|
||||
{
|
||||
/**
|
||||
* Регистрация
|
||||
*
|
||||
* @param string $name Входной псевдоним
|
||||
* @param string $email Почта
|
||||
* @param string $password Пароль (password)
|
||||
* @param bool $authentication Автоматическая аутентификация в случае успешной регистрации
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return array|bool Аккаунт, если удалось аутентифицироваться
|
||||
*/
|
||||
public static function registration(string $name = null, string $email = null, string $password, array &$errors = []): array
|
||||
{
|
||||
try {
|
||||
if (static::account($errors)) {
|
||||
// Аутентифицирован пользователь
|
||||
|
||||
// Запись ошибки
|
||||
throw new exception('Уже аутентифицирован');
|
||||
}
|
||||
|
||||
if (empty($account = static::read(['name' => $name]) or $account = static::read(['email' => $email]))) {
|
||||
// Не удалось найти аккаунт
|
||||
|
||||
if (static::write($name, $email, $password, $errors)) {
|
||||
// Удалось зарегистрироваться
|
||||
|
||||
return $account;
|
||||
}
|
||||
} else {
|
||||
// Удалось найти аккаунт
|
||||
|
||||
return $account;
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Аутентификация
|
||||
*
|
||||
* @param string $login Входной псевдоним
|
||||
* @param string $password Пароль (password)
|
||||
* @param bool $remember Функция "Запомнить меня" - увеличенное время хранения cookies
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return array Аккаунт (если не найден, то пустой массив)
|
||||
*/
|
||||
public static function authentication(string $login, string $password, bool $remember = false, array &$errors = []): array
|
||||
{
|
||||
try {
|
||||
if (static::account($errors)) {
|
||||
// Аутентифицирован пользователь
|
||||
|
||||
// Запись ошибки
|
||||
throw new exception('Уже аутентифицирован');
|
||||
}
|
||||
|
||||
|
||||
if (empty($account = static::read(['name' => $login]) or $account = static::read(['email' => $login]))) {
|
||||
// Не удалось найти аккаунт
|
||||
|
||||
throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
|
||||
if (password_verify($password, $account['password'])) {
|
||||
// Совпадают хеши паролей
|
||||
|
||||
// Инициализация идентификатора сессии
|
||||
session_id($account['id']);
|
||||
|
||||
// Инициализация названия сессии
|
||||
session_name('id');
|
||||
|
||||
// Инициализация сессии
|
||||
session_start();
|
||||
|
||||
// Инициализация времени хранения хеша
|
||||
$time = time() + ($remember ? 604800 : 86400);
|
||||
|
||||
// Инициализация хеша
|
||||
$hash = static::hash((int) $account['id'], crypt($account['password'], time() . $account['id']), $time, $errors)['hash'];
|
||||
|
||||
// Инициализация cookies
|
||||
setcookie("hash", $hash, $time, path: '/', secure: true);
|
||||
|
||||
return $account;
|
||||
} else {
|
||||
// Не совпадают хеши паролей
|
||||
|
||||
throw new exception('Неправильный пароль');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Аутентификация
|
||||
*
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return bool Удалось ли деаутентифицироваться
|
||||
*/
|
||||
public static function deauthentication(array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
if ($account = static::account($errors)) {
|
||||
// Аутентифицирован пользователь
|
||||
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = null, `time` = 0 WHERE `id` = :id");
|
||||
|
||||
// Параметры запроса
|
||||
$params = [
|
||||
":id" => $account['id'],
|
||||
];
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
$request->fetch(pdo::FETCH_ASSOC);
|
||||
|
||||
// Деинициализация cookies
|
||||
setcookie("id", '', 0, path: '/', secure: true);
|
||||
setcookie("hash", '', 0, path: '/', secure: true);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// Не аутентифицирован пользователь
|
||||
|
||||
// Запись ошибки
|
||||
throw new exception('Не аутентифицирован');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Прочитать данные аккаунта, если пользователь аутентифицирован
|
||||
*
|
||||
* Можно использовать как проверку на аутентифицированность
|
||||
*
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return array Аккаунт (если не найден, то пустой массив)
|
||||
*
|
||||
* @todo 1. Сделать в static::read() возможность передачи нескольких параметров и перенести туда непосредственно чтение аккаунта с проверкой хеша
|
||||
*/
|
||||
public static function account(array &$errors = []): array
|
||||
{
|
||||
try {
|
||||
if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
|
||||
// Аутентифицирован аккаунт (найдены cookie и они хранят значения - подразумевается, что не null или пустое)
|
||||
|
||||
if ($_COOKIE['hash'] === static::hash((int) $_COOKIE['id'], errors: $errors)['hash']) {
|
||||
// Совпадает переданный хеш с тем, что хранится в базе данных
|
||||
} else {
|
||||
// Не совпадает переданный хеш с тем, что хранится в базе данных
|
||||
|
||||
// Генерация ошибки
|
||||
throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
|
||||
}
|
||||
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `id` = :id && `hash` = :hash");
|
||||
|
||||
// Параметры запроса
|
||||
$params = [
|
||||
":id" => $_COOKIE['id'],
|
||||
":hash" => $_COOKIE['hash'],
|
||||
];
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
if (empty($account = $request->fetch(pdo::FETCH_ASSOC))) {
|
||||
// Не найдена связка идентификатора с хешем
|
||||
|
||||
// Генерация ошибки
|
||||
throw new exception('Не найден пользотватель или время аутентификации истекло');
|
||||
}
|
||||
|
||||
// Чтение разрешений
|
||||
$account['permissions'] = static::permissions((int) $account['id'], $errors);
|
||||
|
||||
return $account;
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Прочитать разрешения аккаунта
|
||||
*
|
||||
* @param int $id Идентификатор аккаунта
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return array Разрешения аккаунта, если найдены
|
||||
*/
|
||||
public static function permissions(int $id, array &$errors = []): array
|
||||
{
|
||||
try {
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("SELECT * FROM `permissions` WHERE `id` = :id");
|
||||
|
||||
// Параметры запроса
|
||||
$params = [
|
||||
":id" => $id
|
||||
];
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
if (empty($response = $request->fetch(pdo::FETCH_ASSOC))) {
|
||||
// Не найдены разрешения
|
||||
|
||||
// Генерация ошибки
|
||||
throw new exception('Не найдены разрешения');
|
||||
}
|
||||
|
||||
// Удаление ненужных данных
|
||||
unset($response['id']);
|
||||
|
||||
return $response;
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить разрешение
|
||||
*
|
||||
* @param string $permission Разрешение
|
||||
* @param int|null $id Идентификатор аккаунта
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return bool|null Статус разрешения, если оно записано
|
||||
*/
|
||||
public static function access(string $permission, int|null $id = null, array &$errors = []): ?bool
|
||||
{
|
||||
try {
|
||||
// Инициализация аккаунта
|
||||
$account = isset($id) ? self::read(['id' => $id], $errors) : self::account($errors);
|
||||
|
||||
return isset($account['permissions'][$permission]) ? (bool) $account['permissions'][$permission] : null;
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись пользователя в базу данных
|
||||
*
|
||||
* @param string|null $name Имя
|
||||
* @param string|null $email Почта
|
||||
* @param string|null $password Пароль
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return array Аккаунт (если не найден, то пустой массив)
|
||||
*/
|
||||
public static function write(string|null $name = null, string|null $email = null, string|null $password = null, array &$errors = []): array
|
||||
{
|
||||
try {
|
||||
// Инициализация параметров запроса
|
||||
$params = [];
|
||||
|
||||
if (isset($name)) {
|
||||
try {
|
||||
// Проверка параметра
|
||||
if (iconv_strlen($name) < 3) throw new exception('Длина имени должна быть не менее 3 символов');
|
||||
if (iconv_strlen($name) > 60) throw new exception('Длина имени должна быть не более 60 символов');
|
||||
|
||||
// Запись в буфер параметров запроса
|
||||
$params[':name'] = $name;
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
];
|
||||
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($email)) {
|
||||
try {
|
||||
// Проверка параметра
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту');
|
||||
if (iconv_strlen($email) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
|
||||
if (iconv_strlen($email) > 60) throw new exception('Длина почты должна быть не более 80 символов');
|
||||
|
||||
// Запись в буфер параметров запроса
|
||||
$params[':email'] = $email;
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
];
|
||||
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($password)) {
|
||||
try {
|
||||
// Проверка параметра
|
||||
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
|
||||
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
|
||||
|
||||
// Запись в буфер параметров запроса
|
||||
$params[':password'] = password_hash($password, PASSWORD_BCRYPT);
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
];
|
||||
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? '`email`' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? ':email' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
$request->fetch(pdo::FETCH_ASSOC);
|
||||
|
||||
try {
|
||||
if (isset($name)) {
|
||||
// Передано имя аккаунта
|
||||
|
||||
// Чтение аккаунта
|
||||
$account = static::read(['name' => $name]);
|
||||
} else if (isset($email)) {
|
||||
// Передана почта аккаунта
|
||||
|
||||
// Чтение аккаунта
|
||||
$account = static::read(['email' => $email]);
|
||||
} else {
|
||||
// Не передано ни имя, ни почта
|
||||
|
||||
throw new exception('Не переданны данные для полноценной регистрации аккаунта');
|
||||
}
|
||||
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("INSERT INTO `permissions` (`id`) VALUES (:id)");
|
||||
|
||||
// Инициализация параметров
|
||||
$params = [
|
||||
':id' => $account['id']
|
||||
];
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
$request->fetch(pdo::FETCH_ASSOC);
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Конец выполнения
|
||||
end:
|
||||
|
||||
return isset($account) && $account ? $account : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение пользователя из базы данных
|
||||
*
|
||||
* @param array $search Поиск ('поле' => 'значение'), работает только с одним полем
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return array Аккаунт, если найден
|
||||
*/
|
||||
public static function read(array $search, array &$errors = []): array
|
||||
{
|
||||
try {
|
||||
// Инициализация данных для поиска
|
||||
$field = array_keys($search)[0] ?? null;
|
||||
$value = $search[$field] ?? null;
|
||||
|
||||
if (empty($field)) {
|
||||
// Получено пустое значение поля
|
||||
|
||||
// Запись ошибки
|
||||
throw new exception('Пустое значение поля для поиска');
|
||||
}
|
||||
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `$field` = :field LIMIT 1");
|
||||
|
||||
// Параметры запроса
|
||||
$params = [
|
||||
":field" => $value,
|
||||
];
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
if ($account = $request->fetch(pdo::FETCH_ASSOC)) {
|
||||
// Найден аккаунт
|
||||
|
||||
try {
|
||||
if ($permissions = static::permissions((int) $account['id'], $errors)) {
|
||||
// Найдены разрешения
|
||||
|
||||
// Запись в буфер данных аккаунта
|
||||
$account['permissions'] = $permissions;
|
||||
} else {
|
||||
// Не найдены разрешения
|
||||
|
||||
throw new exception('Не удалось найти и прочитать разрешения');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// Не найден аккаунт
|
||||
|
||||
throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return isset($account) && $account ? $account : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись или чтение хеша из базы данных
|
||||
*
|
||||
* @param int $id Идентификатор аккаунта
|
||||
* @param int|null $hash Хеш аутентифиакции
|
||||
* @param string|null $time Время хранения хеша
|
||||
* @param array &$errors Журнал ошибок
|
||||
*
|
||||
* @return array ['hash' => $hash, 'time' => $time]
|
||||
*/
|
||||
public static function hash(int $id, string|null $hash = null, int|null $time = null, array &$errors = []): array
|
||||
{
|
||||
try {
|
||||
if (isset($hash, $time)) {
|
||||
// Переданы хеш и его время хранения
|
||||
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
|
||||
|
||||
// Параметры запроса
|
||||
$params = [
|
||||
":id" => $id,
|
||||
":hash" => $hash,
|
||||
":time" => $time,
|
||||
];
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
$request->fetch(pdo::FETCH_ASSOC);
|
||||
} else {
|
||||
// Не переданы хеш и его время хранения
|
||||
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("SELECT `hash`, `time` FROM `accounts` WHERE `id` = :id");
|
||||
|
||||
// Параметры запроса
|
||||
$params = [
|
||||
":id" => $id,
|
||||
];
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
extract((array) $request->fetch(pdo::FETCH_ASSOC));
|
||||
|
||||
if (!empty($response['time']) && $response['time'] <= time()) {
|
||||
// Истекло время жизни хеша
|
||||
|
||||
// Инициализация запроса
|
||||
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
|
||||
|
||||
// Параметры запроса
|
||||
$params = [
|
||||
":id" => $id,
|
||||
":hash" => null,
|
||||
":time" => null,
|
||||
];
|
||||
|
||||
// Отправка запроса
|
||||
$request->execute($params);
|
||||
|
||||
// Генерация ответа
|
||||
$response = $request->fetch(pdo::FETCH_ASSOC);
|
||||
|
||||
// Генерация ошибки
|
||||
throw new exception('Время аутентификации истекло');
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Запись в журнал ошибок
|
||||
$errors[]= [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return ['hash' => $hash, 'time' => $time];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user