fix menu tax in data and fix session when multiple user login
This commit is contained in:
@@ -36,7 +36,9 @@ class UsersController extends Controller
|
||||
return UserResource::collection($query->paginate(config('app.paginate_per_page', 50)));
|
||||
}
|
||||
public function logout(Request $request){
|
||||
$request->user()->tokens()->delete();
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $request->user()->id)
|
||||
->where('tokenable_type', get_class($request->user()))
|
||||
->delete();
|
||||
return response()->json(['message' => 'logged out successfully']);
|
||||
}
|
||||
public function store(UsersRequest $request){
|
||||
|
||||
@@ -37,7 +37,9 @@ class AuthenticatedSessionController extends Controller
|
||||
$user = Auth::user();
|
||||
|
||||
// Hapus token lama jika ada
|
||||
$user->tokens()->delete();
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->delete();
|
||||
|
||||
// Buat token untuk API dengan scope dan expiration
|
||||
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||
@@ -47,6 +49,10 @@ class AuthenticatedSessionController extends Controller
|
||||
|
||||
// Simpan token di session untuk digunakan di frontend
|
||||
session(['api_token' => $token]);
|
||||
|
||||
// Simpan timestamp login untuk validasi multi-user
|
||||
session(['login_timestamp' => now()->timestamp]);
|
||||
session(['user_id' => $user->id]);
|
||||
|
||||
return redirect()->intended(RouteServiceProvider::HOME);
|
||||
}
|
||||
@@ -66,7 +72,9 @@ class AuthenticatedSessionController extends Controller
|
||||
}
|
||||
|
||||
// Delete existing tokens
|
||||
$user->tokens()->delete();
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->delete();
|
||||
|
||||
// Generate new token
|
||||
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||
@@ -107,7 +115,9 @@ class AuthenticatedSessionController extends Controller
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
if($request->user()){
|
||||
$request->user()->tokens()->delete();
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $request->user()->id)
|
||||
->where('tokenable_type', get_class($request->user()))
|
||||
->delete();
|
||||
}
|
||||
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
167
app/Http/Middleware/ValidateApiTokenForWeb.php
Normal file
167
app/Http/Middleware/ValidateApiTokenForWeb.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ValidateApiTokenForWeb
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
* Middleware ini memvalidasi token API untuk web requests
|
||||
* dan melakukan auto-logout jika token tidak valid
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// Skip validation untuk non-authenticated routes
|
||||
if (!Auth::check()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// Skip validation untuk API routes (sudah ditangani oleh auth:sanctum)
|
||||
if ($request->is('api/*')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$sessionToken = Session::get('api_token');
|
||||
|
||||
// Jika tidak ada token di session, generate token baru
|
||||
if (!$sessionToken) {
|
||||
$this->generateNewToken($user);
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// Validasi token API
|
||||
if (!$this->isTokenValid($sessionToken, $user)) {
|
||||
// Token invalid, check apakah ada user lain yang login
|
||||
if ($this->hasOtherUserLoggedIn($user)) {
|
||||
// User lain sudah login, force logout user ini
|
||||
$this->forceLogout($request, 'User lain telah login. Silakan login ulang.');
|
||||
return $this->redirectToLogin($request, 'User lain telah login. Silakan login ulang.');
|
||||
} else {
|
||||
// Generate token baru jika tidak ada user lain
|
||||
$this->generateNewToken($user);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check apakah token API masih valid
|
||||
*/
|
||||
private function isTokenValid($sessionToken, $user): bool
|
||||
{
|
||||
if (!$sessionToken || !$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract plain token dari session token
|
||||
$tokenParts = explode('|', $sessionToken);
|
||||
if (count($tokenParts) !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$plainToken = $tokenParts[1];
|
||||
|
||||
// Check token di database
|
||||
$validToken = PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->where('token', hash('sha256', $plainToken))
|
||||
->where(function($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->first();
|
||||
|
||||
return $validToken !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check apakah ada user lain yang login (token baru dibuat)
|
||||
*/
|
||||
private function hasOtherUserLoggedIn($currentUser): bool
|
||||
{
|
||||
$sessionUserId = Session::get('user_id');
|
||||
|
||||
// Jika ada user_id di session tapi tidak match dengan current user
|
||||
if ($sessionUserId && $sessionUserId != $currentUser->id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check apakah ada token aktif lain untuk user ini
|
||||
$activeTokens = PersonalAccessToken::where('tokenable_id', $currentUser->id)
|
||||
->where('tokenable_type', get_class($currentUser))
|
||||
->where(function($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->count();
|
||||
|
||||
// Jika tidak ada token aktif, kemungkinan user lain sudah login
|
||||
return $activeTokens === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate token baru untuk user
|
||||
*/
|
||||
private function generateNewToken($user): void
|
||||
{
|
||||
// Hapus token lama
|
||||
PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->delete();
|
||||
|
||||
// Generate token baru
|
||||
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||
$token = $user->createToken($tokenName, ['*'], now()->addDays(30))->plainTextToken;
|
||||
|
||||
// Simpan token di session
|
||||
Session::put('api_token', $token);
|
||||
Session::put('user_id', $user->id);
|
||||
Session::put('login_timestamp', now()->timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force logout user dan clear semua sessions
|
||||
*/
|
||||
private function forceLogout(Request $request, string $reason = 'Session tidak valid'): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($user) {
|
||||
// Delete all tokens for this user
|
||||
PersonalAccessToken::where('tokenable_id', $user->id)
|
||||
->where('tokenable_type', get_class($user))
|
||||
->delete();
|
||||
}
|
||||
|
||||
// Clear session
|
||||
Session::forget(['api_token', 'user_id', 'login_timestamp']);
|
||||
Auth::guard('web')->logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect ke login dengan pesan error
|
||||
*/
|
||||
private function redirectToLogin(Request $request, string $message): Response
|
||||
{
|
||||
if ($request->expectsJson() || $request->ajax()) {
|
||||
return response()->json([
|
||||
'error' => $message,
|
||||
'redirect' => route('login'),
|
||||
'force_logout' => true
|
||||
], 401);
|
||||
}
|
||||
|
||||
return redirect()->route('login')->with('error', $message);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
$middleware->alias([
|
||||
'validate.api.token.web' => \App\Http\Middleware\ValidateApiTokenForWeb::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
$exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $th){
|
||||
|
||||
@@ -14,6 +14,8 @@ class MenuSeeder extends Seeder
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Menu::whereIn('name', ['Data Pajak','Pajak'])->delete();
|
||||
|
||||
$menus = [
|
||||
[
|
||||
"name" => "Neng Bedas",
|
||||
@@ -193,6 +195,12 @@ class MenuSeeder extends Seeder
|
||||
"icon" => null,
|
||||
"sort_order" => 9,
|
||||
],
|
||||
[
|
||||
"name" => "Pajak",
|
||||
"url" => "taxation",
|
||||
"icon" => null,
|
||||
"sort_order" => 10,
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
@@ -270,21 +278,6 @@ class MenuSeeder extends Seeder
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
"name" => "Pajak",
|
||||
"url" => "/tax",
|
||||
"icon" => "mingcute:coin-line",
|
||||
"parent_id" => null,
|
||||
"sort_order" => 10,
|
||||
"children" => [
|
||||
[
|
||||
"name" => "Data Pajak",
|
||||
"url" => "taxation",
|
||||
"icon" => null,
|
||||
"sort_order" => 1,
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($menus as $menuData) {
|
||||
@@ -292,7 +285,7 @@ class MenuSeeder extends Seeder
|
||||
}
|
||||
}
|
||||
|
||||
private function createOrUpdateMenu($menuData, $parentId = null){
|
||||
private function createOrUpdateMenu($menuData, $parentId = null){
|
||||
$menuData['parent_id'] = $parentId;
|
||||
|
||||
$menu = Menu::updateOrCreate(['name' => $menuData['name']], Arr::except($menuData, ['children']));
|
||||
|
||||
@@ -24,9 +24,8 @@ class UsersRoleMenuSeeder extends Seeder
|
||||
'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize',
|
||||
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
||||
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'PETA',
|
||||
'Lap Pimpinan', 'Dalam Sistem', 'Luar Sistem', 'Google Sheets', 'TPA TPT',
|
||||
'Approval Pejabat', 'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)', 'Lap Pertumbuhan',
|
||||
'Pajak', 'Data Pajak'
|
||||
'Lap Pimpinan', 'Dalam Sistem', 'Luar Sistem', 'Google Sheets', 'TPA TPT', 'Pajak',
|
||||
'Approval Pejabat', 'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)', 'Lap Pertumbuhan'
|
||||
])->get()->keyBy('name');
|
||||
|
||||
// Define access levels for each role
|
||||
@@ -37,7 +36,7 @@ class UsersRoleMenuSeeder extends Seeder
|
||||
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
||||
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'Dalam Sistem',
|
||||
'Luar Sistem', 'Lap Pimpinan', 'Google Sheets', 'TPA TPT', 'Approval Pejabat',
|
||||
'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)', 'Lap Pertumbuhan'
|
||||
'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)', 'Lap Pertumbuhan', 'Pajak'
|
||||
],
|
||||
'user' => ['Dashboard', 'Data', 'Laporan', 'Neng Bedas',
|
||||
'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize',
|
||||
@@ -68,10 +67,10 @@ class UsersRoleMenuSeeder extends Seeder
|
||||
|
||||
// Attach User to role super admin
|
||||
$accountSuperadmin = User::where('email', 'superadmin@sibedas.com')->first();
|
||||
$accountDevelopment = User::where('email', 'development@sibedas.com')->first();
|
||||
$accountUser = User::where('email', 'user@sibedas.com')->first();
|
||||
// $accountDefault = User::where('email','user@demo.com')->first();
|
||||
$accountSuperadmin->roles()->sync([$roles['superadmin']->id]);
|
||||
$accountDevelopment->roles()->sync([$roles['superadmin']->id]);
|
||||
$accountUser->roles()->sync([$roles['user']->id]);
|
||||
// $accountDefault->roles()->sync([$roles['user']->id]);
|
||||
}
|
||||
}
|
||||
|
||||
233
public/js/utils/simple-session-validator.js
Normal file
233
public/js/utils/simple-session-validator.js
Normal file
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* Simple Session Validator
|
||||
* Menangani validasi session tanpa periodic checking
|
||||
* Hanya respond pada 401 errors dari API requests
|
||||
*/
|
||||
class SimpleSessionValidator {
|
||||
constructor() {
|
||||
this.isLoggingOut = false;
|
||||
this.consecutiveErrors = 0;
|
||||
this.maxConsecutiveErrors = 2;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log("Simple Session Validator initialized");
|
||||
|
||||
// Intercept all AJAX requests untuk detect 401
|
||||
this.interceptAjaxRequests();
|
||||
|
||||
// Listen untuk page visibility changes
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (!document.hidden && this.consecutiveErrors > 0) {
|
||||
// Reset errors ketika user kembali ke tab
|
||||
this.consecutiveErrors = 0;
|
||||
console.log("Page visible, reset error counter");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interceptAjaxRequests() {
|
||||
const validator = this;
|
||||
|
||||
// Intercept fetch requests
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = async function (...args) {
|
||||
try {
|
||||
const response = await originalFetch(...args);
|
||||
|
||||
// Check if response is 401 dan URL mengandung /api/
|
||||
if (response.status === 401) {
|
||||
const url = args[0];
|
||||
if (typeof url === "string" && url.includes("/api/")) {
|
||||
console.log("401 detected in API fetch request:", url);
|
||||
validator.handleApiError401(url);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Fetch request failed:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Intercept XMLHttpRequest
|
||||
const originalXHRSend = XMLHttpRequest.prototype.send;
|
||||
const originalXHROpen = XMLHttpRequest.prototype.open;
|
||||
|
||||
XMLHttpRequest.prototype.open = function (...args) {
|
||||
this._url = args[1];
|
||||
return originalXHROpen.apply(this, args);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function (...args) {
|
||||
const xhr = this;
|
||||
const originalOnReadyStateChange = xhr.onreadystatechange;
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4 && xhr.status === 401) {
|
||||
if (xhr._url && xhr._url.includes("/api/")) {
|
||||
console.log(
|
||||
"401 detected in API XHR request:",
|
||||
xhr._url
|
||||
);
|
||||
validator.handleApiError401(xhr._url);
|
||||
}
|
||||
}
|
||||
|
||||
if (originalOnReadyStateChange) {
|
||||
originalOnReadyStateChange.apply(xhr, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return originalXHRSend.apply(this, args);
|
||||
};
|
||||
|
||||
// Intercept jQuery AJAX jika tersedia
|
||||
if (typeof $ !== "undefined" && $.ajaxSetup) {
|
||||
$(document).ajaxError(function (event, xhr, settings, thrownError) {
|
||||
if (
|
||||
xhr.status === 401 &&
|
||||
settings.url &&
|
||||
settings.url.includes("/api/")
|
||||
) {
|
||||
console.log(
|
||||
"401 detected in jQuery AJAX request:",
|
||||
settings.url
|
||||
);
|
||||
validator.handleApiError401(settings.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleApiError401(url) {
|
||||
if (this.isLoggingOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`API 401 Error detected on ${url}`);
|
||||
|
||||
// Increment consecutive errors
|
||||
this.consecutiveErrors++;
|
||||
|
||||
// Jika sudah 2x error berturut-turut, logout
|
||||
if (this.consecutiveErrors >= this.maxConsecutiveErrors) {
|
||||
this.handleSessionInvalid(
|
||||
"Token API tidak valid. User lain telah login."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleSessionInvalid(message) {
|
||||
if (this.isLoggingOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoggingOut = true;
|
||||
console.log("Handling session invalid:", message);
|
||||
|
||||
// Show notification
|
||||
this.showNotification(message, "warning");
|
||||
|
||||
// Redirect to login after 3 seconds
|
||||
setTimeout(() => {
|
||||
this.forceLogout();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
showNotification(message, type = "info") {
|
||||
// Try different notification libraries
|
||||
if (typeof toastr !== "undefined") {
|
||||
toastr[type](message);
|
||||
} else if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Peringatan Session",
|
||||
text: message,
|
||||
icon: type,
|
||||
confirmButtonText: "OK",
|
||||
allowOutsideClick: false,
|
||||
timer: 5000,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
} else {
|
||||
// Create custom notification
|
||||
this.createCustomNotification(message, type);
|
||||
}
|
||||
}
|
||||
|
||||
createCustomNotification(message, type) {
|
||||
const notification = document.createElement("div");
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: ${type === "warning" ? "#ffc107" : "#007bff"};
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px;
|
||||
z-index: 9999;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
`;
|
||||
notification.innerHTML = `
|
||||
<strong>Peringatan!</strong><br>
|
||||
${message}<br>
|
||||
<small>Anda akan diarahkan ke halaman login...</small>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Remove after 8 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
forceLogout() {
|
||||
console.log("Forcing logout...");
|
||||
|
||||
// Try to logout via API first
|
||||
fetch("/logout", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-TOKEN":
|
||||
document
|
||||
.querySelector('meta[name="csrf-token"]')
|
||||
?.getAttribute("content") || "",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then(() => {
|
||||
window.location.href = "/login";
|
||||
})
|
||||
.catch(() => {
|
||||
// Force redirect even if logout fails
|
||||
window.location.href = "/login";
|
||||
});
|
||||
}
|
||||
|
||||
// Method untuk manual reset
|
||||
reset() {
|
||||
this.consecutiveErrors = 0;
|
||||
this.isLoggingOut = false;
|
||||
console.log("Session validator reset");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.simpleSessionValidator = new SimpleSessionValidator();
|
||||
});
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = SimpleSessionValidator;
|
||||
}
|
||||
@@ -171,16 +171,23 @@ class ApiTokenManager {
|
||||
try {
|
||||
const response = await fetch(url, mergedOptions);
|
||||
|
||||
// If unauthorized, try to generate new token
|
||||
if (response.status === 401 && this.hasToken()) {
|
||||
console.log('Token expired, generating new token...');
|
||||
const newToken = await this.generateToken();
|
||||
|
||||
if (newToken) {
|
||||
// Retry request with new token
|
||||
mergedOptions.headers.Authorization = `Bearer ${newToken}`;
|
||||
return fetch(url, mergedOptions);
|
||||
// If unauthorized, check if it's a session issue
|
||||
if (response.status === 401) {
|
||||
if (this.hasToken()) {
|
||||
console.log('Token expired, generating new token...');
|
||||
const newToken = await this.generateToken();
|
||||
|
||||
if (newToken) {
|
||||
// Retry request with new token
|
||||
mergedOptions.headers.Authorization = `Bearer ${newToken}`;
|
||||
return fetch(url, mergedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// If still 401, it might be a session issue
|
||||
console.log('Session invalid, redirecting to login...');
|
||||
this.handleSessionInvalid();
|
||||
return response;
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -189,6 +196,33 @@ class ApiTokenManager {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
handleSessionInvalid() {
|
||||
// Show notification
|
||||
this.showNotification('Session Anda telah berakhir. Silakan login ulang.', 'warning');
|
||||
|
||||
// Redirect to login after 3 seconds
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
// Check if notification library exists
|
||||
if (typeof toastr !== 'undefined') {
|
||||
toastr[type](message);
|
||||
} else if (typeof Swal !== 'undefined') {
|
||||
Swal.fire({
|
||||
title: 'Peringatan',
|
||||
text: message,
|
||||
icon: type,
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
} else {
|
||||
// Fallback to alert
|
||||
alert(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
|
||||
337
resources/js/utils/api-token-web-validator.js
Normal file
337
resources/js/utils/api-token-web-validator.js
Normal file
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* API Token Web Validator
|
||||
* Menangani validasi token API untuk web requests dan auto-logout
|
||||
*/
|
||||
class ApiTokenWebValidator {
|
||||
constructor() {
|
||||
this.isLoggingOut = false;
|
||||
this.checkInterval = null;
|
||||
this.lastCheckTime = 0;
|
||||
this.consecutiveErrors = 0;
|
||||
this.maxConsecutiveErrors = 3;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log("API Token Web Validator initialized");
|
||||
|
||||
// Start periodic validation
|
||||
this.startPeriodicValidation();
|
||||
|
||||
// Intercept all AJAX requests
|
||||
this.interceptAjaxRequests();
|
||||
|
||||
// Listen for page visibility changes
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (!document.hidden) {
|
||||
this.validateToken();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for window focus
|
||||
window.addEventListener("focus", () => {
|
||||
this.validateToken();
|
||||
});
|
||||
}
|
||||
|
||||
startPeriodicValidation() {
|
||||
// Check token validity every 15 seconds
|
||||
this.checkInterval = setInterval(() => {
|
||||
this.validateToken();
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
stopPeriodicValidation() {
|
||||
if (this.checkInterval) {
|
||||
clearInterval(this.checkInterval);
|
||||
this.checkInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
async validateToken() {
|
||||
// Prevent multiple simultaneous checks
|
||||
if (this.isLoggingOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent checking too frequently
|
||||
const now = Date.now();
|
||||
if (now - this.lastCheckTime < 3000) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastCheckTime = now;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/check-session", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
console.log("Token validation failed: 401 Unauthorized");
|
||||
this.consecutiveErrors++;
|
||||
|
||||
if (this.consecutiveErrors >= this.maxConsecutiveErrors) {
|
||||
this.handleTokenInvalid(
|
||||
"Token API tidak valid. User lain mungkin telah login."
|
||||
);
|
||||
}
|
||||
} else if (response.status === 200) {
|
||||
// Reset consecutive errors on successful response
|
||||
this.consecutiveErrors = 0;
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.valid) {
|
||||
console.log("Token validation failed: Session invalid");
|
||||
this.handleTokenInvalid(
|
||||
"Session tidak valid. Silakan login ulang."
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Token validation error:", error);
|
||||
this.consecutiveErrors++;
|
||||
|
||||
if (this.consecutiveErrors >= this.maxConsecutiveErrors) {
|
||||
this.handleTokenInvalid(
|
||||
"Terjadi kesalahan koneksi. Silakan login ulang."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interceptAjaxRequests() {
|
||||
// Intercept fetch requests
|
||||
const originalFetch = window.fetch;
|
||||
const validator = this;
|
||||
|
||||
window.fetch = async function (...args) {
|
||||
try {
|
||||
const response = await originalFetch(...args);
|
||||
|
||||
// Check if response is 401 and it's an API call
|
||||
if (response.status === 401) {
|
||||
const url = args[0];
|
||||
if (typeof url === "string" && url.includes("/api/")) {
|
||||
console.log("401 detected in API fetch request:", url);
|
||||
validator.handleApiError401(response, url);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Fetch request failed:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Intercept XMLHttpRequest
|
||||
const originalXHRSend = XMLHttpRequest.prototype.send;
|
||||
const originalXHROpen = XMLHttpRequest.prototype.open;
|
||||
|
||||
XMLHttpRequest.prototype.open = function (...args) {
|
||||
this._url = args[1];
|
||||
return originalXHROpen.apply(this, args);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function (...args) {
|
||||
const xhr = this;
|
||||
const originalOnReadyStateChange = xhr.onreadystatechange;
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4 && xhr.status === 401) {
|
||||
if (xhr._url && xhr._url.includes("/api/")) {
|
||||
console.log(
|
||||
"401 detected in API XHR request:",
|
||||
xhr._url
|
||||
);
|
||||
validator.handleApiError401(null, xhr._url);
|
||||
}
|
||||
}
|
||||
|
||||
if (originalOnReadyStateChange) {
|
||||
originalOnReadyStateChange.apply(xhr, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return originalXHRSend.apply(this, args);
|
||||
};
|
||||
|
||||
// Intercept jQuery AJAX if available
|
||||
if (typeof $ !== "undefined" && $.ajaxSetup) {
|
||||
$(document).ajaxError(function (event, xhr, settings, thrownError) {
|
||||
if (
|
||||
xhr.status === 401 &&
|
||||
settings.url &&
|
||||
settings.url.includes("/api/")
|
||||
) {
|
||||
console.log(
|
||||
"401 detected in jQuery AJAX request:",
|
||||
settings.url
|
||||
);
|
||||
validator.handleApiError401(null, settings.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleApiError401(response, url) {
|
||||
if (this.isLoggingOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`API 401 Error detected on ${url}`);
|
||||
|
||||
// Increment consecutive errors
|
||||
this.consecutiveErrors++;
|
||||
|
||||
// If we get multiple 401s, likely token is invalid
|
||||
if (this.consecutiveErrors >= 2) {
|
||||
this.handleTokenInvalid(
|
||||
"Token API tidak valid. User lain telah login atau session berakhir."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleTokenInvalid(message) {
|
||||
if (this.isLoggingOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoggingOut = true;
|
||||
this.stopPeriodicValidation();
|
||||
|
||||
console.log("Handling token invalid:", message);
|
||||
|
||||
// Show notification
|
||||
this.showNotification(message, "warning");
|
||||
|
||||
// Clear any stored data
|
||||
this.clearStoredData();
|
||||
|
||||
// Redirect to login after 3 seconds
|
||||
setTimeout(() => {
|
||||
this.forceLogout();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
showNotification(message, type = "info") {
|
||||
// Try different notification libraries
|
||||
if (typeof toastr !== "undefined") {
|
||||
toastr[type](message);
|
||||
} else if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Peringatan Session",
|
||||
text: message,
|
||||
icon: type,
|
||||
confirmButtonText: "OK",
|
||||
allowOutsideClick: false,
|
||||
timer: 5000,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
} else {
|
||||
// Create custom notification
|
||||
this.createCustomNotification(message, type);
|
||||
}
|
||||
}
|
||||
|
||||
createCustomNotification(message, type) {
|
||||
const notification = document.createElement("div");
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: ${type === "warning" ? "#ffc107" : "#007bff"};
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px;
|
||||
z-index: 9999;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
`;
|
||||
notification.innerHTML = `
|
||||
<strong>Peringatan!</strong><br>
|
||||
${message}
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Remove after 8 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
clearStoredData() {
|
||||
// Clear localStorage
|
||||
try {
|
||||
localStorage.clear();
|
||||
} catch (e) {
|
||||
console.warn("Could not clear localStorage:", e);
|
||||
}
|
||||
|
||||
// Clear sessionStorage
|
||||
try {
|
||||
sessionStorage.clear();
|
||||
} catch (e) {
|
||||
console.warn("Could not clear sessionStorage:", e);
|
||||
}
|
||||
}
|
||||
|
||||
forceLogout() {
|
||||
console.log("Forcing logout...");
|
||||
|
||||
// Try to logout via API first
|
||||
fetch("/logout", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-TOKEN":
|
||||
document
|
||||
.querySelector('meta[name="csrf-token"]')
|
||||
?.getAttribute("content") || "",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then(() => {
|
||||
window.location.href = "/login";
|
||||
})
|
||||
.catch(() => {
|
||||
// Force redirect even if logout fails
|
||||
window.location.href = "/login";
|
||||
});
|
||||
}
|
||||
|
||||
// Method untuk manual reset
|
||||
reset() {
|
||||
this.consecutiveErrors = 0;
|
||||
this.isLoggingOut = false;
|
||||
this.lastCheckTime = 0;
|
||||
}
|
||||
|
||||
// Method untuk manual logout
|
||||
logout() {
|
||||
this.handleTokenInvalid("Manual logout requested");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.apiTokenWebValidator = new ApiTokenWebValidator();
|
||||
});
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = ApiTokenWebValidator;
|
||||
}
|
||||
264
resources/js/utils/multi-user-session-handler.js
Normal file
264
resources/js/utils/multi-user-session-handler.js
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Multi-User Session Handler
|
||||
* Menangani kasus ketika multiple user login dan session conflict
|
||||
*/
|
||||
class MultiUserSessionHandler {
|
||||
constructor() {
|
||||
this.checkInterval = null;
|
||||
this.lastCheckTime = 0;
|
||||
this.isChecking = false;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log("Multi-User Session Handler initialized");
|
||||
|
||||
// Check session setiap 10 detik
|
||||
this.startPeriodicCheck();
|
||||
|
||||
// Check session ketika tab menjadi aktif
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (!document.hidden) {
|
||||
this.checkSession();
|
||||
}
|
||||
});
|
||||
|
||||
// Check session ketika window focus
|
||||
window.addEventListener("focus", () => {
|
||||
this.checkSession();
|
||||
});
|
||||
|
||||
// Check session ketika user melakukan interaksi
|
||||
document.addEventListener("click", () => {
|
||||
this.checkSession();
|
||||
});
|
||||
|
||||
// Check session ketika ada AJAX request
|
||||
this.interceptAjaxRequests();
|
||||
}
|
||||
|
||||
startPeriodicCheck() {
|
||||
this.checkInterval = setInterval(() => {
|
||||
this.checkSession();
|
||||
}, 10000); // 10 detik
|
||||
}
|
||||
|
||||
stopPeriodicCheck() {
|
||||
if (this.checkInterval) {
|
||||
clearInterval(this.checkInterval);
|
||||
this.checkInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
async checkSession() {
|
||||
// Prevent multiple simultaneous checks
|
||||
if (this.isChecking) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent checking too frequently
|
||||
const now = Date.now();
|
||||
if (now - this.lastCheckTime < 5000) {
|
||||
// 5 detik minimum interval
|
||||
return;
|
||||
}
|
||||
|
||||
this.isChecking = true;
|
||||
this.lastCheckTime = now;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/check-session", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
console.log("Session invalid detected, logging out...");
|
||||
this.handleSessionInvalid();
|
||||
} else if (response.status === 200) {
|
||||
const data = await response.json();
|
||||
if (!data.valid) {
|
||||
console.log("Session validation failed, logging out...");
|
||||
this.handleSessionInvalid();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Session check failed:", error);
|
||||
} finally {
|
||||
this.isChecking = false;
|
||||
}
|
||||
}
|
||||
|
||||
interceptAjaxRequests() {
|
||||
// Intercept fetch requests
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = async (...args) => {
|
||||
try {
|
||||
const response = await originalFetch(...args);
|
||||
|
||||
// Check if response is 401
|
||||
if (response.status === 401) {
|
||||
console.log(
|
||||
"401 detected in fetch request, checking session..."
|
||||
);
|
||||
this.checkSession();
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Fetch request failed:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Intercept XMLHttpRequest
|
||||
const originalXHROpen = XMLHttpRequest.prototype.open;
|
||||
const originalXHRSend = XMLHttpRequest.prototype.send;
|
||||
|
||||
XMLHttpRequest.prototype.open = function (...args) {
|
||||
this._url = args[1];
|
||||
return originalXHROpen.apply(this, args);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function (...args) {
|
||||
const xhr = this;
|
||||
const originalOnReadyStateChange = xhr.onreadystatechange;
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4 && xhr.status === 401) {
|
||||
console.log(
|
||||
"401 detected in XHR request, checking session..."
|
||||
);
|
||||
window.multiUserSessionHandler.checkSession();
|
||||
}
|
||||
|
||||
if (originalOnReadyStateChange) {
|
||||
originalOnReadyStateChange.apply(xhr, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return originalXHRSend.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
handleSessionInvalid() {
|
||||
this.stopPeriodicCheck();
|
||||
|
||||
// Show notification
|
||||
this.showNotification(
|
||||
"Session Anda telah berakhir karena user lain login. Silakan login ulang.",
|
||||
"warning"
|
||||
);
|
||||
|
||||
// Clear any stored data
|
||||
this.clearStoredData();
|
||||
|
||||
// Redirect to login after 2 seconds
|
||||
setTimeout(() => {
|
||||
window.location.href = "/login";
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
showNotification(message, type = "info") {
|
||||
// Try different notification libraries
|
||||
if (typeof toastr !== "undefined") {
|
||||
toastr[type](message);
|
||||
} else if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Peringatan Session",
|
||||
text: message,
|
||||
icon: type,
|
||||
confirmButtonText: "OK",
|
||||
allowOutsideClick: false,
|
||||
});
|
||||
} else if (typeof alert !== "undefined") {
|
||||
alert(message);
|
||||
} else {
|
||||
// Create custom notification
|
||||
this.createCustomNotification(message, type);
|
||||
}
|
||||
}
|
||||
|
||||
createCustomNotification(message, type) {
|
||||
const notification = document.createElement("div");
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: ${type === "warning" ? "#ffc107" : "#007bff"};
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px;
|
||||
z-index: 9999;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
`;
|
||||
notification.textContent = message;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
clearStoredData() {
|
||||
// Clear localStorage
|
||||
try {
|
||||
localStorage.clear();
|
||||
} catch (e) {
|
||||
console.warn("Could not clear localStorage:", e);
|
||||
}
|
||||
|
||||
// Clear sessionStorage
|
||||
try {
|
||||
sessionStorage.clear();
|
||||
} catch (e) {
|
||||
console.warn("Could not clear sessionStorage:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Method untuk manual logout
|
||||
logout() {
|
||||
this.stopPeriodicCheck();
|
||||
|
||||
fetch("/logout", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-TOKEN":
|
||||
document
|
||||
.querySelector('meta[name="csrf-token"]')
|
||||
?.getAttribute("content") || "",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then(() => {
|
||||
this.clearStoredData();
|
||||
window.location.href = "/login";
|
||||
})
|
||||
.catch(() => {
|
||||
this.clearStoredData();
|
||||
window.location.href = "/login";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.multiUserSessionHandler = new MultiUserSessionHandler();
|
||||
});
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = MultiUserSessionHandler;
|
||||
}
|
||||
126
resources/js/utils/session-manager.js
Normal file
126
resources/js/utils/session-manager.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Session Manager untuk menangani multi-user session
|
||||
*/
|
||||
class SessionManager {
|
||||
constructor() {
|
||||
this.checkInterval = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Check session setiap 30 detik
|
||||
this.startSessionCheck();
|
||||
|
||||
// Listen untuk visibility change (tab focus/blur)
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (!document.hidden) {
|
||||
this.checkSession();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen untuk storage events (multi-tab)
|
||||
window.addEventListener("storage", (e) => {
|
||||
if (e.key === "session_invalid") {
|
||||
this.handleSessionInvalid();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startSessionCheck() {
|
||||
this.checkInterval = setInterval(() => {
|
||||
this.checkSession();
|
||||
}, 30000); // 30 detik
|
||||
}
|
||||
|
||||
stopSessionCheck() {
|
||||
if (this.checkInterval) {
|
||||
clearInterval(this.checkInterval);
|
||||
this.checkInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
async checkSession() {
|
||||
try {
|
||||
const response = await fetch("/api/check-session", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
this.handleSessionInvalid();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Session check failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
handleSessionInvalid() {
|
||||
this.stopSessionCheck();
|
||||
|
||||
// Show notification
|
||||
this.showNotification(
|
||||
"Session Anda telah berakhir. Silakan login ulang.",
|
||||
"warning"
|
||||
);
|
||||
|
||||
// Redirect to login after 3 seconds
|
||||
setTimeout(() => {
|
||||
window.location.href = "/login";
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
showNotification(message, type = "info") {
|
||||
// Check if notification library exists (like Toastr, SweetAlert, etc.)
|
||||
if (typeof toastr !== "undefined") {
|
||||
toastr[type](message);
|
||||
} else if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Peringatan",
|
||||
text: message,
|
||||
icon: type,
|
||||
confirmButtonText: "OK",
|
||||
});
|
||||
} else {
|
||||
// Fallback to alert
|
||||
alert(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Method untuk logout manual
|
||||
logout() {
|
||||
this.stopSessionCheck();
|
||||
|
||||
fetch("/logout", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-TOKEN":
|
||||
document
|
||||
.querySelector('meta[name="csrf-token"]')
|
||||
?.getAttribute("content") || "",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then(() => {
|
||||
window.location.href = "/login";
|
||||
})
|
||||
.catch(() => {
|
||||
window.location.href = "/login";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize session manager when DOM is ready
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.sessionManager = new SessionManager();
|
||||
});
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = SessionManager;
|
||||
}
|
||||
@@ -6,6 +6,11 @@
|
||||
@vite('resources/js/app.js')
|
||||
@endif
|
||||
|
||||
<!-- Simple Session Validator -->
|
||||
@auth
|
||||
<script src="{{ asset('js/utils/simple-session-validator.js') }}"></script>
|
||||
@endauth
|
||||
|
||||
@yield('scripts')
|
||||
|
||||
<script>
|
||||
|
||||
@@ -44,7 +44,7 @@ Route::get('/quick-search/{id}', [QuickSearchController::class, 'show'])->name('
|
||||
Route::get('/quick-search/{uuid}/task-assignments', [QuickSearchController::class, 'task_assignments'])->name('api.quick-search-task-assignments');
|
||||
|
||||
// auth
|
||||
Route::group(['middleware' => 'auth'], function(){
|
||||
Route::group(['middleware' => ['auth', 'validate.api.token.web']], function(){
|
||||
|
||||
Route::get('', [BigDataController::class, 'index'])->name('any');
|
||||
Route::get('/home', [HomeController::class, 'index'])->name('home');
|
||||
|
||||
Reference in New Issue
Block a user