Tambah Auth Fetures

This commit is contained in:
2026-02-20 00:41:09 +08:00
parent 6ab40e6d65
commit 0d6d28e4ea
14 changed files with 3898 additions and 9 deletions

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;
class AuthController extends Controller
{
public function showLogin()
{
if (Auth::check()) {
return redirect('/dashboard');
}
return view('auth.login');
}
public function login(Request $request)
{
$request->validate([
'login' => 'required',
'password' => 'required'
]);
$loginType = filter_var($request->login, FILTER_VALIDATE_EMAIL) ? 'email' : 'employee_id';
$credentials = [
$loginType => $request->login,
'password' => $request->password
];
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
session(['token_version' => Auth::user()->token_version]);
Redis::lpush('login_logs', json_encode([
'user_id' => Auth::id(),
'event' => 'login',
'ip' => request()->ip(),
'time' => now()
]));
return redirect('/dashboard');
}
return back()->withErrors([
'login' => 'ID Pekerja / Email atau Password salah.'
]);
}
public function logout(Request $request)
{
$userId = Auth::id(); // ambil dulu sebelum logout
Redis::lpush('login_logs', json_encode([
'user_id' => $userId,
'event' => 'logout',
'ip' => request()->ip(),
'time' => now()
]));
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/login');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
class CheckTokenVersion
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::check()) {
if (session('token_version') != Auth::user()->token_version) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/login')->with('message', 'Session expired. Please login again.');
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
class IdleTimeout
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::check()) {
$timeout = 1800; // 15 minit (900 saat)
if (session()->has('last_activity')) {
if (time() - session('last_activity') > $timeout) {
Auth::logout();
session()->invalidate();
return redirect('/login')->with('message', 'Session expired.');
}
}
session(['last_activity' => time()]);
}
return $next($request);
}
}

View File

@@ -19,6 +19,7 @@ class User extends Authenticatable
*/
protected $fillable = [
'name',
'employee_id',
'email',
'password',
];

View File

