feat(core): harden sessions settlement and credit activity
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\SitePlatformRole;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Support\SitePlatformRole;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
@@ -92,6 +92,89 @@ test('admin login returns bearer token when captcha passes validation', function
|
||||
->assertJsonPath('data.scope', 'admin');
|
||||
});
|
||||
|
||||
test('later admin login replaces the previous browser session', function () {
|
||||
$admin = AdminUser::query()->create([
|
||||
'username' => 'single_session_admin',
|
||||
'name' => '单会话管理员',
|
||||
'email' => null,
|
||||
'password' => 'secret-strong',
|
||||
'status' => 0,
|
||||
]);
|
||||
grantSuperAdminRole($admin);
|
||||
|
||||
$login = function () {
|
||||
$captchaKey = (string) Str::uuid();
|
||||
Cache::put(
|
||||
'admin_captcha:'.$captchaKey,
|
||||
hash_hmac('sha256', 'xwz2', (string) config('app.key')),
|
||||
now()->addSeconds(120),
|
||||
);
|
||||
|
||||
return $this->postJson('/api/v1/admin/auth/login', [
|
||||
'account' => 'single_session_admin',
|
||||
'password' => 'secret-strong',
|
||||
'captcha_key' => $captchaKey,
|
||||
'captcha_code' => 'xwz2',
|
||||
])->assertOk();
|
||||
};
|
||||
|
||||
$firstToken = (string) $login()->json('data.token');
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$firstToken)
|
||||
->getJson('/api/v1/admin/ping')
|
||||
->assertOk();
|
||||
|
||||
$secondToken = (string) $login()->json('data.token');
|
||||
|
||||
app('auth')->forgetGuards();
|
||||
$this->withHeader('Authorization', 'Bearer '.$firstToken)
|
||||
->getJson('/api/v1/admin/ping')
|
||||
->assertUnauthorized()
|
||||
->assertJsonPath('code', ErrorCode::AdminSessionReplaced->value);
|
||||
|
||||
app('auth')->forgetGuards();
|
||||
$this->withHeader('Authorization', 'Bearer '.$secondToken)
|
||||
->getJson('/api/v1/admin/ping')
|
||||
->assertOk()
|
||||
->assertJsonPath('code', ErrorCode::Success->value);
|
||||
|
||||
expect($admin->fresh()->admin_session_version)->toBe(2)
|
||||
->and($admin->tokens()->where('name', 'admin-api')->count())->toBe(2);
|
||||
});
|
||||
|
||||
test('admin logout revokes browser sessions without deleting programmatic tokens', function () {
|
||||
$admin = AdminUser::query()->create([
|
||||
'username' => 'logout_admin',
|
||||
'name' => '退出管理员',
|
||||
'email' => null,
|
||||
'password' => 'secret-strong',
|
||||
'status' => 0,
|
||||
'admin_session_version' => 1,
|
||||
]);
|
||||
|
||||
$browserToken = $admin->createToken(
|
||||
'admin-api',
|
||||
['admin-session:1'],
|
||||
now()->addDay(),
|
||||
)->plainTextToken;
|
||||
$admin->createToken('test', ['*'], now()->addDay());
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$browserToken)
|
||||
->postJson('/api/v1/admin/auth/logout')
|
||||
->assertOk()
|
||||
->assertJsonPath('code', ErrorCode::Success->value)
|
||||
->assertJsonPath('data.logged_out', true);
|
||||
|
||||
expect($admin->tokens()->where('name', 'admin-api')->count())->toBe(0)
|
||||
->and($admin->tokens()->where('name', 'test')->count())->toBe(1);
|
||||
|
||||
app('auth')->forgetGuards();
|
||||
$this->withHeader('Authorization', 'Bearer '.$browserToken)
|
||||
->getJson('/api/v1/admin/ping')
|
||||
->assertUnauthorized()
|
||||
->assertJsonPath('code', ErrorCode::AdminUnauthenticated->value);
|
||||
});
|
||||
|
||||
test('agent operator auth me omits platform-only navigation', function (): void {
|
||||
$this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user