feat: enhance agent management and validation logic
- Updated AGENTS.md to clarify agent account restrictions and permissions. - Implemented checks in AgentNodeAdminUserStoreController and AgentNodeRoleStoreController to restrict admin user and role creation to the agent's own node. - Enhanced validation in AdminPlayerStoreController and AdminPlayerUpdateController to enforce credit limit and rebate rate rules based on player funding mode. - Refactored various request classes to utilize shared admin account field rules for consistency. - Improved error handling in services related to credit allocation and rebate limits to ensure proper validation and messaging.
This commit is contained in:
@@ -51,6 +51,18 @@ final class AgentNodeAdminUserStoreController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
// Agent accounts can only create admin users on their own node, not descendants
|
||||
$primaryNode = AdminAgentScope::primaryAgentNode($admin);
|
||||
if ($primaryNode !== null && (int) $primaryNode->id !== (int) $agent_node->id) {
|
||||
return ApiMessage::errorResponse(
|
||||
$request,
|
||||
'admin.agent_user_manage_denied',
|
||||
ErrorCode::AdminForbidden->value,
|
||||
null,
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
$user = $service->createUnderAgent($agent_node, $request->validated());
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
|
||||
@@ -51,6 +51,7 @@ final class AgentNodeProfileController extends Controller
|
||||
: null;
|
||||
|
||||
$payload = $request->validated();
|
||||
|
||||
if ($parent !== null) {
|
||||
$service->assertChildCapabilityGrantsWithinParent($parent, $payload, $admin);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,18 @@ final class AgentNodeRoleStoreController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
// Agent accounts can only create roles on their own node, not descendants
|
||||
$primaryNode = AdminAgentScope::primaryAgentNode($admin);
|
||||
if ($primaryNode !== null && (int) $primaryNode->id !== (int) $agent_node->id) {
|
||||
return ApiMessage::errorResponse(
|
||||
$request,
|
||||
'admin.agent_role_manage_denied',
|
||||
ErrorCode::AdminForbidden->value,
|
||||
null,
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
$role = $service->createForAgent($admin, $agent_node, $request->validated());
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
|
||||
@@ -29,11 +29,11 @@ final class AdminCreditLedgerIndexController extends Controller
|
||||
abort_if($admin === null, 401);
|
||||
|
||||
$adminSiteId = (int) $request->query('admin_site_id', 0);
|
||||
abort_if($adminSiteId <= 0, 422, 'admin_site_id required');
|
||||
abort_if($adminSiteId <= 0, 422, 'admin.admin_site_id_required');
|
||||
abort_if(! AdminAgentSettlementScope::siteAccessible($admin, $adminSiteId), 403);
|
||||
|
||||
$siteCode = (string) DB::table('admin_sites')->where('id', $adminSiteId)->value('code');
|
||||
abort_if($siteCode === '', 422, 'admin_site not found');
|
||||
abort_if($siteCode === '', 422, 'admin.admin_site_not_found');
|
||||
|
||||
$periodId = (int) $request->query('settlement_period_id', 0);
|
||||
if ($periodId > 0) {
|
||||
|
||||
@@ -33,7 +33,7 @@ final class AgentSettlementReportShowController extends Controller
|
||||
abort_unless(in_array($type, self::TYPES, true), 404);
|
||||
|
||||
if ($type === 'platform_pnl' && AdminAgentScope::primaryAgentNode($admin) !== null) {
|
||||
abort(403, 'agent_cannot_view_platform_pnl');
|
||||
abort(403, 'admin.agent_cannot_view_platform_pnl');
|
||||
}
|
||||
|
||||
$periodId = (int) $request->query('settlement_period_id', 0);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Player;
|
||||
use App\Models\Player;
|
||||
use App\Models\AdminSite;
|
||||
use App\Models\AgentNode;
|
||||
use App\Models\AdminUser;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\ApiMessage;
|
||||
use App\Support\ApiResponse;
|
||||
@@ -92,7 +93,7 @@ final class AdminPlayerStoreController extends Controller
|
||||
|
||||
$agentNodeId = $admin->isSuperAdmin()
|
||||
? $this->resolveAgentNodeIdForSuperAdmin($request->validated('agent_node_id'), $siteCode)
|
||||
: $admin->primaryAgentNodeId();
|
||||
: $this->resolveAgentNodeIdForNonSuperAdmin($admin, $request->validated('agent_node_id'), $siteCode);
|
||||
|
||||
if ($agentNodeId === null) {
|
||||
return ApiMessage::errorResponse(
|
||||
@@ -104,17 +105,10 @@ final class AdminPlayerStoreController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
if (! $admin->isSuperAdmin()) {
|
||||
$agent = AdminAgentScope::primaryAgentNode($admin);
|
||||
if ($agent === null || (int) $agentNodeId !== (int) $agent->id) {
|
||||
return ApiMessage::errorResponse($request, 'admin.player_create_agent_forbidden', ErrorCode::AdminForbidden->value, null, 403);
|
||||
}
|
||||
}
|
||||
|
||||
$agent = AgentNode::query()->findOrFail($agentNodeId);
|
||||
$rebateRate = 0.0;
|
||||
$extraRebateRate = 0.0;
|
||||
if ($request->has('rebate_rate')) {
|
||||
if ($request->has('rebate_rate') || $request->has('extra_rebate_rate')) {
|
||||
$rebateRate = (float) $request->input('rebate_rate', 0) / 100;
|
||||
$extraRebateRate = (float) $request->input('extra_rebate_rate', 0) / 100;
|
||||
$rebateLimitValidator->assertPlayerRebateWithinAgent(
|
||||
@@ -124,6 +118,12 @@ final class AdminPlayerStoreController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
if (! $isNative && ($request->has('credit_limit') || $request->has('rebate_rate') || $request->has('extra_rebate_rate'))) {
|
||||
throw ValidationException::withMessages([
|
||||
'credit_limit' => ['wallet_player_prohibited'],
|
||||
]);
|
||||
}
|
||||
|
||||
$creditLimit = $request->has('credit_limit')
|
||||
? (int) $request->input('credit_limit', 0)
|
||||
: ($isNative ? 0 : 0);
|
||||
@@ -201,6 +201,43 @@ final class AdminPlayerStoreController extends Controller
|
||||
return $rootId !== null ? (int) $rootId : null;
|
||||
}
|
||||
|
||||
private function resolveAgentNodeIdForNonSuperAdmin(AdminUser $admin, mixed $requested, string $siteCode): ?int
|
||||
{
|
||||
// Check if admin is a platform account (bound via admin_user_site_roles)
|
||||
$accessibleSiteIds = $admin->accessibleAdminSiteIds();
|
||||
if ($accessibleSiteIds !== null) {
|
||||
// Platform account (site admin) can specify agent_node_id
|
||||
if ($requested !== null && (int) $requested > 0) {
|
||||
$agent = AgentNode::query()->find((int) $requested);
|
||||
if ($agent !== null && in_array((int) $agent->admin_site_id, $accessibleSiteIds, true)) {
|
||||
return (int) $requested;
|
||||
}
|
||||
}
|
||||
// Default to root node of the site
|
||||
$siteId = AdminSite::query()->where('code', $siteCode)->value('id');
|
||||
if ($siteId !== null && in_array((int) $siteId, $accessibleSiteIds, true)) {
|
||||
$rootId = AgentNode::query()
|
||||
->where('admin_site_id', (int) $siteId)
|
||||
->where('depth', 0)
|
||||
->value('id');
|
||||
return $rootId !== null ? (int) $rootId : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Agent account (bound via agent node) - can only create under own node
|
||||
$agent = AdminAgentScope::primaryAgentNode($admin);
|
||||
if ($agent === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($requested !== null && (int) $requested > 0 && (int) $requested !== (int) $agent->id) {
|
||||
return null; // Agent account cannot create under other nodes
|
||||
}
|
||||
|
||||
return (int) $agent->id;
|
||||
}
|
||||
|
||||
private function generateNativeSitePlayerId(string $siteCode): string
|
||||
{
|
||||
$prefix = strtoupper(substr(preg_replace('/[^A-Za-z]/', '', $siteCode) ?: 'LP', 0, 2));
|
||||
|
||||
@@ -7,12 +7,14 @@ use App\Models\AgentNode;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Support\AdminSiteScope;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Support\PlayerFundingMode;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Support\PlayerApiPresenter;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Agent\AgentProfileService;
|
||||
use App\Services\Agent\RebateLimitValidator;
|
||||
use App\Services\Player\PlayerCreditService;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\Services\Player\PlayerRebateProfileService;
|
||||
use App\Http\Requests\Admin\AdminPlayerUpdateRequest;
|
||||
|
||||
@@ -44,9 +46,18 @@ final class AdminPlayerUpdateController extends Controller
|
||||
? AgentNode::query()->find((int) $player->agent_node_id)
|
||||
: null;
|
||||
|
||||
if (
|
||||
($request->has('credit_limit') || $request->has('rebate_rate') || $request->has('extra_rebate_rate'))
|
||||
&& ! PlayerFundingMode::usesCredit($player)
|
||||
) {
|
||||
throw ValidationException::withMessages([
|
||||
'credit_limit' => ['wallet_player_prohibited'],
|
||||
]);
|
||||
}
|
||||
|
||||
$rebateRate = 0.0;
|
||||
$extraRebateRate = 0.0;
|
||||
if ($agent !== null && $request->has('rebate_rate')) {
|
||||
if ($agent !== null && ($request->has('rebate_rate') || $request->has('extra_rebate_rate'))) {
|
||||
$rebateRate = (float) $request->input('rebate_rate', 0) / 100;
|
||||
$extraRebateRate = (float) $request->input('extra_rebate_rate', 0) / 100;
|
||||
$rebateLimitValidator->assertPlayerRebateWithinAgent(
|
||||
@@ -67,7 +78,7 @@ final class AdminPlayerUpdateController extends Controller
|
||||
unset($data['credit_limit']);
|
||||
}
|
||||
|
||||
if ($request->has('rebate_rate')) {
|
||||
if ($request->has('rebate_rate') || $request->has('extra_rebate_rate')) {
|
||||
DB::table('player_rebate_profiles')->updateOrInsert(
|
||||
['player_id' => $player->id, 'game_type' => '*'],
|
||||
[
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Player\PlayerAuthLoginRequest;
|
||||
use App\Services\Player\PlayerNativeAuthService;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Support\LotteryMessage;
|
||||
use App\Exceptions\PlayerAuthenticationException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -22,7 +23,7 @@ final class PlayerAuthLoginController extends Controller
|
||||
);
|
||||
} catch (PlayerAuthenticationException $e) {
|
||||
return ApiResponse::error(
|
||||
$e->getMessage(),
|
||||
LotteryMessage::sso($request, $e->lotteryCode),
|
||||
$e->lotteryCode,
|
||||
null,
|
||||
$e->httpStatus,
|
||||
|
||||
Reference in New Issue
Block a user