38 lines
2.3 KiB
PHP
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];
|
|
});
|
|
}
|
|
}
|