create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 1, ]); $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'admin123', ])->assertOk() ->assertJson([ 'code' => 0, 'msg' => '登录成功', 'data' => route('admin.home'), ]); $this->assertAuthenticatedAs($admin, 'admin'); $this->assertNotNull($admin->fresh()->last_ip); } #[Test] public function ajax_login_rejects_bad_password(): void { Admin::query()->create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 1, ]); $response = $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'wrongpass', ])->assertOk() ->assertJsonPath('code', 1); $this->assertStringContainsString('用户名或密码错误', (string) $response->json('msg')); $this->assertGuest('admin'); } #[Test] public function disabled_admin_cannot_login(): void { Admin::query()->create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 0, ]); $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'admin123', ])->assertOk() ->assertJson(['code' => 1, 'msg' => '用户已被禁用']); $this->assertGuest('admin'); } #[Test] public function google2fa_required_when_enabled(): void { $google2fa = app(AdminGoogle2fa::class); $secret = $google2fa->generateSecret(); Admin::query()->create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 1, 'google_auth_open' => 1, 'google_secret' => $secret, ]); $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'admin123', ])->assertOk() ->assertJson(['code' => 1, 'msg' => '请输入谷歌验证码!']); $this->assertGuest('admin'); $code = (new \PragmaRX\Google2FA\Google2FA)->getCurrentOtp($secret); $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'admin123', 'GACode' => $code, ])->assertOk() ->assertJson(['code' => 0]); $this->assertAuthenticated('admin'); } #[Test] public function google2fa_bound_without_login_verify_skips_code(): void { $google2fa = app(AdminGoogle2fa::class); $secret = $google2fa->generateSecret(); Admin::query()->create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 1, 'google_auth_open' => 0, 'google_secret' => $secret, ]); $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'admin123', ])->assertOk() ->assertJson(['code' => 0]); $this->assertAuthenticated('admin'); } #[Test] public function login_is_rate_limited_after_failures(): void { Admin::query()->create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 1, ]); for ($i = 0; $i < 5; $i++) { $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'bad-password', ])->assertOk()->assertJsonPath('code', 1); } $response = $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'admin123', ])->assertOk() ->assertJsonPath('code', 1); $this->assertStringContainsString('登陆失败次数过多', (string) $response->json('msg')); } #[Test] public function account_is_locked_after_five_failed_attempts(): void { $admin = Admin::query()->create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 1, ]); // Four attempts: account not yet locked, shows remaining attempts. for ($i = 4; $i >= 1; $i--) { $response = $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'bad-password', ])->assertOk()->assertJsonPath('code', 1); $this->assertStringContainsString('剩余 '.$i.' 次', (string) $response->json('msg')); } // Fifth attempt locks the account. $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'bad-password', ])->assertOk() ->assertJsonPath('code', 1) ->assertJsonPath('msg', '密码连续输错 5 次,账号已被封锁,请联系超级管理员解除'); $this->assertNotNull($admin->fresh()->locked_at); $this->assertTrue($admin->fresh()->isLocked()); $this->assertSame(5, (int) $admin->fresh()->login_attempts); } #[Test] public function locked_account_cannot_login_with_correct_password(): void { $admin = Admin::query()->create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 1, 'login_attempts' => 5, 'locked_at' => now(), ]); $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'admin123', ])->assertOk() ->assertJsonPath('code', 1) ->assertJsonPath('msg', '账号已被封锁(连续输错密码 5 次),请联系超级管理员解除'); $this->assertGuest('admin'); } #[Test] public function successful_login_clears_failed_attempts(): void { $admin = Admin::query()->create([ 'username' => 'admin', 'password' => 'admin123', 'status' => 1, 'login_attempts' => 3, ]); $this->post('/admin/login', [ 'username' => 'admin', 'password' => 'admin123', ])->assertOk()->assertJsonPath('code', 0); $this->assertAuthenticatedAs($admin, 'admin'); $this->assertSame(0, (int) $admin->fresh()->login_attempts); $this->assertNull($admin->fresh()->locked_at); } #[Test] public function super_admin_can_unlock_account(): void { $super = Admin::query()->create([ 'username' => 'super', 'password' => 'super123', 'status' => 1, 'is_super' => 1, ]); $locked = Admin::query()->create([ 'username' => 'locked', 'password' => 'locked123', 'status' => 1, 'is_super' => 0, 'login_attempts' => 5, 'locked_at' => now(), ]); $this->actingAs($super, 'admin') ->postJson('/admin/system/admins/'.$locked->id.'/unlock') ->assertOk() ->assertJsonPath('code', 0) ->assertJsonPath('msg', '已解除封禁'); $this->assertNull($locked->fresh()->locked_at); $this->assertSame(0, (int) $locked->fresh()->login_attempts); $this->assertFalse($locked->fresh()->isLocked()); } #[Test] public function non_super_admin_cannot_unlock_account(): void { $regular = Admin::query()->create([ 'username' => 'regular', 'password' => 'regular123', 'status' => 1, 'is_super' => 0, ]); $locked = Admin::query()->create([ 'username' => 'locked', 'password' => 'locked123', 'status' => 1, 'is_super' => 0, 'login_attempts' => 5, 'locked_at' => now(), ]); $this->actingAs($regular, 'admin') ->postJson('/admin/system/admins/'.$locked->id.'/unlock') ->assertStatus(403) ->assertJsonPath('code', 1) ->assertJsonPath('msg', '需要超级管理员权限'); $this->assertTrue($locked->fresh()->isLocked()); } #[Test] public function unlock_returns_error_for_unlocked_account(): void { $super = Admin::query()->create([ 'username' => 'super', 'password' => 'super123', 'status' => 1, 'is_super' => 1, ]); $normal = Admin::query()->create([ 'username' => 'normal', 'password' => 'normal123', 'status' => 1, 'is_super' => 0, ]); $this->actingAs($super, 'admin') ->postJson('/admin/system/admins/'.$normal->id.'/unlock') ->assertOk() ->assertJsonPath('code', 1) ->assertJsonPath('msg', '该账号未被封禁'); } }