Compare commits
10 Commits
d6aac48e7a
...
auth
| Author | SHA1 | Date | |
|---|---|---|---|
| e4e7443f81 | |||
| 5a8f309038 | |||
| 4dc75d7b56 | |||
| ca157ace77 | |||
| b734bdf849 | |||
| ae5548b27a | |||
| 20715e880f | |||
| 681cfc7cad | |||
| 5c83b8717c | |||
| 8d1b19bfa4 |
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\AuthorizationService;
|
||||
use App\Facades\UserContext;
|
||||
|
||||
/**
|
||||
* Контроллер авторизации
|
||||
*/
|
||||
class AuthorizationController extends Controller
|
||||
{
|
||||
public function __construct(AuthorizationService $authorizationService)
|
||||
{
|
||||
}
|
||||
|
||||
public function getUserRole($moduleName)
|
||||
{
|
||||
$userPermissions = UserContext::getUserAppPermissions();
|
||||
//Проверяем есть ли у пользователя в принципе доступ к приложению
|
||||
if (array_key_exists($moduleName, $userPermissions) !== false) {
|
||||
return response()->json(['userRole' => $userPermissions[$moduleName]], 403);
|
||||
} else {
|
||||
return response()->json(['message' => 'Приложение недоступно'], 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Facades\UserContext;
|
||||
|
||||
//ЭТОТ ПОСРЕДНИК ПРИМЕНЯЕТСЯ В WEB РОУТАХ И В API ЕНДПОИНТАХ ПОКА ТОЛЬКО ПРИЛОЖЕНИЯ ТАКСИ, НО ПО ЛОГИКЕ, ЕГО НУЖНО ИСПОЛЬЗОВАТЬ ВО ВСЕХ БУДУЩИХ МОДУЛЯХ. С ЕГО ПОМОЩЬЮ ПРОВЕРЯЕТСЯ КАЖДЫЙ ЗАПРОС К МОДУЛЮ - ВООБЩЕ ЕСТЬ ДОСТУП У ПОЛЬЗОВАТЕЛЯ ИЛИ НЕТ
|
||||
//В KERNEL У ЭТОГО ПОСРЕДНИКА ПРОПИСАН АЛИАС, КОТОРЫЙ УКАЗЫВАЕТСЯ УЖЕ В MODULE/ROUTES/WEB.PHP И API.PHP
|
||||
|
||||
/**
|
||||
* Посредник проверки доступа пользователя к приложению
|
||||
*/
|
||||
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 РОУТ НЕ С ФРОНТА, ТО ПРОИСХОДИТ РЕДИРЕКТ БЕЗ УКАЗАНИЯ ТЕКСТА ОШИБКИ. нАПРИМЕР, ПРИ РЕДИРЕКТЕ НИЖЕ НА СТРАНИЦУ МЕНЮ, ПОЛЬЗОВАТЕЛЬ НЕ УВИДИТ НИКАКОГО ОПОВЕЩЕНИЯ ОБ ОШИБКЕ
|
||||
//ЭТУ ПРОБЛЕМУ Я ИСПРАВЛЯЛ, ЧЕРЕЗ ГЕНЕРАЦИЮ НОТИФИКАЦИЙ НА БЭКЕ И ДОБАВЛЕНИЯ ИХ В ОПРЕДЕЛЕННУЮ ОЧЕРЕДЬ REDIS, КОТОРУЮ ЧИТАЕТ КАЖДАЯ СТРАНИЦА ПРИ ПЕРВИЧНОМ РЕНДЕРИНГЕ. НАДО ПОСМОТРЕТЬ ГДЕ Я УЖЕ ТАКОЕ РЕАЛИЗОВЫВАЛ
|
||||
return redirect('/menu');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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 РОУТ НЕ С ФРОНТА, ТО ПРОИСХОДИТ РЕДИРЕКТ БЕЗ УКАЗАНИЯ ТЕКСТА ОШИБКИ. нАПРИМЕР, ПРИ РЕДИРЕКТЕ НИЖЕ НА СТРАНИЦУ МЕНЮ, ПОЛЬЗОВАТЕЛЬ НЕ УВИДИТ НИКАКОГО ОПОВЕЩЕНИЯ ОБ ОШИБКЕ.
|
||||
//ЭТУ ПРОБЛЕМУ Я ИСПРАВЛЯЛ, ЧЕРЕЗ ГЕНЕРАЦИЮ НОТИФИКАЦИЙ НА БЭКЕ И ДОБАВЛЕНИЯ ИХ В ОПРЕДЕЛЕННУЮ ОЧЕРЕДЬ REDIS, КОТОРУЮ ЧИТАЕТ КАЖДАЯ СТРАНИЦА ПРИ ПЕРВИЧНОМ РЕНДЕРИНГЕ. НАДО ПОСМОТРЕТЬ ГДЕ Я УЖЕ ТАКОЕ РЕАЛИЗОВЫВАЛ
|
||||
return redirect('/menu');
|
||||
}
|
||||
#Гаврилов
|
||||
//РЕДИРЕКТ НА СТРАНИЦУ 403
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
+22
-20
@@ -6,43 +6,45 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
// protected $fillable = [
|
||||
// 'name',
|
||||
// 'email',
|
||||
// 'password',
|
||||
// ];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'login',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
// protected $hidden = [
|
||||
// 'password',
|
||||
// 'remember_token',
|
||||
// ];
|
||||
protected $hidden = [];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- custom.users definition
|
||||
|
||||
CREATE TABLE `users` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`login` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Логин пользователя',
|
||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`email_verified_at` timestamp NULL DEFAULT NULL,
|
||||
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `users_email_unique` (`email`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -0,0 +1,3 @@
|
||||
INSERT INTO custom.users (login,name,email,email_verified_at,password,remember_token,created_at,updated_at) VALUES
|
||||
('dgavrilov',NULL,NULL,NULL,NULL,NULL,'2025-11-09 16:50:05','2025-11-09 16:50:05'),
|
||||
('developer',NULL,NULL,NULL,NULL,NULL,'2025-11-20 13:45:05','2025-11-20 13:45:05');
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getCsrfToken } from './../services/getCsrfService';
|
||||
|
||||
function MagicLogin ()
|
||||
{
|
||||
//Состояние с введенным логином
|
||||
const [userLogin, setUserLogin] = useState<string>('');
|
||||
|
||||
return (
|
||||
<div id="login-container">
|
||||
<div id="login-container__magic-name">Magic</div>
|
||||
<div id="login-container__form">
|
||||
<form action="login" method="POST" autoComplete="on">
|
||||
<input
|
||||
type="hidden"
|
||||
name="_token"
|
||||
value={getCsrfToken()}
|
||||
/>
|
||||
<input name="_auth_login" type="text" onChange={ (e) => setUserLogin(e.target.value) } placeholder="Логин" required/>
|
||||
<input name="_auth_password" type="password" placeholder="Пароль" required/>
|
||||
<button type="submit" onClick={ () => {
|
||||
//При сабмите кладем логин в локалсторадж для того, чтобы при вызове api ендпоинтов передавать его в заголовках
|
||||
//гаврилов
|
||||
//ТОЧНО ЛИ ЭТО НУЖНО? МОЖЕТ, ЛУЧШЕ ВСЕ API ЕНДПОИНТЫ ПЕРЕПИСАТ ПОД ВЫЗОВ WEB РОУТОВ?
|
||||
localStorage.setItem('magic_auth_login', userLogin);
|
||||
} }>Вход</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MagicLogin;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import MagicLogin from './components/MagicLogin.tsx';
|
||||
import React from 'react';
|
||||
|
||||
const container = document.getElementById('root')!;
|
||||
const root = createRoot(container);
|
||||
root.render(<MagicLogin />);
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Сервис для полуения csrt токена для размещения в формах
|
||||
* @date 24.07.2025
|
||||
* @author dgavrilov
|
||||
*/
|
||||
|
||||
export const getCsrfToken = ():string => {
|
||||
const METATAG:HTMLElement|null = document.querySelector('meta[name="csrf-token"]');
|
||||
|
||||
if (!METATAG) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const CSRFTOKEN:string|null = METATAG.getAttribute('content');
|
||||
if (!CSRFTOKEN) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return CSRFTOKEN;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Laravel + React</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- Без команды ниже корректно не обрабатывается React скрипт -->
|
||||
@viteReactRefresh
|
||||
<!-- файл со стилями resources/css/magicLogin.css не создан, так как не успел приступить к доделке меню -->
|
||||
@vite(['resources/css/magicLogin.css', 'resources/js/magicLogin.tsx'])
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use \App\Http\Controllers;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| API Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register API routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider and all of them will
|
||||
| be assigned to the "api" middleware group. Make something great!
|
||||
|
|
||||
*/
|
||||
|
||||
#Гаврилов
|
||||
//РОУТ НИЖЕ НУЖЕН? НЕ ПОМНЮ ОТКУДА ОН ВЗЯЛСЯ. Пока оставляю, но надо разобраться для теста он или нужен на проде
|
||||
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Получение пользовательской роли
|
||||
*/
|
||||
Route::get('user_role/{moduleName}', [Controllers\AuthorizationController::class, 'getUserRole'])->where('moduleName', '^[a-z0-9_]+$');
|
||||
@@ -1,7 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use \App\Http\Controllers;
|
||||
use App\Http\Middleware\AuthenticateMagic;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Web Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register web routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider and all of them will
|
||||
| be assigned to the "web" middleware group. Make something great!
|
||||
|
|
||||
*/
|
||||
|
||||
#Гаврилов
|
||||
//РЕДИРЕКТ НА СТРАНИЦУ ЛОГИН
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
});
|
||||
//Роут вызова вьюхи аутентификации (без посредника аутентификации)
|
||||
Route::get('/login', function () {
|
||||
return view('magic_login');
|
||||
})->withoutMiddleware([AuthenticateMagic::class])->name('magic_login');
|
||||
Route::get('/logout', ([Controllers\LoginController::class, 'logout']))->name('magic_logout');
|
||||
//Роут вызова процессе аутентификации (без посредника аутентификации)
|
||||
Route::post('login', [Controllers\LoginController::class, 'ldapCheckUser'])->withoutMiddleware([AuthenticateMagic::class]);
|
||||
|
||||
//ГАВРИЛОВ. добавить without middleware AuthMagicApi?
|
||||
//Фоновое обновление санктум токена, если api вернул 401 (санктум протух), а сессия еще "жива"
|
||||
Route::get('/silent_token_refresh', [Controllers\LoginController::class, 'silentRefreshUserSanctumToken'])->withoutMiddleware([AuthenticateMagic::class]);
|
||||
|
||||
// Route::get('/role', [\App\Http\Controllers\TestController::class, 'getRoles'])->name('get_role');
|
||||
//Route::get('/access', [\App\Http\Controllers\TestController::class, 'getAccess']);
|
||||
|
||||
// Route::controller(Controllers\TestController::class)->group(function ()
|
||||
// {
|
||||
// Route::get('/access/{id}', 'getAccess')->where(['id' => '[0-9]+'])->name('getAccessById');
|
||||
// //Route::get('/access/{id}', 'getAccess')->where(['id' => '[0-9]+']);
|
||||
// });
|
||||
|
||||
// Route::get('/testData/{int}/{char}', [Controllers\TestDataController::class, 'insertNewData'])->where(['int' => '[0-9]+', 'char' => '[a-z]+']);
|
||||
|
||||
// Route::get('/testParam/{id}', [\App\Http\Controllers\TestController::class, 'getParam'])->where(['id' => '[0-9]+']);
|
||||
// Route::get('/redirect', [Controllers\TestController::class, 'redirect']);
|
||||
// Route::post('/role', [\App\Http\Controllers\TestController::class, 'setRole']);
|
||||
// Route::post('/role_del', [\App\Http\Controllers\TestController::class, 'delRole']);
|
||||
|
||||
// Route::get('/test_table', [Controllers\TestFormController::class, 'getForm']);
|
||||
// Route::post('/test_table', [Controllers\TestFormController::class, 'setForm'])->name('test_set_form');
|
||||
|
||||
// Route::get('/access/{id?}', [Controllers\AccessListController::class, 'getAccess'])->where(['id' => '[0-9]+']);
|
||||
// Route::delete('/access/{id}', [Controllers\AccessListController::class, 'delAccess'])->where(['id' => '[0-9]+'])->name('del_lk_tm_access');
|
||||
// Route::post('/access', [Controllers\AccessListController::class, 'postAccess']);
|
||||
|
||||
Reference in New Issue
Block a user