CaiWuXiTong/app/Http/Controllers/Business/StoreAccountPasswords.php

175 lines
8.5 KiB
PHP

<?php
namespace App\Http\Controllers\Business;
use App\Services\StoreAccountPasswordClient;
use App\Utilities\SidebarPermissions;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Modules\EcommerceFinance\Repositories\StoreAccountMirrorRepository;
use Throwable;
class StoreAccountPasswords extends Controller
{
public function reveal(string $account, Request $request, StoreAccountMirrorRepository $accounts, StoreAccountPasswordClient $client)
{
if (app()->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',
]);
}
}