payfrit-api/api/hub/vcgateway/invites/create.php
Mike cd373dd616 Add VC Gateway endpoints for invite links, visitor auth, DM, and rate limiting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:34:52 +00:00

96 lines
2.7 KiB
PHP

<?php
/**
* POST /api/hub/vcgateway/invites/create.php
*
* Create an invite link for VC Gateway.
* Requires agent auth (X-Agent-Address header).
*
* Body:
* Label string optional Human label (e.g. "Sequoia - Partner X")
* AllowedChannels int[] required Array of Hub_Channels.ID the visitor can read
* HostAddress string required Sprinter agent address for DM target
* ExpiresAt string optional ISO8601 expiration datetime (null = never)
* MaxUses int optional Max times this link can be used (0 = unlimited)
*
* Response:
* OK, ID, Token, InviteURL
*/
require_once __DIR__ . '/../helpers.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['OK' => false, 'ERROR' => 'method_not_allowed'], 405);
}
$agentAddress = requireAgentAuth();
$body = readJsonBody();
// Validate required fields
$allowedChannels = $body['AllowedChannels'] ?? null;
$hostAddress = trim($body['HostAddress'] ?? '');
if (!is_array($allowedChannels) || empty($allowedChannels)) {
jsonResponse(['OK' => false, 'ERROR' => 'allowed_channels_required'], 400);
}
if (empty($hostAddress)) {
jsonResponse(['OK' => false, 'ERROR' => 'host_address_required'], 400);
}
// Validate all channel IDs exist
$channelIds = array_map('intval', $allowedChannels);
$placeholders = implode(',', array_fill(0, count($channelIds), '?'));
$channels = queryTimed(
"SELECT ID FROM Hub_Channels WHERE ID IN ($placeholders)",
$channelIds
);
$foundIds = array_column($channels, 'ID');
$missing = array_diff($channelIds, array_map('intval', $foundIds));
if (!empty($missing)) {
jsonResponse(['OK' => false, 'ERROR' => 'invalid_channel_ids', 'InvalidIDs' => array_values($missing)], 400);
}
// Optional fields
$label = trim($body['Label'] ?? '');
if (strlen($label) > 255) {
$label = substr($label, 0, 255);
}
$expiresAt = null;
if (!empty($body['ExpiresAt'])) {
$dt = strtotime($body['ExpiresAt']);
if ($dt === false || $dt <= time()) {
jsonResponse(['OK' => false, 'ERROR' => 'expires_at_must_be_future'], 400);
}
$expiresAt = date('Y-m-d H:i:s', $dt);
}
$maxUses = max(0, (int)($body['MaxUses'] ?? 0));
// Generate a URL-safe token
$token = bin2hex(random_bytes(24)); // 48 chars, URL-safe
queryTimed(
"INSERT INTO Hub_InviteLinks (Token, Label, AllowedChannels, HostAddress, ExpiresAt, MaxUses, CreatedBy)
VALUES (?, ?, ?, ?, ?, ?, ?)",
[
$token,
$label,
json_encode($channelIds),
$hostAddress,
$expiresAt,
$maxUses,
$agentAddress,
]
);
$id = (int)lastInsertId();
$inviteUrl = baseUrl() . '/vc/' . $token;
jsonResponse([
'OK' => true,
'ID' => $id,
'Token' => $token,
'InviteURL' => $inviteUrl,
]);