update
This commit is contained in:
@@ -4,7 +4,7 @@ namespace App\Services\Api\Client;
|
||||
|
||||
use App\Http\Services\Telegram;
|
||||
use App\Mail\Admin\TrialRegistrationRequest as TrialRegistrationRequestMail;
|
||||
use App\Mail\Customer\PasswordResetByAdmin;
|
||||
use App\Mail\Customer\PasswordResetLink;
|
||||
use App\Mail\Customer\VerifyAccount as VerifyAccountMail;
|
||||
use App\Mail\Customer\Welcome as WelcomeMail;
|
||||
use App\Models\Customer;
|
||||
@@ -12,10 +12,11 @@ use App\Models\RefreshToken;
|
||||
use App\Models\Setting;
|
||||
use App\Repositories\Api\Client\ClientRepositoryInterface;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tymon\JWTAuth\Facades\JWTAuth;
|
||||
@@ -25,6 +26,8 @@ class ClientAuthService
|
||||
// Refresh token lifetime in days
|
||||
const REFRESH_TOKEN_TTL_DAYS = 30;
|
||||
|
||||
const VERIFY_MAX_ATTEMPTS = 5;
|
||||
|
||||
protected $clientRepository;
|
||||
|
||||
public function __construct(ClientRepositoryInterface $clientRepository)
|
||||
@@ -143,12 +146,28 @@ class ClientAuthService
|
||||
{
|
||||
$client = \App\Models\Customer::whereRaw('LOWER(email) = ?', [strtolower(trim($email))])->first();
|
||||
|
||||
if (! $client || $client->email_verify_code !== $code) {
|
||||
// Chống brute-force mã 6 số: sai quá VERIFY_MAX_ATTEMPTS lần thì huỷ mã, buộc gửi lại.
|
||||
$attemptKey = 'verify-email:' . strtolower(trim($email));
|
||||
|
||||
if (! $client || ! $client->email_verify_code || ! hash_equals($client->email_verify_code, $code)) {
|
||||
if ($client?->email_verify_code && RateLimiter::hit($attemptKey, 600) >= self::VERIFY_MAX_ATTEMPTS) {
|
||||
RateLimiter::clear($attemptKey);
|
||||
$client->email_verify_code = null;
|
||||
$client->email_verify_code_expired_at = null;
|
||||
$client->save();
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'code' => ['Nhập sai quá nhiều lần, vui lòng yêu cầu gửi lại mã xác thực.'],
|
||||
]);
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'code' => ['Mã xác thực không chính xác.'],
|
||||
]);
|
||||
}
|
||||
|
||||
RateLimiter::clear($attemptKey);
|
||||
|
||||
if (! $client->email_verify_code_expired_at || now()->gt($client->email_verify_code_expired_at)) {
|
||||
throw ValidationException::withMessages([
|
||||
'code' => ['Mã xác thực đã hết hạn, vui lòng yêu cầu gửi lại.'],
|
||||
@@ -305,10 +324,11 @@ class ClientAuthService
|
||||
}
|
||||
|
||||
/**
|
||||
* Cấp mật khẩu mới ngẫu nhiên khi khách hàng quên mật khẩu. Không tiết lộ
|
||||
* email có tồn tại hay không — trả về null nếu không tìm thấy tài khoản.
|
||||
* Gửi link đặt lại mật khẩu (token một lần, hết hạn theo auth.passwords.users.expire).
|
||||
* Không đổi mật khẩu ngay — chỉ chủ hộp thư mới đổi được. Trả về null nếu không có
|
||||
* tài khoản; caller không được tiết lộ kết quả cho client.
|
||||
*/
|
||||
public function resetForgottenPassword(string $email): ?Customer
|
||||
public function sendPasswordResetLink(string $email): ?Customer
|
||||
{
|
||||
$customer = Customer::whereRaw('LOWER(email) = ?', [strtolower(trim($email))])->first();
|
||||
|
||||
@@ -316,22 +336,49 @@ class ClientAuthService
|
||||
return null;
|
||||
}
|
||||
|
||||
$newPassword = Str::password(16);
|
||||
// Broker tự throttle theo email (auth.passwords.users.throttle).
|
||||
Password::broker('users')->sendResetLink(
|
||||
['email' => $customer->email],
|
||||
function (Customer $customer, string $token) {
|
||||
$url = rtrim(config('app.frontend_url'), '/') . '/dat-lai-mat-khau?' . http_build_query([
|
||||
'token' => $token,
|
||||
'email' => $customer->email,
|
||||
]);
|
||||
|
||||
DB::transaction(function () use ($customer, $newPassword) {
|
||||
$customer->password = Hash::make($newPassword);
|
||||
$customer->save();
|
||||
|
||||
Mail::to($customer->email)->later(now()->addSeconds(5), new PasswordResetByAdmin([
|
||||
'fullname' => $customer->fullname,
|
||||
'email' => $customer->email,
|
||||
'password' => $newPassword,
|
||||
]));
|
||||
});
|
||||
Mail::to($customer->email)->queue(new PasswordResetLink([
|
||||
'fullname' => $customer->fullname,
|
||||
'reset_url' => $url,
|
||||
]));
|
||||
},
|
||||
);
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Đặt lại mật khẩu bằng token trong email. Thành công thì thu hồi toàn bộ refresh token
|
||||
* để các phiên cũ (có thể của kẻ gian) phải đăng nhập lại.
|
||||
*/
|
||||
public function resetPassword(array $data): ?Customer
|
||||
{
|
||||
$resetCustomer = null;
|
||||
|
||||
$status = Password::broker('users')->reset($data, function (Customer $customer, string $password) use (&$resetCustomer) {
|
||||
$customer->password = Hash::make($password);
|
||||
$customer->save();
|
||||
RefreshToken::where('customer_id', $customer->id)->delete();
|
||||
$resetCustomer = $customer;
|
||||
});
|
||||
|
||||
if ($status !== Password::PASSWORD_RESET) {
|
||||
throw ValidationException::withMessages([
|
||||
'token' => ['Link đặt lại mật khẩu không hợp lệ hoặc đã hết hạn.'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $resetCustomer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cập nhật thông tin cá nhân. Trả về old/dirty values để caller ghi activity log.
|
||||
*/
|
||||
@@ -375,5 +422,8 @@ class ClientAuthService
|
||||
|
||||
$client->password = Hash::make($newPassword);
|
||||
$client->save();
|
||||
|
||||
// Access token cũ tự vô hiệu nhờ claim `pv`; thu hồi luôn refresh token để mọi phiên phải đăng nhập lại.
|
||||
RefreshToken::where('customer_id', $client->id)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user