diff --git a/app/Http/Controllers/Admin/AuthController.php b/app/Http/Controllers/Admin/AuthController.php index 579ad0a..0430966 100755 --- a/app/Http/Controllers/Admin/AuthController.php +++ b/app/Http/Controllers/Admin/AuthController.php @@ -245,33 +245,51 @@ class AuthController extends Controller if ($validator->fails()) { return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } - $service = app(AdminSmsChallengeService::class); - $canSend = $service->canSend($all['mobile'], request()->ip()); - $admin = Admin::where('mobile', $all['mobile'])->first(); - $challengeId = null; + $stage = 'can_send'; + try { + $service = app(AdminSmsChallengeService::class); + $canSend = $service->canSend($all['mobile'], request()->ip()); + $stage = 'lookup_admin'; + $admin = Admin::where('mobile', $all['mobile'])->first(); + $challengeId = null; - if ($canSend && $admin) { - $smsSign = Config::getValueByKey('sms_sign'); - $challengeId = $service->issueChallenge($all['mobile'], request()->ip(), function ($code) use ($all, $smsSign) { - $content = "{$smsSign}验证码{$code},您正在登陆苏州科技商学院信息化系统,请在5分钟内完成验证。"; - try { - return (bool) ymSms($all['mobile'], $content); - } catch (\Throwable $e) { - // Do not expose provider/network errors or the mobile number to the client. - Log::error('admin_sms_provider_error', [ - 'mobile_hash' => hash('sha256', (string) $all['mobile']), - 'exception' => get_class($e), - ]); - return false; - } - }, (string) request()->userAgent()); - } + if ($canSend && $admin) { + $stage = 'issue_challenge'; + $smsSign = Config::getValueByKey('sms_sign'); + $challengeId = $service->issueChallenge($all['mobile'], request()->ip(), function ($code) use ($all, $smsSign) { + $content = "{$smsSign}验证码{$code},您正在登陆苏州科技商学院信息化系统,请在5分钟内完成验证。"; + try { + return (bool) ymSms($all['mobile'], $content); + } catch (\Throwable $e) { + // Do not expose provider/network errors or the mobile number to the client. + Log::error('admin_sms_provider_error', [ + 'mobile_hash' => hash('sha256', (string) $all['mobile']), + 'exception' => get_class($e), + ]); + return false; + } + }, (string) request()->userAgent()); + } - // Always return the same shape, including for unknown or throttled numbers. - return $this->success([ - 'message' => '如果手机号已登记,验证码将发送', - 'challenge_id' => $challengeId ?: $service->issueDecoyChallenge($all['mobile'], request()->ip(), (string) request()->userAgent()), - ]); + if (!$challengeId) { + $stage = 'issue_decoy'; + $challengeId = $service->issueDecoyChallenge($all['mobile'], request()->ip(), (string) request()->userAgent()); + } + + // Always return the same shape, including for unknown or throttled numbers. + return $this->success([ + 'message' => '如果手机号已登记,验证码将发送', + 'challenge_id' => $challengeId, + ]); + } catch (\Throwable $e) { + // A challenge cannot be issued if a dependency such as the cache is unavailable. + Log::error('admin_sms_send_error', [ + 'mobile_hash' => hash('sha256', (string) $all['mobile']), + 'exception' => get_class($e), + 'stage' => $stage, + ]); + return $this->fail([ResponseCode::ERROR_INSIDE, '验证码服务暂不可用,请稍后重试']); + } } diff --git a/tests/Feature/AdminSmsSendFailureTest.php b/tests/Feature/AdminSmsSendFailureTest.php new file mode 100644 index 0000000..a90da7d --- /dev/null +++ b/tests/Feature/AdminSmsSendFailureTest.php @@ -0,0 +1,71 @@ +set('database.default', 'sqlite'); + config()->set('database.connections.sqlite.database', ':memory:'); + DB::purge('sqlite'); + + Schema::create('admins', function (Blueprint $table) { + $table->increments('id'); + $table->string('mobile'); + $table->softDeletes(); + }); + Schema::create('configs', function (Blueprint $table) { + $table->increments('id'); + $table->string('key'); + $table->string('value'); + $table->softDeletes(); + }); + DB::table('admins')->insert(['mobile' => '13800138000']); + DB::table('configs')->insert(['key' => 'sms_sign', 'value' => '测试']); + } + + public function test_provider_failure_and_unknown_mobile_have_the_same_response(): void + { + $challengeId = '4d2fd7fc-2630-4432-8df5-34f3edc029d0'; + $service = Mockery::mock(AdminSmsChallengeService::class); + $service->shouldReceive('canSend')->twice()->andReturn(true); + $service->shouldReceive('issueChallenge')->once()->andReturn(null); + $service->shouldReceive('issueDecoyChallenge')->twice()->andReturn($challengeId); + $this->app->instance(AdminSmsChallengeService::class, $service); + + $registered = $this->postJson('/api/admin/auth/send-sms', ['mobile' => '13800138000']); + $unknown = $this->postJson('/api/admin/auth/send-sms', ['mobile' => '13900139000']); + + $registered->assertOk()->assertExactJson([ + 'message' => '如果手机号已登记,验证码将发送', + 'challenge_id' => $challengeId, + ]); + $unknown->assertOk()->assertExactJson($registered->json()); + } + + public function test_challenge_store_failure_returns_a_controlled_error(): void + { + $service = Mockery::mock(AdminSmsChallengeService::class); + $service->shouldReceive('canSend')->once()->andReturn(true); + $service->shouldReceive('issueChallenge')->once()->andReturn(null); + $service->shouldReceive('issueDecoyChallenge')->once()->andThrow(new \RuntimeException('cache unavailable')); + $this->app->instance(AdminSmsChallengeService::class, $service); + + $this->postJson('/api/admin/auth/send-sms', ['mobile' => '13800138000']) + ->assertOk() + ->assertExactJson([ + 'errcode' => 10003, + 'errmsg' => '验证码服务暂不可用,请稍后重试', + ]); + } +}