45 lines
2.8 KiB
PHP
Executable File
45 lines
2.8 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
namespace app\adminapi\service\iam\v2;
|
|
use RuntimeException;
|
|
use think\facade\Db;
|
|
|
|
final class IamV2Flow
|
|
{
|
|
public static function put(string $kind, string $secret, array $payload, int $ttl = 300): void
|
|
{
|
|
Db::name('iam_v2_flow')->insert(['flow_key' => hash('sha256', $kind . ':' . $secret), 'kind' => $kind,
|
|
'payload' => json_encode($payload, JSON_THROW_ON_ERROR), 'expires_at' => time() + $ttl, 'consumed' => 0]);
|
|
}
|
|
public static function peek(string $kind, string $secret, string $sid): array
|
|
{
|
|
$row = Db::name('iam_v2_flow')->where('flow_key', hash('sha256', $kind . ':' . $secret))->find();
|
|
if (!$row || $row['expires_at'] <= time() || (int) $row['consumed'] !== 0) { throw new RuntimeException('TRANSACTION_EXPIRED'); }
|
|
$p = json_decode($row['payload'], true, 32, JSON_THROW_ON_ERROR);
|
|
if (($p['sessionId'] ?? '') !== $sid) { throw new RuntimeException('ACCOUNT_MISMATCH'); }
|
|
return $p;
|
|
}
|
|
public static function take(string $kind, string $secret, string $sid): array
|
|
{
|
|
return Db::transaction(function () use ($kind, $secret, $sid) {
|
|
$key = hash('sha256', $kind . ':' . $secret);
|
|
$row = Db::name('iam_v2_flow')->where('flow_key', $key)->lock(true)->find();
|
|
if (!$row || $row['expires_at'] <= time() || (int) $row['consumed'] !== 0) { throw new RuntimeException('TRANSACTION_EXPIRED'); }
|
|
$p = json_decode($row['payload'], true, 32, JSON_THROW_ON_ERROR);
|
|
if (($p['sessionId'] ?? '') !== $sid) { throw new RuntimeException('ACCOUNT_MISMATCH'); }
|
|
if (Db::name('iam_v2_flow')->where('flow_key', $key)->where('consumed', 0)->update(['consumed' => 1]) !== 1) { throw new RuntimeException('TRANSACTION_EXPIRED'); }
|
|
return $p;
|
|
});
|
|
}
|
|
public static function form(string $url, array $fields): string
|
|
{
|
|
$nonce = bin2hex(random_bytes(18));
|
|
$escape = fn($s) => htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
$inputs = '';
|
|
foreach ($fields as $name => $value) { $inputs .= '<input type="hidden" name="' . $escape($name) . '" value="' . $escape($value) . '">'; }
|
|
$origin = parse_url($url, PHP_URL_SCHEME) . '://' . parse_url($url, PHP_URL_HOST) . (parse_url($url, PHP_URL_PORT) ? ':' . parse_url($url, PHP_URL_PORT) : '');
|
|
return '<!doctype html><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src \'none\'; base-uri \'none\'; form-action ' . $escape($origin) . '; script-src \'nonce-' . $nonce . '\'"><title>正在验证身份</title><form method="post" action="' . $escape($url) . '">' . $inputs . '<button>继续</button></form><script nonce="' . $nonce . '">document.forms[0].submit()</script>';
|
|
}
|
|
}
|