fix(security): harden password reset email code validation
This commit is contained in:
parent
f2ac5db4bd
commit
1eb085d4c0
@ -16,7 +16,7 @@ class AuthForget extends FormRequest
|
||||
return [
|
||||
'email' => 'required|email:strict',
|
||||
'password' => 'required|min:8',
|
||||
'email_code' => 'required'
|
||||
'email_code' => 'required|digits:6'
|
||||
];
|
||||
}
|
||||
|
||||
@ -27,7 +27,8 @@ class AuthForget extends FormRequest
|
||||
'email.email' => __('Email format is incorrect'),
|
||||
'password.required' => __('Password can not be empty'),
|
||||
'password.min' => __('Password must be greater than 8 digits'),
|
||||
'email_code.required' => __('Email verification code cannot be empty')
|
||||
'email_code.required' => __('Email verification code cannot be empty'),
|
||||
'email_code.digits' => __('Incorrect email verification code')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +93,8 @@ class LoginService
|
||||
}
|
||||
|
||||
// 验证邮箱验证码
|
||||
if ((string) Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $email)) !== (string) $emailCode) {
|
||||
$cachedEmailCode = Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $email));
|
||||
if ($cachedEmailCode === null || !hash_equals((string) $cachedEmailCode, $emailCode)) {
|
||||
Cache::put($forgetRequestLimitKey, $forgetRequestLimit ? $forgetRequestLimit + 1 : 1, 300);
|
||||
return [false, [400, __('Incorrect email verification code')]];
|
||||
}
|
||||
|
||||
83
tests/Unit/Services/Auth/LoginServiceTest.php
Normal file
83
tests/Unit/Services/Auth/LoginServiceTest.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\LoginService;
|
||||
use App\Utils\CacheKey;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LoginServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private LoginService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Cache::flush();
|
||||
$this->service = app(LoginService::class);
|
||||
}
|
||||
|
||||
public function test_reset_password_rejects_missing_cached_email_code(): void
|
||||
{
|
||||
$user = $this->createUser('victim@example.com', 'old-password');
|
||||
|
||||
[$success, $result] = $this->service->resetPassword($user->email, '', 'new-password');
|
||||
|
||||
$this->assertFalse($success);
|
||||
$this->assertSame(400, $result[0]);
|
||||
|
||||
$user->refresh();
|
||||
$this->assertTrue(password_verify('old-password', $user->password));
|
||||
}
|
||||
|
||||
public function test_reset_password_accepts_matching_cached_email_code(): void
|
||||
{
|
||||
$user = $this->createUser('user@example.com', 'old-password');
|
||||
Cache::put(CacheKey::get('EMAIL_VERIFY_CODE', $user->email), 123456, 300);
|
||||
|
||||
[$success, $result] = $this->service->resetPassword($user->email, '123456', 'new-password');
|
||||
|
||||
$this->assertTrue($success);
|
||||
$this->assertTrue($result);
|
||||
|
||||
$user->refresh();
|
||||
$this->assertTrue(password_verify('new-password', $user->password));
|
||||
$this->assertNull(Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $user->email)));
|
||||
}
|
||||
|
||||
public function test_forget_password_validation_rejects_boolean_email_code(): void
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'email' => 'victim@example.com',
|
||||
'password' => 'new-password',
|
||||
'email_code' => false,
|
||||
], [
|
||||
'email' => 'required|email:strict',
|
||||
'password' => 'required|min:8',
|
||||
'email_code' => 'required|digits:6',
|
||||
]);
|
||||
|
||||
$this->assertTrue($validator->fails());
|
||||
$this->assertArrayHasKey('email_code', $validator->errors()->toArray());
|
||||
}
|
||||
|
||||
private function createUser(string $email, string $password): User
|
||||
{
|
||||
return User::query()->create([
|
||||
'email' => $email,
|
||||
'password' => password_hash($password, PASSWORD_DEFAULT),
|
||||
'uuid' => Helper::guid(true),
|
||||
'token' => Helper::guid(true),
|
||||
'created_at' => time(),
|
||||
'updated_at' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user