@@ -11,7 +11,10 @@
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
$middleware->alias([
'auth.idle' => \App\Http\Middleware\IdleTimeout::class,
'auth.token' => \App\Http\Middleware\CheckTokenVersion::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//

View File

@@ -18,7 +18,7 @@
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
'driver' => env('SESSION_DRIVER', 'redis'),
/*
|--------------------------------------------------------------------------
@@ -32,7 +32,7 @@
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'lifetime' => (int) env('SESSION_LIFETIME', 30),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('employee_id')->unique()->after('id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->integer('token_version')->default(1);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
};

View File

@@ -15,11 +15,6 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
$this->call(UserSeeder::class);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
User::create([
'name' => 'Admin',
'employee_id' => 'ADMIN001',
'email' => 'admin@company.com',
'password' => Hash::make('admin123')
]);
}
}

View File

@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sign In | UBold - Responsive Bootstrap 5 Admin Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="UBold is a modern, responsive admin dashboard available on ThemeForest. Ideal for building CRM, CMS, project management tools, and custom web applications with a clean UI, flexible layouts, and rich features.">
<meta name="keywords" content="UBold, admin dashboard, ThemeForest, Bootstrap 5 admin, responsive admin, CRM dashboard, CMS admin, web app UI, admin theme, premium admin template">
<meta name="author" content="Coderthemes">
<!-- App favicon -->
<link rel="shortcut icon" href="assets/images/favicon.ico">
<!-- Theme Config Js -->
<script src="assets/js/config.js"></script>
<!-- Vendor css -->
<link href="assets/css/vendors.min.css" rel="stylesheet" type="text/css">
<!-- App css -->
<link href="assets/css/app.min.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="auth-box p-0 w-100">
<div class="row w-100 g-0">
<div class="col-md-auto">
<!--Auth Box content -->
<div class="card auth-box-form border-0 mb-0">
<div class="position-absolute top-0 end-0" style="width: 180px;">
<svg style="opacity: 0.075; width: 100%; height: auto;" width="600" height="560" viewBox="0 0 600 560" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_948_1464)">
<mask id="mask0_948_1464" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="600" height="1200">
<path d="M0 0L0 1200H600L600 0H0Z" fill="white" />
</mask>
<g mask="url(#mask0_948_1464)">
<path d="M537.448 166.697L569.994 170.892L550.644 189.578L537.448 166.697Z" fill="#FF4C3E" />
</g>
<mask id="mask1_948_1464" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="600" height="1200">
<path d="M0 0L0 1200H600L600 0H0Z" fill="white" />
</mask>
<g mask="url(#mask1_948_1464)">
<path d="M364.093 327.517L332.306 359.304C321.885 369.725 304.989 369.725 294.568 359.304L262.781 327.517C252.36 317.096 252.36 300.2 262.781 289.779L294.568 257.992C304.989 247.571 321.885 247.571 332.306 257.992L364.093 289.779C374.514 300.2 374.514 317.096 364.093 327.517Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M377.923 101.019L315.106 163.836C299.517 179.425 274.242 179.425 258.653 163.836L195.836 101.019C180.247 85.4301 180.247 60.1551 195.836 44.5661L258.653 -18.251C274.242 -33.84 299.517 -33.84 315.106 -18.251L377.923 44.5661C393.512 60.1551 393.512 85.4301 377.923 101.019Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M696.956 -50.1542L650.648 -3.84605C635.059 11.743 609.784 11.743 594.195 -3.84605L547.887 -50.1542C532.298 -65.7432 532.298 -91.0182 547.887 -106.607L594.195 -152.915C609.784 -168.504 635.059 -168.504 650.648 -152.915L696.956 -106.607C712.545 -91.0172 712.545 -65.7432 696.956 -50.1542Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M758.493 103.825L712.185 150.133C696.596 165.722 671.321 165.722 655.733 150.133L609.425 103.825C593.836 88.2359 593.836 62.9608 609.425 47.3718L655.733 1.06386C671.322 -14.5251 696.597 -14.5251 712.185 1.06386L758.493 47.3718C774.082 62.9608 774.082 88.2359 758.493 103.825Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M674.716 80.202L501.67 253.248C486.081 268.837 460.806 268.837 445.217 253.248L272.171 80.202C256.582 64.613 256.582 39.338 272.171 23.749L445.217 -149.297C460.806 -164.886 486.081 -164.886 501.67 -149.297L674.716 23.75C690.305 39.339 690.305 64.613 674.716 80.202Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M579.394 334.046L523.831 389.609C508.242 405.198 482.967 405.198 467.378 389.609L411.815 334.046C396.226 318.457 396.226 293.182 411.815 277.593L467.378 222.03C482.967 206.441 508.242 206.441 523.831 222.03L579.394 277.593C594.983 293.182 594.983 318.457 579.394 334.046Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M185.618 87.2381L158.648 114.208C146.305 126.551 126.293 126.551 113.95 114.208L86.9799 87.2381C74.6369 74.8951 74.6369 54.883 86.9799 42.539L113.95 15.569C126.293 3.22605 146.305 3.22605 158.648 15.569L185.618 42.539C197.961 54.882 197.961 74.8941 185.618 87.2381Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M249.319 23.767L228.859 44.227C221.817 51.269 210.4 51.269 203.358 44.227L182.898 23.767C175.856 16.725 175.856 5.30798 182.898 -1.73402L203.358 -22.194C210.4 -29.236 221.817 -29.236 228.859 -22.194L249.319 -1.73402C256.361 5.30798 256.361 16.725 249.319 23.767Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M375.3 217.828L354.84 238.288C347.798 245.33 336.381 245.33 329.339 238.288L308.879 217.828C301.837 210.786 301.837 199.369 308.879 192.327L329.339 171.867C336.381 164.825 347.798 164.825 354.84 171.867L375.3 192.327C382.342 199.369 382.342 210.786 375.3 217.828Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M262.326 229.367L255.702 235.991C252.281 239.412 246.734 239.412 243.313 235.991L236.689 229.367C233.268 225.946 233.268 220.399 236.689 216.978L243.313 210.354C246.734 206.933 252.281 206.933 255.702 210.354L262.326 216.978C265.747 220.399 265.747 225.946 262.326 229.367Z" stroke="#089df1" stroke-width="2" stroke-miterlimit="10" />
<path d="M403.998 311.555L372.211 343.342C361.79 353.763 344.894 353.763 334.473 343.342L302.686 311.555C292.265 301.134 292.265 284.238 302.686 273.817L334.473 242.03C344.894 231.609 361.79 231.609 372.211 242.03L403.998 273.817C414.419 284.238 414.419 301.134 403.998 311.555Z" fill="#089df1" />
<path d="M417.828 85.0572L355.011 147.874C339.422 163.463 314.147 163.463 298.558 147.874L235.741 85.0572C220.152 69.4682 220.152 44.1931 235.741 28.6051L298.558 -34.2119C314.147 -49.8009 339.422 -49.8009 355.011 -34.2119L417.828 28.6051C433.417 44.1931 433.417 69.4682 417.828 85.0572Z" fill="#7b70ef" />
<path d="M714.621 64.24L541.575 237.286C525.986 252.875 500.711 252.875 485.122 237.286L312.076 64.24C296.487 48.651 296.487 23.376 312.076 7.787L485.122 -165.259C500.711 -180.848 525.986 -180.848 541.575 -165.259L714.621 7.787C730.21 23.377 730.21 48.651 714.621 64.24Z" fill="#f9bf59" />
<path d="M619.299 318.084L563.736 373.647C548.147 389.236 522.872 389.236 507.283 373.647L451.72 318.084C436.131 302.495 436.131 277.22 451.72 261.631L507.283 206.068C522.872 190.479 548.147 190.479 563.736 206.068L619.299 261.631C634.888 277.221 634.888 302.495 619.299 318.084Z" fill="#089df1" />
<path d="M225.523 71.276L198.553 98.2459C186.21 110.589 166.198 110.589 153.854 98.2459L126.884 71.276C114.541 58.933 114.541 38.921 126.884 26.578L153.854 -0.392014C166.197 -12.735 186.209 -12.735 198.553 -0.392014L225.523 26.578C237.866 38.92 237.866 58.932 225.523 71.276Z" fill="#f7577e" />
<path d="M289.224 7.80493L268.764 28.2649C261.722 35.3069 250.305 35.3069 243.263 28.2649L222.803 7.80493C215.761 0.762926 215.761 -10.6542 222.803 -17.6962L243.263 -38.1561C250.305 -45.1981 261.722 -45.1981 268.764 -38.1561L289.224 -17.6962C296.266 -10.6542 296.266 0.762926 289.224 7.80493Z" fill="#f7577e" />
<path d="M415.205 201.866L394.745 222.326C387.703 229.368 376.286 229.368 369.244 222.326L348.784 201.866C341.742 194.824 341.742 183.407 348.784 176.365L369.244 155.905C376.286 148.863 387.703 148.863 394.745 155.905L415.205 176.365C422.247 183.407 422.247 194.824 415.205 201.866Z" fill="#f7577e" />
<path d="M302.231 213.405L295.607 220.029C292.186 223.45 286.639 223.45 283.218 220.029L276.594 213.405C273.173 209.984 273.173 204.437 276.594 201.016L283.218 194.392C286.639 190.971 292.186 190.971 295.607 194.392L302.231 201.016C305.652 204.437 305.652 209.984 302.231 213.405Z" fill="#f7577e" />
</g>
</g>
<defs>
<clipPath id="clip0_948_1464">
<rect width="560" height="600" fill="white" transform="matrix(0 -1 1 0 0 560)" />
</clipPath>
</defs>
</svg>
</div>
<div class="card-body min-vh-100 d-flex flex-column justify-content-center">
<div class="auth-brand mb-0 text-center">
<a href="index.html" class="logo-dark">
<img src="assets/images/logo-black.png" alt="dark logo" height="28">
</a>
<a href="index.html" class="logo-light">
<img src="assets/images/logo.png" alt="logo" height="28">
</a>
</div>
<div class="mt-auto">
<p class="text-muted text-center auth-sub-text mx-auto">Lets get you signed in. Enter your email and password to continue.</p>
<form class="mt-4" method="POST" action="/login">
@csrf
@error('login')
<div style="color:red;">{{ $message }}</div>
@enderror
<div class="mb-3">
<label for="userEmail" class="form-label">Email or ID <span class="text-danger">*</span></label>
<div class="app-search">
<input type="text" class="form-control" name="login" id="login" placeholder="you@example.com" required>
<i data-lucide="circle-user" class="app-search-icon text-muted"></i>
</div>
</div>
<div class="mb-3">
<label for="userPassword" class="form-label">Password <span class="text-danger">*</span></label>
<div class="app-search">
<input type="password" class="form-control" name="password" id="password" placeholder="••••••••" required>
<i data-lucide="key-round" class="app-search-icon text-muted"></i>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="form-check">
<input class="form-check-input form-check-input-light fs-14" type="checkbox" checked id="rememberMe">
<label class="form-check-label" for="rememberMe">Keep me signed in</label>
</div>
<a href="auth-2-reset-pass.html" class="text-decoration-underline link-offset-3 text-muted">Forgot Password?</a>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary fw-bold py-2">Sign In</button>
</div>
</form>
</div>
<p class="text-muted text-center mt-4 mb-0">
New here? <a href="auth-2-sign-up.html" class="text-decoration-underline link-offset-3 fw-semibold">Create an account</a>
</p>
<p class="text-center text-muted mt-auto mb-0">
©
<script>document.write(new Date().getFullYear())</script> UBold by <span class="fw-semibold">Coderthemes</span>
</p>
</div>
</div>
<!-- End Auth Box Content -->
</div>
<div class="col">
<div class="h-100 position-relative card-side-img rounded-0 overflow-hidden">
<div class="p-4 card-img-overlay auth-overlay d-flex align-items-end justify-content-center">
</div>
</div>
</div>
</div>
</div>
<!-- Vendor js -->
<script src="assets/js/vendors.min.js"></script>
<!-- App js -->
<script src="assets/js/app.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,26 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
Route::get('/', function () {
return view('welcome');
});
Route::get('/login', [AuthController::class, 'showLogin'])->name('login');
Route::post('/login', [AuthController::class, 'login']);
Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth');
Route::middleware(['auth', 'auth.idle', 'auth.token'])->group(function () {
Route::get('/dashboard', function () {
return view('dashboard');
});
});
Route::middleware('guest')->group(function () {
Route::get('/login', [AuthController::class, 'showLogin'])->name('login');
Route::post('/login', [AuthController::class, 'login']);
});