279 lines
9.4 KiB
PHP
279 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Auth;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Mail\RegisterOtpMail;
|
|
use App\Models\LoginHistory;
|
|
use App\Models\User;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Illuminate\View\View;
|
|
use Throwable;
|
|
|
|
class AuthController extends Controller
|
|
{
|
|
private const PENDING_REGISTRATION_SESSION_KEY = 'auth.pending_registration';
|
|
|
|
public function showLogin(): View
|
|
{
|
|
return view('auth.login');
|
|
}
|
|
|
|
public function showRegister(): View
|
|
{
|
|
return view('auth.register');
|
|
}
|
|
|
|
public function showVerifyRegisterOtp(Request $request): View|RedirectResponse
|
|
{
|
|
$pendingRegistration = $this->pendingRegistrationData($request);
|
|
|
|
if ($pendingRegistration === null) {
|
|
return redirect()
|
|
->route('register')
|
|
->withErrors(['email' => 'Silakan isi formulir registrasi terlebih dahulu.']);
|
|
}
|
|
|
|
return view('auth.verify-register-otp', [
|
|
'pendingEmail' => $pendingRegistration['email'],
|
|
'expiresAt' => $pendingRegistration['otp_expires_at'],
|
|
]);
|
|
}
|
|
|
|
public function login(Request $request): RedirectResponse
|
|
{
|
|
$request->validate([
|
|
'login' => ['required', 'string'],
|
|
'password' => ['required', 'string'],
|
|
]);
|
|
|
|
$loginInput = $request->input('login');
|
|
$field = filter_var($loginInput, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
|
|
|
|
if (! Auth::attempt([$field => $loginInput, 'password' => $request->input('password')], $request->boolean('remember'))) {
|
|
throw ValidationException::withMessages([
|
|
'login' => 'Email/username atau password tidak valid.',
|
|
]);
|
|
}
|
|
|
|
$request->session()->regenerate();
|
|
|
|
/** @var User $user */
|
|
$user = $request->user();
|
|
|
|
$this->recordLoginHistory($request, $user, $loginInput);
|
|
|
|
return $this->redirectAfterLogin($request, $user)
|
|
->with('status', 'Login berhasil.');
|
|
}
|
|
|
|
public function register(Request $request): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => ['required', 'string', 'max:100'],
|
|
'username' => ['required', 'string', 'max:50', 'alpha_dash', 'unique:users,username'],
|
|
'email' => ['required', 'email', 'max:100', 'unique:users,email'],
|
|
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
|
]);
|
|
|
|
try {
|
|
$this->issueRegistrationOtp($request, [
|
|
'name' => $validated['name'],
|
|
'username' => $validated['username'],
|
|
'email' => $validated['email'],
|
|
'password' => Hash::make($validated['password']),
|
|
]);
|
|
} catch (Throwable) {
|
|
return back()
|
|
->withInput($request->except(['password', 'password_confirmation']))
|
|
->withErrors(['email' => 'Kode OTP gagal dikirim ke email. Pastikan konfigurasi Gmail SMTP sudah aktif lalu coba lagi.']);
|
|
}
|
|
|
|
return redirect()
|
|
->route('register.verify')
|
|
->with('status', 'Kode OTP sudah dikirim ke email Anda. Masukkan 6 digit OTP untuk menyelesaikan registrasi.');
|
|
}
|
|
|
|
public function verifyRegisterOtp(Request $request): RedirectResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'otp' => ['required', 'digits:6'],
|
|
]);
|
|
|
|
$pendingRegistration = $this->pendingRegistrationData($request);
|
|
|
|
if ($pendingRegistration === null) {
|
|
return redirect()
|
|
->route('register')
|
|
->withErrors(['email' => 'Sesi OTP registrasi sudah habis. Silakan daftar ulang.']);
|
|
}
|
|
|
|
if (now()->timestamp > $pendingRegistration['otp_expires_at']) {
|
|
return back()->withErrors([
|
|
'otp' => 'Kode OTP sudah kedaluwarsa. Silakan kirim ulang OTP.',
|
|
]);
|
|
}
|
|
|
|
if (! Hash::check($validated['otp'], $pendingRegistration['otp_hash'])) {
|
|
throw ValidationException::withMessages([
|
|
'otp' => 'Kode OTP tidak valid.',
|
|
]);
|
|
}
|
|
|
|
if (
|
|
User::query()->where('email', '=', $pendingRegistration['email'])->exists()
|
|
|| User::query()->where('username', '=', $pendingRegistration['username'])->exists()
|
|
) {
|
|
$this->clearPendingRegistration($request);
|
|
|
|
return redirect()
|
|
->route('register')
|
|
->withErrors(['email' => 'Email atau username sudah digunakan. Silakan registrasi ulang dengan data yang berbeda.']);
|
|
}
|
|
|
|
$user = User::create([
|
|
'name' => $pendingRegistration['name'],
|
|
'username' => $pendingRegistration['username'],
|
|
'email' => $pendingRegistration['email'],
|
|
'password' => $pendingRegistration['password'],
|
|
'role' => 'user',
|
|
'member_level' => 'tanpa_member',
|
|
'email_verified_at' => now(),
|
|
]);
|
|
|
|
$this->clearPendingRegistration($request);
|
|
|
|
Auth::login($user);
|
|
$request->session()->regenerate();
|
|
|
|
$this->recordLoginHistory($request, $user, $user->email);
|
|
|
|
return redirect()
|
|
->route('profile')
|
|
->with('status', 'Registrasi berhasil. Email sudah terverifikasi dan Anda sudah masuk ke akun.');
|
|
}
|
|
|
|
public function resendRegisterOtp(Request $request): RedirectResponse
|
|
{
|
|
$pendingRegistration = $this->pendingRegistrationData($request);
|
|
|
|
if ($pendingRegistration === null) {
|
|
return redirect()
|
|
->route('register')
|
|
->withErrors(['email' => 'Sesi OTP registrasi sudah habis. Silakan daftar ulang.']);
|
|
}
|
|
|
|
if (
|
|
isset($pendingRegistration['otp_sent_at'])
|
|
&& (now()->timestamp - (int) $pendingRegistration['otp_sent_at']) < 60
|
|
) {
|
|
return back()->withErrors([
|
|
'otp' => 'Tunggu sebentar sebelum meminta OTP baru.',
|
|
]);
|
|
}
|
|
|
|
try {
|
|
$this->issueRegistrationOtp($request, [
|
|
'name' => $pendingRegistration['name'],
|
|
'username' => $pendingRegistration['username'],
|
|
'email' => $pendingRegistration['email'],
|
|
'password' => $pendingRegistration['password'],
|
|
]);
|
|
} catch (Throwable) {
|
|
return back()->withErrors([
|
|
'otp' => 'OTP gagal dikirim ulang. Periksa konfigurasi email lalu coba lagi.',
|
|
]);
|
|
}
|
|
|
|
return back()->with('status', 'OTP baru sudah dikirim ke email Anda.');
|
|
}
|
|
|
|
public function logout(Request $request): RedirectResponse
|
|
{
|
|
Auth::logout();
|
|
|
|
$request->session()->invalidate();
|
|
$request->session()->regenerateToken();
|
|
|
|
return redirect()->route('landing')->with('status', 'Berhasil logout.');
|
|
}
|
|
|
|
private function redirectAfterLogin(Request $request, User $user): RedirectResponse
|
|
{
|
|
if ($user->isAdmin()) {
|
|
$request->session()->forget('url.intended');
|
|
|
|
return redirect()->route('dashboard');
|
|
}
|
|
|
|
$hasUnreadAdminNotification = $user->penyewaans()
|
|
->whereNotNull('admin_catatan')
|
|
->whereNull('admin_catatan_read_at')
|
|
->exists();
|
|
|
|
if ($hasUnreadAdminNotification) {
|
|
$request->session()->forget('url.intended');
|
|
|
|
return redirect()
|
|
->route('profile')
|
|
->with('status', 'Ada notifikasi baru dari admin pada pengajuan sewa Anda.');
|
|
}
|
|
|
|
$intendedUrl = $request->session()->pull('url.intended');
|
|
|
|
if ($intendedUrl) {
|
|
return redirect()->to($intendedUrl);
|
|
}
|
|
|
|
return redirect()->route('profile');
|
|
}
|
|
|
|
private function recordLoginHistory(Request $request, User $user, string $loginIdentifier): void
|
|
{
|
|
LoginHistory::create([
|
|
'user_id' => $user->id,
|
|
'login_identifier' => $loginIdentifier,
|
|
'ip_address' => $request->ip(),
|
|
'user_agent' => (string) $request->userAgent(),
|
|
'login_at' => now(),
|
|
]);
|
|
}
|
|
|
|
private function issueRegistrationOtp(Request $request, array $registrationData): void
|
|
{
|
|
$otp = (string) random_int(100000, 999999);
|
|
|
|
Mail::to($registrationData['email'])->send(new RegisterOtpMail(
|
|
otp: $otp,
|
|
recipientName: $registrationData['name']
|
|
));
|
|
|
|
$request->session()->put(self::PENDING_REGISTRATION_SESSION_KEY, [
|
|
'name' => $registrationData['name'],
|
|
'username' => $registrationData['username'],
|
|
'email' => $registrationData['email'],
|
|
'password' => $registrationData['password'],
|
|
'otp_hash' => Hash::make($otp),
|
|
'otp_expires_at' => now()->addMinutes(10)->timestamp,
|
|
'otp_sent_at' => now()->timestamp,
|
|
]);
|
|
}
|
|
|
|
private function pendingRegistrationData(Request $request): ?array
|
|
{
|
|
$pendingRegistration = $request->session()->get(self::PENDING_REGISTRATION_SESSION_KEY);
|
|
|
|
return is_array($pendingRegistration) ? $pendingRegistration : null;
|
|
}
|
|
|
|
private function clearPendingRegistration(Request $request): void
|
|
{
|
|
$request->session()->forget(self::PENDING_REGISTRATION_SESSION_KEY);
|
|
}
|
|
}
|