feat: Separate admin and user tables for independent authentication

- Create new 'admins' table with role column (admin/super_admin)
- Create Admin model for admin authentication
- Add AdminSeeder with test data (super_admin, admin)
- Update config/auth.php with dual guards (web for users, admin for admins)
- Update AdminAuthController to use Admin model and 'admin' guard
- Remove role column from users table (users have no role)
- Update register validation messages in Indonesian
- Now same email can be used by user and admin (separate tables)
This commit is contained in:
micko samawa 2026-05-18 20:50:22 +07:00
parent cf41a8b582
commit 585d36fe09
10 changed files with 239 additions and 25 deletions

View File

@ -4,7 +4,7 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Models\User; use App\Models\Admin;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class AdminAuthController extends Controller class AdminAuthController extends Controller
@ -23,16 +23,8 @@ public function login(Request $request)
'password' => 'required' 'password' => 'required'
]); ]);
// FIXED: Hapus guard('admin'), pakai default Auth // Gunakan guard 'admin' - ambil dari tabel admins
if (Auth::attempt($credentials)) { if (Auth::guard('admin')->attempt($credentials)) {
// Cek role - hanya admin dan super_admin yang boleh login
$user = Auth::user();
if (!in_array($user->role, ['admin', 'super_admin'])) {
Auth::logout();
$request->session()->invalidate();
return back()->with('error', 'Akses ditolak. Halaman ini khusus untuk pemilik bisnis.');
}
$request->session()->regenerate(); $request->session()->regenerate();
return redirect()->intended(route('dashboard')); return redirect()->intended(route('dashboard'));
} }
@ -51,16 +43,25 @@ public function register(Request $request)
{ {
$request->validate([ $request->validate([
'name' => 'required|string|max:50', 'name' => 'required|string|max:50',
'email' => 'required|email|unique:users,email', 'email' => 'required|email|unique:admins,email',
'password' => 'required|min:6|confirmed' 'password' => 'required|min:6|confirmed'
], [
'name.required' => 'Nama wajib diisi',
'name.max' => 'Nama maksimal 50 karakter',
'email.required' => 'Email wajib diisi',
'email.email' => 'Format email tidak valid',
'email.unique' => 'Email sudah terdaftar',
'password.required' => 'Password wajib diisi',
'password.min' => 'Password minimal 6 karakter',
'password.confirmed' => 'Konfirmasi password tidak cocok'
]); ]);
// Simpan ke tabel users (ROLE SELALU admin - tidak bisa diubah user) // Simpan ke tabel admins (ROLE SELALU admin)
User::create([ Admin::create([
'name' => $request->name, 'name' => $request->name,
'email' => $request->email, 'email' => $request->email,
'password' => Hash::make($request->password), 'password' => Hash::make($request->password),
'role' => 'admin' // PAKSA SELALU ADMIN - tidak bisa jadi super_admin lewat register 'role' => 'admin' // Hanya admin, tidak bisa super_admin
]); ]);
return redirect()->route('admin.login')->with('success', 'Akun admin berhasil dibuat!'); return redirect()->route('admin.login')->with('success', 'Akun admin berhasil dibuat!');
@ -69,8 +70,7 @@ public function register(Request $request)
// Logout Admin // Logout Admin
public function logout(Request $request) public function logout(Request $request)
{ {
// FIXED: Hapus guard('admin'), pakai default Auth Auth::guard('admin')->logout();
Auth::logout();
$request->session()->invalidate(); $request->session()->invalidate();
$request->session()->regenerateToken(); $request->session()->regenerateToken();

View File

@ -19,6 +19,8 @@ class AuthController extends Controller
public function register(RegisterRequest $request) public function register(RegisterRequest $request)
{ {
try { try {
Log::info('Register attempt', ['email' => $request->email, 'name' => $request->name]);
$user = User::create([ $user = User::create([
'name' => $request->name, 'name' => $request->name,
'email' => $request->email, 'email' => $request->email,
@ -29,6 +31,8 @@ public function register(RegisterRequest $request)
$token = $user->createToken('mobile-app-token')->plainTextToken; $token = $user->createToken('mobile-app-token')->plainTextToken;
Log::info('Registration successful', ['user_id' => $user->id]);
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'message' => 'Registrasi berhasil', 'message' => 'Registrasi berhasil',
@ -37,11 +41,28 @@ public function register(RegisterRequest $request)
'token' => $token 'token' => $token
] ]
], 201); ], 201);
} catch (\Exception $e) { } catch (\Illuminate\Database\QueryException $e) {
Log::error('Registration error: ' . $e->getMessage()); Log::error('Database error during registration', [
'email' => $request->email,
'message' => $e->getMessage(),
'sql' => $e->getSql() ?? 'N/A',
'bindings' => $e->getBindings() ?? []
]);
return response()->json([ return response()->json([
'success' => false, 'success' => false,
'message' => 'Terjadi kesalahan saat registrasi', 'message' => 'Database error: ' . $e->getMessage(),
'error_code' => 'DB_ERROR',
], 500);
} catch (\Exception $e) {
Log::error('Registration error', [
'email' => $request->email,
'exception' => get_class($e),
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan saat registrasi: ' . $e->getMessage(),
'error_code' => 'REGISTRATION_FAILED', 'error_code' => 'REGISTRATION_FAILED',
], 500); ], 500);
} }

View File

@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class Admin extends Authenticatable
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
'role',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}

View File

@ -4,7 +4,6 @@
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
@ -12,7 +11,7 @@
class User extends Authenticatable class User extends Authenticatable
{ {
/** @use HasFactory<\Database\Factories\UserFactory> */ /** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable, SoftDeletes, HasApiTokens; use HasFactory, Notifiable, HasApiTokens;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -23,7 +22,6 @@ class User extends Authenticatable
'name', 'name',
'email', 'email',
'password', 'password',
'role',
'phone', 'phone',
]; ];

View File

@ -3,7 +3,7 @@
return [ return [
'defaults' => [ 'defaults' => [
'guard' => 'admin', 'guard' => 'web',
'passwords' => 'users', 'passwords' => 'users',
], ],
@ -15,7 +15,7 @@
'admin' => [ 'admin' => [
'driver' => 'session', 'driver' => 'session',
'provider' => 'users', // ← PENTING!! 'provider' => 'admins', // ← Guard terpisah untuk admin
], ],
], ],
@ -24,6 +24,11 @@
'driver' => 'eloquent', 'driver' => 'eloquent',
'model' => App\Models\User::class, 'model' => App\Models\User::class,
], ],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
], ],
'passwords' => [ 'passwords' => [

View File

@ -0,0 +1,40 @@
<?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::create('admins', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->enum('role', ['admin', 'super_admin'])->default('admin'); // Pembeda admin vs super_admin
$table->rememberToken();
$table->timestamps();
});
Schema::create('admin_password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('admins');
Schema::dropIfExists('admin_password_reset_tokens');
}
};

View File

@ -0,0 +1,18 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
// Force delete soft-deleted users to clean up
DB::table('users')->whereNotNull('deleted_at')->delete();
}
public function down(): void
{
// Cannot restore deleted records
}
};

View File

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Remove soft deletes from users table
Schema::table('users', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->softDeletes();
});
}
};

View File

@ -0,0 +1,29 @@
<?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) {
// Hapus kolom role karena sekarang sudah ada di tabel admins
$table->dropColumn('role');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->enum('role', ['super_admin', 'admin', 'user'])->default('user')->nullable();
});
}
};

View File

@ -0,0 +1,33 @@
<?php
namespace Database\Seeders;
use App\Models\Admin;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class AdminSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Buat super admin
Admin::create([
'name' => 'Super Admin',
'email' => 'super@admin.com',
'password' => Hash::make('password123'),
'role' => 'super_admin'
]);
// Buat admin biasa
Admin::create([
'name' => 'Admin',
'email' => 'admin@admin.com',
'password' => Hash::make('password123'),
'role' => 'admin'
]);
}
}