diff --git a/app/Http/Requests/Passport/AuthForget.php b/app/Http/Requests/Passport/AuthForget.php index 8106f28..6442cab 100644 --- a/app/Http/Requests/Passport/AuthForget.php +++ b/app/Http/Requests/Passport/AuthForget.php @@ -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') ]; } } diff --git a/app/Services/Auth/LoginService.php b/app/Services/Auth/LoginService.php index 363e82b..814fffb 100644 --- a/app/Services/Auth/LoginService.php +++ b/app/Services/Auth/LoginService.php @@ -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')]]; } diff --git a/tests/Unit/Services/Auth/LoginServiceTest.php b/tests/Unit/Services/Auth/LoginServiceTest.php new file mode 100644 index 0000000..28fc878 --- /dev/null +++ b/tests/Unit/Services/Auth/LoginServiceTest.php @@ -0,0 +1,83 @@ +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(), + ]); + } +} \ No newline at end of file