добавляю все изменения проекта на текущий момент

This commit is contained in:
vasya
2026-02-27 18:49:27 +03:00
parent e927910fda
commit 9c35f4e35e
303 changed files with 79434 additions and 2558 deletions
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route('login');
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
use App\Facades\UserContext;
use App\Models\User;
use Illuminate\Support\Facades\Redis;
use Laravel\Sanctum\PersonalAccessToken;
/**
* Глобальный посредник аутентификации на платформе Magic для всех роутов платформы
*/
class AuthenticateMagic
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
//TODO ПРОВЕРИТЬ
//Если сессия не стартовала возможно пользователь сразу обращается к api ендпоинту (ПРОВЕРИТЬ)
if (session()->isStarted()) {
if (session()->has('_auth_login')) {
//гаврилов. получение токена
// $token = $request->user();
// $userId = User::where('login', $token->getAttributes()['samaccountname'][0])->get()->toArray()[0]['id'];
// $tokenExpires = PersonalAccessToken::where('tokenable_id', $userId)->get()->toArray()[0]['expires_at'];
// echo '<pre>'; var_dump(new \DateTime($tokenExpires)); echo'</pre>';
// echo '<pre>'; var_dump(PersonalAccessToken::where('tokenable_id', $userId)->get()->toArray()[0]['expires_at']); echo'</pre>';
//Если токен истекает менее через 60 минут, продлеваем его на 2 часа. Ситуации, что сессия протухла, а токен продолжает жить не может случиться, так как апи запросы с фронта отправляют куку аутентификации, которая проверяется при $this->authenticate. Если она протухла, возвратится 401 ошибку.
// if ($token->expires_at->diffInMinutes(now()) < 60) {
// $token->update(
// [
// 'expires_at' => now()->addHours(2)
// ]
// );
// }
UserContext::setUserLogin(session()->get('_auth_login'));
$userGroups = session()->get('_auth_groups');
UserContext::setUserADGroups($userGroups);
UserContext::setUserEmails($userGroups);
UserContext::setIsAdminFlag(session()->get('is_admin'));
//На этапе посредника мы не проводим повторное определение ролей пользователя, это было сделано в LoginController в процессе авторизации после успешной аутентификации. Здесь мы уже берем его доступы из таблицы с токенами
$user = User::where('login', UserContext::getUserLogin())->first();
UserContext::setUserId($user->id);
UserContext::setUserAppPermissions($user->tokens()->latest()->first()->abilities['permissions']);
#Гаврилов
//ЧТО ЗА КОМАНДА НИЖЕ?
Redis::setex('notifications', 60, 123);
return $next($request);
} else {
//Получаем адрес предыдущей страницы, на которую хотел попасть пользователь, чтобы направить его после успешной аутентификации на этот адрес
$prevPageUrl = explode('/', $_SERVER['REDIRECT_URL']);
//Удаляем из URL редиректа пустые сегменты и сегмент с названием приложения (оно подставляется при редиректе само)
unset($prevPageUrl[0], $prevPageUrl[1]);
//Кладем в сессию адрес страницы. только если это не страница выхода, иначе после аутентификации пользователя сразу выбросит
session()->put('_auth_prev_page', implode('/', $prevPageUrl) == 'logout' ? '/menu' : implode('/', $prevPageUrl));
return redirect('/login');
//редирект на страницу login с сообщением об ошибке
}
} else {
return redirect('/login');
//redirect на страницу login после которой точно сессия застартует, так как это webроут, а не api
}
// return $next($request);
}
}
@@ -0,0 +1,61 @@
<?php
/**
* Кастомный посредник для обработки неудачной аутентификации Sanctum при обращении к api ендпоинтам. Стандартный посредник аутентификации пытается редиректить на роут login, чего быть не должно при работе с api
* @author dgavrilov
*/
namespace app\Http\Middleware;
use Closure;
use Illuminate\Auth\Middleware\Authenticate as BaseAuthenticate;
use Illuminate\Auth\AuthenticationException;
use App\Facades\UserContext;
class AuthenticateMagicApi extends BaseAuthenticate
{
public function handle($request, Closure $next, ...$guards)
{
// if ($request->is('api/silent_token_refresh')) {
// return $next($request);
// }
//Если пользователь в рамках сессии обращается к api ендпоинтам из приложения, он не всегда может установить заголовок с sanctum токеном (например, переходя по ссылкке <a href='.../api/delItem/id'>). В этом случае, проверяем куки и устанавливаем заголовок оттуда, так как при аутентификации пользовательский логин кладется в куки. Это позволяет нам не устанвливать заголовк в каждом fetch запросе на фронте
if ($request->is('api/*') && ($token = $request->cookie('sanctum_token'))) {
$request->headers->set("Authorization", "Bearer $token");
}
//Переопределяем поведение в случае возникновения ошибки при стандартной аутентификации. Стандартное поведение перебрасывает на страницу login, мы же возвращаем 401 ошибку, так как понимаем, что пользователь обратился по api
//DGAVRILOV. ПОВЕДЕНИЕ, ОПИСАННОЕ ВЫШЕ, ПРИВОДИТ К ОШИБКЕ. ЕСЛИ ПОЛЬЗОВАТЕЛЬ ОТПРАВИЛ API ЗАПРОС С ФРОНТА И ЕГО СЕССИЯ ПРОТУХЛА, ВОЗВРАЩАЕТСЯ 401 ОШИБКА ВО ВКЛАДКЕ NETWORK, НО ПОЛЬЗОВАТЕЛЯ НЕ ПЕРЕБРАСЫВАЕТ НА СТРАНИЦУ /LOGIN. ЕСЛИ ПОПРАВИТЬ ПОСРЕДНИК WEB АУТЕНТИФИКАЦИИ, ТО ЭТОЙ ПРОБЛЕМЫ НЕ БУДЕТ? БУДЕТ ПЕРЕБРАСЫВАТЬ НА СТРАНИЦУ LOGIN СО СТРАНИЦЫ, ОТКУДА СОВЕРЩАЛСЯ ВЫЗО API ЕНДПОИНТА? СКОРЕЕ ВСЕГО НЕТ, НУЖНО БУДЕТ ПРИ ВЫЗОВЕ API КАК-ТО ПЕРЕХВАТЫВАТЬ 401 ОШИБКУ И ОТПРАВЛЯТЬ НА СТРАНИЦУ /LOGIN
try {
//Стандартная аутентификация по санктум токену (проверяется срок жизни)
$this->authenticate($request, $guards);
//$token = $request->user()->currentAccessToken();
//Если токен истекает менее через 60 минут, продлеваем его на 2 часа. Ситуации, что сессия протухла, а токен продолжает жить не может случиться, так как апи запросы с фронта отправляют куку аутентификации, которая проверяется при $this->authenticate. Если она протухла, возвратится 401 ошибку.
// if ($token->expires_at->diffInMinutes(now()) < 60) {
// $token->update(
// [
// 'expires_at' => now()->addHours(2)
// ]
// );
// }
//После успешной аутентификации Sanctum обогащаем UserService параметрами пользователя (логин, роли приложения)
#Гаврилов
//ПОКА НЕ ДОБАВЛЯЮ ГРУППЫ AD B EMAILS, ТАК КАК В WEB КОНТУРЕ ОНИ ОПРЕДЕЛЯЮТСЯ ПРИ AD АУТЕНТИФИКАЦИИ И ПОЛУЧЕННЫЕ ЗНАЧЕНИЯ СКЛАДЫВАЮТСЯ В SESSION. В API КОНТУРЕ ДОСТУПА К СЕССИИ НЕТ - ПРОВОДИТЬ AD АУТЕНТИФИКАЦИЮ НЕ ПОЛУЧИТС БЕЗ ПАРОЛЯ ПОЛЬЗОВАТЕЛЯ, КОТОРЫЙ НИГДЕ НЕ ХРАНИТСЯ. ПОЭТОМУ, ЕСЛИ ПОНАДОБИТСЯ ДОСТАВАТЬ В API КОНТУРЕ ГРУППЫ ПОЛЬЗОВАТЕЛЯ, ПРИ AD АУТЕНТИФИКАЦИИ ПОНАДОБИТСЯ КЛАСТЬ AD ГРУППЫ ПОЛЬЗОВАТЕЛЯ И ЕГО EMAILS В ПОЛЕ ABILITIES ТАБЛИЦЫ PERSONAL ACCESS TOKENS ПО АНАЛОГИИ С PERISSIONS
#Гаврилов
//НУЖНО ЛИ ОБОГАЩАТЬ USERcoNTEXT НИЖЕ, ЕСЛИ ПРИ WEB АУТЕНТИФИКАЦИИ КОНТЕКСТ УЖЕ ДОЛЖЕН БЫЛ БЫТЬ ОБОГАЩЕН. вЕРОЯТНО, ЭТО НУЖНО ДЛЯ ОБРАБОТКИ ЧИСТО API ЗАПРОСОВ (БЕЗ ВХОДА В СИСТЕМУ), НО НАДО ПРОВЕРИТЬ
UserContext::setUserLogin($request->user()->login);
UserContext::setUserAppPermissions($request->user()->currentAccessToken()->abilities['permissions']);
} catch (AuthenticationException $auth) {
if ($request->is('api/*')) {
return response()->json([
#Гаврилов
//СКОРРЕКТИРУЙ ИНФОРМАЦИЮ О ВОЗВРАЩАЕМОЙ ОШИБКЕ?
'error' => 'Unauthenticated',
'message' => 'Invalid or missing authentication token'
], 401);
}
throw $auth;
}
return $next($request);
}
}
@@ -0,0 +1,39 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Facades\UserContext;
/**
* Посредник проверки доступа пользователя к приложению
*/
class CheckUserAppAccess
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$moduleName = explode('/', $request->route()->getPrefix());
$moduleName = end($moduleName) ?? null;
if ($moduleName) {
if (array_key_exists($moduleName, UserContext::getUserAppPermissions()) !== false) {
return $next($request);
}
}
//Если ошибка при обращении к api ендпоинту
if ($request->expectsJson()) {
return response()->json(['message' => "Ошибка! Приложение недоступно"], 403);
//Если ошибка при обращении к web роуту
} else {
#Гаврилов
//ЕСЛИ ВЫЗЫВАЕТСЯ WEB РОУТ НЕ С ФРОНТА, ТО ПРОИСХОДИТ РЕДИРЕКТ БЕЗ УКАЗАНИЯ ТЕКСТА ОШИБКИ. нАПРИМЕР, ПРИ РЕДИРЕКТЕ НИЖЕ НА СТРАНИЦУ МЕНЮ, ПОЛЬЗОВАТЕЛЬ НЕ УВИДИТ НИКАКОГО ОПОВЕЩЕНИЯ ОБ ОШИБКЕ.
return redirect('/menu');
}
}
}
@@ -0,0 +1,66 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Models\User;
use App\Facades\UserContext;
/**
* Посредник проверки доступа пользовательской роли к роуту или ендпоинту
*/
class CheckUserPermission
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string $requirement): Response
{
//Роли, которым доступен функционал
$accessRoles = explode(';', $requirement);
$userPermissions = UserContext::getUserAppPermissions();
//Всем роутам модуля добавляем префикс, поэтому можем ориентироваться на него, чтобы получить имя модуля, откуда пришел запрос
$moduleName = explode('/', $request->route()->getPrefix());
$moduleName = end($moduleName) ?? null;
if ($moduleName) {
if (array_key_exists($moduleName, UserContext::getUserAppPermissions()) !== false) {
if (in_array($userPermissions[$moduleName], $accessRoles)) {
return $next($request);
}
// else {
// return redirect('/menu');
// #Гаврилов
// //РЕДИРЕКТ НА СТРАНИЦУ 403
// }
}
// else {
// return redirect('/menu');
// #Гаврилов
// //РЕДИРЕКТ НА СТРАНИЦУ 403
// //redirect();
// }
}
// else {
// return redirect('/menu');
// #Гаврилов
// //КУДА РЕДИРЕКТИТЬ или что возвращать, если имя модуля не определено?
// }
//Если ошибка при обращении к api ендпоинту
if ($request->expectsJson()) {
return response()->json(['message' => "Ошибка! Функционал недоступен для вашей роли"], 403);
//Если ошибка при обращении к web роуту
} else {
#Гаврилов
//ЕСЛИ ВЫЗЫВАЕТСЯ WEB РОУТ НЕ С ФРОНТА, ТО ПРОИСХОДИТ РЕДИРЕКТ БЕЗ УКАЗАНИЯ ТЕКСТА ОШИБКИ. нАПРИМЕР, ПРИ РЕДИРЕКТЕ НИЖЕ НА СТРАНИЦУ МЕНЮ, ПОЛЬЗОВАТЕЛЬ НЕ УВИДИТ НИКАКОГО ОПОВЕЩЕНИЯ ОБ ОШИБКЕ.
return redirect('/menu');
}
#Гаврилов
//РЕДИРЕКТ НА СТРАНИЦУ 403
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
//
];
}
@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
//
];
}
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts(): array
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [
// 'fbclid',
// 'utm_campaign',
// 'utm_content',
// 'utm_medium',
// 'utm_source',
// 'utm_term',
];
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
//
'/access/*',
'/access',
];
}