198 lines
8.4 KiB
PHP
198 lines
8.4 KiB
PHP
<?php
|
|
/**
|
|
* Public identity verification REST API.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Identity;
|
|
|
|
use AKSRT\Ledger\OperationKey;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
final class RestController {
|
|
|
|
private const NS = 'aksrt/v1';
|
|
|
|
private ?array $request_application = null;
|
|
private string $request_uuid = '';
|
|
|
|
public function __construct(
|
|
private ?ApplicationRepository $applications = null,
|
|
private ?ApplicationService $service = null
|
|
) {
|
|
$this->applications ??= new ApplicationRepository();
|
|
$this->service ??= new ApplicationService($this->applications);
|
|
}
|
|
|
|
public function register_routes(): void {
|
|
$uuid_args = [
|
|
'uuid' => [
|
|
'type' => 'string',
|
|
'format' => 'uuid',
|
|
'required' => true,
|
|
'validate_callback' => static fn($value): bool => wp_is_uuid((string) $value),
|
|
'sanitize_callback' => static fn($value): string => strtolower(sanitize_text_field((string) $value)),
|
|
],
|
|
];
|
|
register_rest_route(self::NS, '/identity/applications', [
|
|
'methods' => \WP_REST_Server::CREATABLE,
|
|
'callback' => [$this, 'create'],
|
|
'permission_callback' => [$this, 'logged_in'],
|
|
'args' => [
|
|
'type' => ['type' => 'string', 'required' => true, 'enum' => ['personal', 'enterprise'], 'sanitize_callback' => 'sanitize_key'],
|
|
'person_name' => ['type' => 'string', 'required' => true, 'minLength' => 1, 'maxLength' => 50, 'sanitize_callback' => 'sanitize_text_field'],
|
|
'id_number' => ['type' => 'string', 'required' => true, 'minLength' => 15, 'maxLength' => 18, 'sanitize_callback' => 'sanitize_text_field'],
|
|
'company_name' => ['type' => 'string', 'required' => false, 'maxLength' => 191, 'sanitize_callback' => 'sanitize_text_field'],
|
|
'company_code' => ['type' => 'string', 'required' => false, 'maxLength' => 32, 'sanitize_callback' => 'sanitize_text_field'],
|
|
'consent' => ['type' => 'boolean', 'required' => true],
|
|
'meta_info' => ['type' => 'string', 'required' => false, 'maxLength' => 65535, 'sanitize_callback' => 'sanitize_text_field'],
|
|
],
|
|
]);
|
|
register_rest_route(self::NS, '/identity/applications/current', [
|
|
'methods' => \WP_REST_Server::READABLE,
|
|
'callback' => [$this, 'current'],
|
|
'permission_callback' => [$this, 'logged_in'],
|
|
]);
|
|
register_rest_route(self::NS, '/identity/applications/(?P<uuid>[a-f0-9-]{36})', [
|
|
'methods' => \WP_REST_Server::READABLE,
|
|
'callback' => [$this, 'get'],
|
|
'permission_callback' => [$this, 'owns_application'],
|
|
'args' => $uuid_args,
|
|
]);
|
|
register_rest_route(self::NS, '/identity/applications/(?P<uuid>[a-f0-9-]{36})/refresh', [
|
|
'methods' => \WP_REST_Server::CREATABLE,
|
|
'callback' => [$this, 'refresh'],
|
|
'permission_callback' => [$this, 'owns_application'],
|
|
'args' => $uuid_args,
|
|
]);
|
|
register_rest_route(self::NS, '/identity/applications/(?P<uuid>[a-f0-9-]{36})/manual', [
|
|
'methods' => \WP_REST_Server::CREATABLE,
|
|
'callback' => [$this, 'manual'],
|
|
'permission_callback' => [$this, 'owns_application'],
|
|
'args' => $uuid_args,
|
|
]);
|
|
}
|
|
|
|
public function logged_in(\WP_REST_Request $request) {
|
|
if (!is_user_logged_in()) {
|
|
return new \WP_Error('aksrt_identity_auth', __('请先登录', 'aksrt'), ['status' => 401]);
|
|
}
|
|
$nonce = (string) $request->get_header('X-WP-Nonce');
|
|
if ($nonce === '' || !wp_verify_nonce($nonce, 'wp_rest')) {
|
|
return new \WP_Error('aksrt_identity_nonce', __('安全校验失败,请刷新页面后重试', 'aksrt'), ['status' => 403]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function owns_application(\WP_REST_Request $request) {
|
|
$authenticated = $this->logged_in($request);
|
|
if (is_wp_error($authenticated)) {
|
|
return $authenticated;
|
|
}
|
|
$application = $this->application_for_request($request);
|
|
if (!is_array($application) || (int) $application['user_id'] !== get_current_user_id()) {
|
|
return new \WP_Error('aksrt_identity_forbidden', __('无权访问该认证申请', 'aksrt'), ['status' => 403]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function create(\WP_REST_Request $request): \WP_REST_Response {
|
|
$params = $request->get_json_params();
|
|
$params = is_array($params) ? $params : [];
|
|
$operation_key = OperationKey::from_request($request, 'identity-create', get_current_user_id());
|
|
if (is_wp_error($operation_key)) {
|
|
return $this->respond($operation_key);
|
|
}
|
|
$request_id = strtolower(trim((string) $request->get_header('Idempotency-Key')));
|
|
$result = $this->service->create(get_current_user_id(), $params, $request_id, $operation_key);
|
|
return $this->respond($result, 201);
|
|
}
|
|
|
|
public function current(): \WP_REST_Response {
|
|
$user_id = get_current_user_id();
|
|
$data = class_exists('\\AKSRT\\User\\AuthApply')
|
|
? \AKSRT\User\AuthApply::snapshot_view($user_id)
|
|
: ['user_id' => $user_id, 'current' => null, 'active' => null, 'latest' => null];
|
|
return $this->no_store(new \WP_REST_Response(['success' => true, 'data' => $data]));
|
|
}
|
|
|
|
public function get(\WP_REST_Request $request): \WP_REST_Response {
|
|
$application = $this->application_for_request($request);
|
|
if (!$application) {
|
|
return $this->no_store(new \WP_REST_Response([
|
|
'success' => false,
|
|
'code' => 'aksrt_identity_missing',
|
|
'message' => __('认证申请不存在', 'aksrt'),
|
|
], 404));
|
|
}
|
|
return $this->no_store(new \WP_REST_Response(['success' => true, 'data' => $this->service->view($application)]));
|
|
}
|
|
|
|
public function refresh(\WP_REST_Request $request): \WP_REST_Response {
|
|
$application = $this->application_for_request($request);
|
|
if (!$application) {
|
|
return $this->no_store(new \WP_REST_Response([
|
|
'success' => false,
|
|
'code' => 'aksrt_identity_missing',
|
|
'message' => __('认证申请不存在', 'aksrt'),
|
|
], 404));
|
|
}
|
|
$operation_key = OperationKey::from_request($request, 'identity-refresh', get_current_user_id());
|
|
if (is_wp_error($operation_key)) {
|
|
return $this->respond($operation_key);
|
|
}
|
|
return $this->respond($this->service->refresh($application, $operation_key));
|
|
}
|
|
|
|
public function manual(\WP_REST_Request $request): \WP_REST_Response {
|
|
$application = $this->application_for_request($request);
|
|
if (!$application) {
|
|
return $this->no_store(new \WP_REST_Response([
|
|
'success' => false,
|
|
'code' => 'aksrt_identity_missing',
|
|
'message' => __('认证申请不存在', 'aksrt'),
|
|
], 404));
|
|
}
|
|
$operation_key = OperationKey::from_request($request, 'identity-manual', get_current_user_id());
|
|
if (is_wp_error($operation_key)) {
|
|
return $this->respond($operation_key);
|
|
}
|
|
$files = $request->get_file_params();
|
|
return $this->respond($this->service->move_to_manual($application, $files['evidence'] ?? null, $operation_key));
|
|
}
|
|
|
|
/** @return array<string,mixed>|null */
|
|
private function application_for_request(\WP_REST_Request $request): ?array {
|
|
$uuid = (string) $request['uuid'];
|
|
if ($uuid !== $this->request_uuid) {
|
|
$this->request_uuid = $uuid;
|
|
$this->request_application = $this->applications->find_by_uuid($uuid);
|
|
}
|
|
return is_array($this->request_application) ? $this->request_application : null;
|
|
}
|
|
|
|
private function respond($result, int $success_status = 200): \WP_REST_Response {
|
|
if (is_wp_error($result)) {
|
|
$data = $result->get_error_data();
|
|
$status = is_array($data) ? (int) ($data['status'] ?? 0) : 0;
|
|
if ($status < 400 || $status > 599) {
|
|
$status = in_array($result->get_error_code(), ['aksrt_identity_active', 'aksrt_identity_verified', 'aksrt_identity_conflict', 'aksrt_identity_idempotency_conflict'], true) ? 409 : 400;
|
|
}
|
|
return $this->no_store(new \WP_REST_Response([
|
|
'success' => false,
|
|
'code' => $result->get_error_code(),
|
|
'message' => $result->get_error_message(),
|
|
], $status));
|
|
}
|
|
return $this->no_store(new \WP_REST_Response(['success' => true, 'data' => $result], $success_status));
|
|
}
|
|
|
|
private function no_store(\WP_REST_Response $response): \WP_REST_Response {
|
|
$response->header('Cache-Control', 'no-store, private');
|
|
$response->header('Pragma', 'no-cache');
|
|
return $response;
|
|
}
|
|
}
|