add superadmin dashboard. subscriptions

This commit is contained in:
root
2026-01-13 20:03:16 +03:00
parent 3c24c250e5
commit edb4df7e37
36 changed files with 3329 additions and 100 deletions
+98
View File
@@ -0,0 +1,98 @@
<?php
namespace App\Filters;
use App\Controllers\Auth;
use App\Models\UserModel;
use App\Models\OrganizationUserModel;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
/**
* AuthFilter - Фильтр аутентификации с поддержкой Remember Me
*
* Проверяет авторизацию пользователя двумя способами:
* 1. По сессии (стандартный способ)
* 2. По remember-токену из cookies (если сессия отсутствует)
*
* Если пользователь не авторизован — редирект на /login.
*
* Применяется к маршрутам, которые требуют авторизации, но НЕ требуют
* выбранной организации (например, профиль, выбор организации).
*/
class AuthFilter implements FilterInterface
{
/**
* Проверка аутентификации перед обработкой запроса
*
* @param RequestInterface $request
* @param array|null $arguments
* @return ResponseInterface|void
*/
public function before(RequestInterface $request, $arguments = null)
{
$session = session();
// Если пользователь уже авторизован по сессии — пропускаем
if ($session->get('isLoggedIn')) {
return;
}
// Проверяем remember-токен
$userId = Auth::checkRememberToken();
if ($userId !== null) {
// Токен найден и валиден — восстанавливаем сессию
$userModel = new UserModel();
$user = $userModel->find($userId);
if ($user && $user['email_verified']) {
$orgUserModel = new OrganizationUserModel();
$userOrgs = $orgUserModel->where('user_id', $user['id'])->findAll();
if (!empty($userOrgs)) {
// Восстанавливаем данные сессии
$sessionData = [
'user_id' => $user['id'],
'email' => $user['email'],
'name' => $user['name'],
'isLoggedIn' => true,
];
// Выбираем организацию если она была
if (count($userOrgs) === 1) {
$sessionData['active_org_id'] = $userOrgs[0]['organization_id'];
}
$session->set($sessionData);
log_message('info', "User {$user['email']} logged in via remember token");
return;
}
}
// Токен недействителен — удаляем его
$response = service('response');
$response->deleteCookie('remember_selector');
$response->deleteCookie('remember_token');
}
// Пользователь не авторизован — редирект на логин
return redirect()->to('/login');
}
/**
* Обработка после выполнения запроса
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param array|null $arguments
* @return void
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
{
// Ничего не делаем после запроса
}
}
+30 -57
View File
@@ -6,74 +6,47 @@ use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
/**
* OrganizationFilter - Фильтр выбора организации
*
* Проверяет, что у авторизованного пользователя выбрана организация.
* Если организация не выбрана — редирект на /organizations.
*
* Применяется к маршрутам, которые требуют выбранной организации
* (например, дашборд, управление клиентами, задачи и т.д.).
*/
class OrganizationFilter implements FilterInterface
{
/**
* Проверка выбора организации перед обработкой запроса
*
* @param RequestInterface $request
* @param array|null $arguments
* @return ResponseInterface|void
*/
public function before(RequestInterface $request, $arguments = null)
{
$session = session();
$uri = $request->getUri();
$currentPath = $uri->getPath();
// === БЛОК ДЛЯ ГОСТЕЙ (НЕЗАЛОГИНЕННЫХ) ===
if (!$session->get('isLoggedIn')) {
// Список публичных маршрутов для гостей
$publicRoutes = [
'/',
'/login',
'/register',
'/register/success',
'/auth/verify',
'/auth/resend-verification',
];
if (!in_array($currentPath, $publicRoutes)) {
// Если гость лезет не туда (например /organizations) — на главную
return redirect()->to('/');
}
// Иначе пропускаем
return;
}
// =======================================
// === БЛОК ДЛЯ ЗАЛОГИНЕННЫХ ===
// Список маршрутов, доступных БЕЗ выбранной организации
$publicAuthRoutes = [
'/login',
'/register',
'/logout',
'/register/success',
'/auth/verify',
'/auth/resend-verification',
'/organizations',
'/organizations/create',
'/organizations/switch' // Начало пути для переключения
];
// Если мы на этих маршрутах — проверка orgId не нужна (мы просто выбираем её)
if (in_array($currentPath, $publicAuthRoutes) || strpos($currentPath, '/organizations/switch') === 0) {
// Если пользователь не авторизован — пропускаем (AuthFilter разберётся)
if (!session()->get('isLoggedIn')) {
return;
}
// Если мы попадаем сюда — значит пользователь идет на закрытую страницу (например /crm)
// или на главную (/).
// Главную страницу (/) мы проверяем на уровне контроллера Home::index,
// поэтому фильтр для '/' можно пропускать, ИЛИ проверить здесь.
// Пропустим '/', чтобы контроллер сам решил: редиректить на /organizations или показывать дашборд.
if ($currentPath === '/') {
return;
}
// Для всех остальных закрытых страниц проверяем active_org_id
// Проверяем, выбрана ли организация
if (empty(session()->get('active_org_id'))) {
return redirect()->to('/organizations');
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
/**
* Обработка после выполнения запроса
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @param array|null $arguments
* @return void
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
{
// Do nothing
// Ничего не делаем после запроса
}
}
}
+25 -6
View File
@@ -34,24 +34,43 @@ class RoleFilter implements FilterInterface
$access = AccessService::getInstance();
// Проверка авторизации в организации
if (!$access->isAuthenticated()) {
// Если пользователь не авторизован в организации - редирект на выбор организации
return redirect()->to('/organizations');
// Разбор аргументов
// Формат: 'role:admin,manager' или 'role:system:superadmin' или 'permission:manage_users:users'
// Проверка системных ролей (system: prefix)
if (is_string($arguments) && str_starts_with($arguments, 'role:system:')) {
$roles = explode(',', substr($arguments, 13)); // 13 = длина 'role:system:'
$roles = array_map('trim', $roles);
// Для системных ролей НЕ требуется авторизация в организации
if (!$access->isSystemRole($roles)) {
return $this->forbiddenResponse();
}
return null;
}
// Разбор аргументов
// Формат: 'role:admin,manager' или 'permission:manage_users:users'
// Проверка организационных ролей
if (is_string($arguments) && str_starts_with($arguments, 'role:')) {
$roles = explode(',', substr($arguments, 5));
$roles = array_map('trim', $roles);
// Проверка авторизации в организации
if (!$access->isAuthenticated()) {
// Если пользователь не авторизован в организации - редирект на выбор организации
return redirect()->to('/organizations');
}
if (!$access->isRole($roles)) {
return $this->forbiddenResponse();
}
}
if (is_string($arguments) && str_starts_with($arguments, 'permission:')) {
// Проверка авторизации в организации для разрешений
if (!$access->isAuthenticated()) {
return redirect()->to('/organizations');
}
$parts = explode(':', substr($arguments, 11));
if (count($parts) >= 2) {
$permission = $parts[0];