bound('debugbar')) { app('debugbar')->disable(); } $requestId = (string) Str::uuid(); $purpose = $request->input('purpose'); if (! preg_match('/^[A-Za-z0-9_-]{1,128}$/D', $account) || ! in_array($purpose, ['view', 'copy'], true) || array_diff(array_keys($request->all()), ['purpose', '_token']) !== [] || $request->query() !== []) { return $this->respond(422, $requestId); } try { DB::table('store_account_password_audits')->insert([ 'request_id' => $requestId, 'actor_user_id' => $request->user()->id, 'company_id' => company_id(), 'store_account_id' => $account, 'purpose' => $purpose, 'created_at' => now(), ]); } catch (Throwable) { return $this->respond(503, $requestId); } $result = ['status' => 503]; try { if (! $this->canReveal($request)) { $result = ['status' => 403]; } elseif (RateLimiter::tooManyAttempts('store-password:'.$request->user()->id, 10)) { $result = ['status' => 429]; } else { RateLimiter::hit('store-password:'.$request->user()->id, 60); $result = $accounts->containsAccount($account) ? $client->reveal($account, $request->user()->id, $purpose) : ['status' => 404]; } } catch (Throwable) { $result = ['status' => 503]; } try { $saved = DB::table('store_account_password_audits')->where('request_id', $requestId)->update([ 'status' => $result['status'], 'upstream_request_id' => $result['upstream_request_id'] ?? null, 'completed_at' => now(), ]); if ($saved !== 1) { return $this->respond(503, $requestId); } } catch (Throwable) { return $this->respond(503, $requestId); } return $this->respond($result['status'], $requestId, $result['password'] ?? null); } public function revealVisiblePage(Request $request, StoreAccountMirrorRepository $accounts, StoreAccountPasswordClient $client) { if (app()->bound('debugbar')) { app('debugbar')->disable(); } $batchRequestId = (string) Str::uuid(); $accountIds = $request->input('accounts'); if (array_diff(array_keys($request->all()), ['accounts', '_token']) !== [] || $request->query() !== [] || ! is_array($accountIds) || count($accountIds) < 1 || count($accountIds) > 50 || count(array_unique($accountIds, SORT_REGULAR)) !== count($accountIds) || collect($accountIds)->contains(fn ($id) => ! is_string($id) || ! preg_match('/^[A-Za-z0-9_-]{1,128}$/D', $id))) { return $this->respondVisiblePage(422, $batchRequestId); } $accountIds = array_values($accountIds); $requestIds = collect($accountIds)->mapWithKeys(fn ($id) => [$id => (string) Str::uuid()])->all(); try { DB::table('store_account_password_audits')->insert(collect($accountIds)->map(fn ($id) => [ 'request_id' => $requestIds[$id], 'actor_user_id' => $request->user()->id, 'company_id' => company_id(), 'store_account_id' => $id, 'purpose' => 'view', 'created_at' => now(), ])->all()); } catch (Throwable) { return $this->respondVisiblePage(503, $batchRequestId); } $result = ['status' => 503]; try { if (! $this->canReveal($request)) { $result = ['status' => 403]; } elseif (RateLimiter::tooManyAttempts('store-password-page:'.$request->user()->id, 5)) { $result = ['status' => 429]; } else { RateLimiter::hit('store-password-page:'.$request->user()->id, 60); $result = $accounts->containsAccounts($accountIds) ? $client->revealVisiblePage($accountIds, $request->user()->id) : ['status' => 404]; } } catch (Throwable) { $result = ['status' => 503]; } $items = []; try { DB::transaction(function () use ($accountIds, $requestIds, $result, &$items) { foreach ($accountIds as $accountId) { $upstream = $result['status'] === 200 ? ($result['items'][$accountId] ?? ['status' => 503]) : ['status' => $result['status']]; $saved = DB::table('store_account_password_audits')->where('request_id', $requestIds[$accountId])->update([ 'status' => $upstream['status'], 'upstream_request_id' => $upstream['upstream_request_id'] ?? null, 'completed_at' => now(), ]); if ($saved !== 1) { throw new \RuntimeException('store_password_audit_update_failed'); } $items[$accountId] = [ 'status' => $upstream['status'], 'requestId' => $requestIds[$accountId], 'password' => $upstream['status'] === 200 ? $upstream['password'] : null, ]; } }); } catch (Throwable) { return $this->respondVisiblePage(503, $batchRequestId); } if ($result['status'] !== 200) { return $this->respondVisiblePage($result['status'], $batchRequestId); } return $this->respondVisiblePage(200, $batchRequestId, $items); } private function canReveal(Request $request): bool { $section = collect(SidebarPermissions::navigationFor($request->user()))->firstWhere('slug', 'store-management'); return collect($section['pages'] ?? [])->contains('slug', 'store-accounts'); } private function respond(int $status, string $requestId, ?string $password = null) { $messages = [403 => '当前账号没有查看店铺密码的权限。', 404 => '该账号暂无可读取的密码。', 422 => '请求无效,请刷新页面后重试。', 429 => '操作过于频繁,请稍后再试。', 503 => '密码服务暂不可用,请稍后再试或联系管理员。']; return response()->json($status === 200 ? ['password' => $password, 'requestId' => $requestId] : ['message' => $messages[$status] ?? $messages[503], 'requestId' => $requestId], $status) ->withHeaders(['Cache-Control' => 'no-store, private', 'Pragma' => 'no-cache', 'X-Content-Type-Options' => 'nosniff', 'Referrer-Policy' => 'no-referrer']); } private function respondVisiblePage(int $status, string $requestId, array $items = []) { $messages = [403 => '当前账号没有查看店铺密码的页面权限。', 404 => '当前页包含无效账号,请刷新后重试。', 422 => '请求无效,请刷新页面后重试。', 429 => '自动读取过于频繁,请稍后刷新。', 503 => '密码服务暂不可用,请稍后刷新或联系管理员。']; $body = $status === 200 ? ['items' => collect($items)->map(fn ($item, $accountId) => array_filter([ 'accountId' => $accountId, 'status' => $item['status'], 'requestId' => $item['requestId'], 'password' => $item['status'] === 200 ? $item['password'] : null, 'message' => $item['status'] === 200 ? null : ($messages[$item['status']] ?? $messages[503]), ], fn ($value) => $value !== null))->values()->all()] : ['message' => $messages[$status] ?? $messages[503], 'requestId' => $requestId]; return response()->json($body, $status)->withHeaders([ 'Cache-Control' => 'no-store, private', 'Pragma' => 'no-cache', 'X-Content-Type-Options' => 'nosniff', 'Referrer-Policy' => 'no-referrer', ]); } }