Files
zyt/server/app/adminapi/service/iam/v2/IamV2Events.php
T

38 lines
2.3 KiB
PHP

<?php
declare(strict_types=1);
namespace app\adminapi\service\iam\v2;
use RuntimeException;
use think\facade\Db;
final class IamV2Events
{
public static function accept(string $raw, string $signature, string $eventId, string $secret, ?callable $revoke = null): array
{
if (strlen($raw) > 65536 || strlen($secret) < 32 || !hash_equals('sha256=' . hash_hmac('sha256', $raw, $secret), $signature)) { throw new RuntimeException('FORBIDDEN'); }
$event = json_decode($raw, true, 32, JSON_THROW_ON_ERROR);
if (empty($event['employeeId']) || $eventId === '' || strlen($eventId) > 128 || $eventId !== ($event['id'] ?? '') || !in_array('zyt', (array) ($event['applicationIds'] ?? []), true)) { throw new RuntimeException('INVALID_EVENT'); }
$occurredAt = (string) ($event['occurredAt'] ?? '');
if (!preg_match('/^\d{4}-\d{2}-\d{2}T.*(Z|[+-][0-9]{2}:[0-9]{2})$/D', $occurredAt)) { throw new RuntimeException('INVALID_EVENT'); }
$stamp = strtotime($occurredAt);
if (!$stamp || $stamp > time() + 60 || time() - $stamp > 300) { throw new RuntimeException('TRANSACTION_EXPIRED'); }
$revoke ??= fn(int $id) => IamV2Manager::revoke($id);
return Db::transaction(function () use ($event, $eventId, $raw, $revoke) {
$existing = Db::name('iam_v2_event')->where('event_id', $eventId)->lock(true)->find();
if ($existing) {
if (!hash_equals($existing['digest'], hash('sha256', $raw))) { throw new RuntimeException('INVALID_EVENT'); }
return ['accepted' => true, 'duplicate' => true];
}
Db::name('iam_v2_event')->insert(['event_id' => $eventId, 'digest' => hash('sha256', $raw), 'received_at' => time()]);
$rows = Db::name('iam_v2_binding')->where('employee_id', (string) $event['employeeId'])->lock(true)->select()->toArray();
foreach ($rows as $row) {
if (isset($event['bindingVersion']) && (int) $event['bindingVersion'] < (int) $row['version']) { continue; }
Db::name('iam_v2_binding')->where('admin_id', $row['admin_id'])->update(['state' => 'revoked', 'checked_at' => 0,
'version' => max((int) $row['version'], (int) ($event['bindingVersion'] ?? 0))]);
$revoke((int) $row['admin_id']);
}
return ['accepted' => true];
});
}
}