feat: enhance player authentication and agent management features
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
- Updated AGENTS.md to clarify player interface bindings and agent account restrictions. - Improved PlayerAuthLoginController to include captcha verification for player login. - Enhanced AdminPlayerIndexController with permission checks for admin users. - Refactored AdminPlayerStoreController to enforce agent node restrictions for non-super admins. - Introduced new error codes for player authentication failures and updated related services. - Enhanced validation rules for agent profiles to include settlement cycle options. - Improved AdminCaptchaService to support separate scopes for admin and player captcha handling. - Updated various services to ensure proper credit management and settlement processes.
This commit is contained in:
@@ -40,18 +40,7 @@ final class AgentNodeRoleStoreController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
if (! AdminAgentScope::nodeManageableBy($admin, $agent_node)) {
|
||||
return AdminAgentNodeAccess::denyUnlessCanManageParent($admin, $agent_node)
|
||||
?? ApiMessage::errorResponse(
|
||||
$request,
|
||||
'admin.agent_role_manage_denied',
|
||||
ErrorCode::AdminForbidden->value,
|
||||
null,
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
// Agent accounts can only create roles on their own node, not descendants
|
||||
// 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(
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Http\Controllers\Api\V1\Admin\Player;
|
||||
|
||||
use App\Models\Player;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\ApiMessage;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@@ -19,6 +21,20 @@ final class AdminPlayerIndexController extends Controller
|
||||
$admin = $request->lotteryAdmin();
|
||||
abort_if($admin === null, 401);
|
||||
|
||||
if (
|
||||
! $admin->isSuperAdmin()
|
||||
&& ! $admin->hasPermissionCode('service.players.view')
|
||||
&& ! $admin->hasPermissionCode('service.players.manage')
|
||||
) {
|
||||
return ApiMessage::errorResponse(
|
||||
$request,
|
||||
'admin.permission_denied',
|
||||
ErrorCode::AdminForbidden->value,
|
||||
null,
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
$p = AdminApiList::readPaging($request);
|
||||
$keyword = trim((string) $request->query('keyword', ''));
|
||||
$status = $request->query('status');
|
||||
|
||||
@@ -203,39 +203,30 @@ final class AdminPlayerStoreController extends Controller
|
||||
|
||||
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) {
|
||||
if ($agent !== null) {
|
||||
if ($requested !== null && (int) $requested > 0 && (int) $requested !== (int) $agent->id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $agent->id;
|
||||
}
|
||||
|
||||
if (! AdminAgentScope::isSiteOnlyOperator($admin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($requested !== null && (int) $requested > 0 && (int) $requested !== (int) $agent->id) {
|
||||
return null; // Agent account cannot create under other nodes
|
||||
if ($requested === null || (int) $requested <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $agent->id;
|
||||
$accessibleSiteIds = $admin->accessibleAdminSiteIds() ?? [];
|
||||
$node = AgentNode::query()->find((int) $requested);
|
||||
if ($node === null || ! in_array((int) $node->admin_site_id, $accessibleSiteIds, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $requested;
|
||||
}
|
||||
|
||||
private function generateNativeSitePlayerId(string $siteCode): string
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Player;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\AdminCaptchaService;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/** GET /api/v1/player/auth/captcha — 玩家账号密码登录图形验证码 */
|
||||
final class PlayerAuthCaptchaController extends Controller
|
||||
{
|
||||
public function __invoke(AdminCaptchaService $captcha): JsonResponse
|
||||
{
|
||||
$payload = $captcha->create(AdminCaptchaService::SCOPE_PLAYER);
|
||||
|
||||
return ApiResponse::success([
|
||||
'captcha_key' => $payload['captcha_key'],
|
||||
'image_base64' => $payload['image_base64'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,35 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Player;
|
||||
|
||||
use App\Exceptions\PlayerAuthenticationException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Player\PlayerAuthLoginRequest;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Services\AdminCaptchaService;
|
||||
use App\Services\Player\PlayerNativeAuthService;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Support\LotteryMessage;
|
||||
use App\Exceptions\PlayerAuthenticationException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/** POST /api/v1/player/auth/login — 代理线下玩家账号密码登录 */
|
||||
final class PlayerAuthLoginController extends Controller
|
||||
{
|
||||
public function __invoke(PlayerAuthLoginRequest $request, PlayerNativeAuthService $auth): JsonResponse
|
||||
{
|
||||
public function __invoke(
|
||||
PlayerAuthLoginRequest $request,
|
||||
PlayerNativeAuthService $auth,
|
||||
AdminCaptchaService $captcha,
|
||||
): JsonResponse {
|
||||
$data = $request->validated();
|
||||
|
||||
if (! $captcha->verify($data['captcha_key'], $data['captcha_code'], AdminCaptchaService::SCOPE_PLAYER)) {
|
||||
return ApiResponse::error(
|
||||
LotteryMessage::sso($request, ErrorCode::PlayerCaptchaInvalid->value),
|
||||
ErrorCode::PlayerCaptchaInvalid->value,
|
||||
null,
|
||||
422,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $auth->login(
|
||||
(string) $request->validated('site_code', ''),
|
||||
|
||||
@@ -16,6 +16,7 @@ trait AgentProfileFieldRules
|
||||
'can_grant_extra_rebate' => ['sometimes', 'boolean'],
|
||||
'can_create_child_agent' => ['sometimes', 'boolean'],
|
||||
'can_create_player' => ['sometimes', 'boolean'],
|
||||
'settlement_cycle' => ['sometimes', 'nullable', 'string', 'in:weekly,monthly'],
|
||||
'risk_tags' => ['sometimes', 'array'],
|
||||
'risk_tags.*' => ['string', 'max:64'],
|
||||
];
|
||||
@@ -23,6 +24,13 @@ trait AgentProfileFieldRules
|
||||
|
||||
protected function prepareAgentProfileFieldsForValidation(): void
|
||||
{
|
||||
// 预处理字段(如需要)
|
||||
if (! $this->has('settlement_cycle')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cycle = trim((string) $this->input('settlement_cycle'));
|
||||
$this->merge([
|
||||
'settlement_cycle' => $cycle === '' ? 'weekly' : $cycle,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ final class PlayerAuthLoginRequest extends ApiFormRequest
|
||||
'site_code' => ['sometimes', 'nullable', 'string', 'max:64'],
|
||||
'username' => $this->nativePlayerUsernameRules(required: true),
|
||||
'password' => $this->nativePlayerPasswordRules(required: true),
|
||||
'captcha_key' => ['required', 'string', 'uuid'],
|
||||
'captcha_code' => ['required', 'string', 'max:32'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user