[INVITE] Sistema inviti/licenze B2B + provisioning con invite_token
- InviteController: CRUD inviti (gen, list, show, revoke, rigenera, validate) - Token inv_* sha256-hashed, one-shot o multi-use, canali, scadenza - ServicesController::provision() accetta invite_token al posto di X-Provision-Secret Piano e durata forzati dall'invito, markUsed() chiaamto dopo provisioning riuscito - index.php: routing /api/invites/* aggiunto (controller + action map) - integrazioniext.html: nuovo tab "Inviti & Licenze" con flow completo, endpoints, esempi curl/php, guida lg231 aggiornata con sezione provisioning automatico Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6933e1d3fb
commit
612befd66d
@@ -477,22 +477,51 @@ class ServicesController extends BaseController
|
||||
{
|
||||
$this->setServiceHeaders();
|
||||
|
||||
// ── 1. Auth via PROVISION_SECRET ────────────────────────────────
|
||||
$secret = $_SERVER['HTTP_X_PROVISION_SECRET']
|
||||
?? ($_SERVER['HTTP_AUTHORIZATION'] ? str_replace('Provision ', '', $_SERVER['HTTP_AUTHORIZATION']) : null)
|
||||
?? null;
|
||||
|
||||
if (!$secret || !hash_equals(PROVISION_SECRET, $secret)) {
|
||||
$this->jsonError('Provision secret mancante o non valido', 401, 'INVALID_PROVISION_SECRET');
|
||||
}
|
||||
|
||||
// ── 2. Leggi e valida body ───────────────────────────────────────
|
||||
// ── 1. Leggi body (serve per invite_token) ───────────────────────
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$company = $body['company'] ?? [];
|
||||
$admin = $body['admin'] ?? [];
|
||||
$license = $body['license'] ?? [];
|
||||
$caller = $body['caller'] ?? [];
|
||||
|
||||
// ── 2. Auth: invite_token (canale B2B) O X-Provision-Secret (admin diretto) ──
|
||||
$inviteToken = trim($body['invite_token'] ?? '');
|
||||
$resolvedInvite = null;
|
||||
|
||||
if ($inviteToken) {
|
||||
// Percorso B2B tramite invito (lg231, e-commerce, partner)
|
||||
require_once __DIR__ . '/InviteController.php';
|
||||
$result = InviteController::resolveInvite($inviteToken);
|
||||
if (!$result['valid']) {
|
||||
$this->jsonError('Invito non valido: ' . $result['error'], 401, 'INVALID_INVITE_' . $result['code']);
|
||||
}
|
||||
$resolvedInvite = $result['invite'];
|
||||
|
||||
// Verifica restrizione P.IVA se presente nell'invito
|
||||
$partitaIvaCheck = preg_replace('/[^0-9]/', '', $company['partita_iva'] ?? '');
|
||||
if ($resolvedInvite['restrict_vat'] && $resolvedInvite['restrict_vat'] !== $partitaIvaCheck) {
|
||||
$this->jsonError('Invito non valido per questa P.IVA', 403, 'INVITE_VAT_MISMATCH');
|
||||
}
|
||||
// Verifica restrizione email admin se presente
|
||||
$adminEmailCheck = trim($admin['email'] ?? '');
|
||||
if ($resolvedInvite['restrict_email'] && strcasecmp($resolvedInvite['restrict_email'], $adminEmailCheck) !== 0) {
|
||||
$this->jsonError('Invito non valido per questa email admin', 403, 'INVITE_EMAIL_MISMATCH');
|
||||
}
|
||||
|
||||
// Piano e durata vengono dall'invito (non dal body)
|
||||
$license['plan'] = $resolvedInvite['plan'];
|
||||
$license['duration_months'] = (int)$resolvedInvite['duration_months'];
|
||||
} else {
|
||||
// Percorso diretto admin/e-commerce via secret header
|
||||
$secret = $_SERVER['HTTP_X_PROVISION_SECRET']
|
||||
?? (isset($_SERVER['HTTP_AUTHORIZATION'])
|
||||
? str_replace('Provision ', '', $_SERVER['HTTP_AUTHORIZATION'])
|
||||
: null);
|
||||
if (!$secret || !hash_equals(PROVISION_SECRET, $secret)) {
|
||||
$this->jsonError('invite_token o X-Provision-Secret richiesti', 401, 'AUTH_REQUIRED');
|
||||
}
|
||||
}
|
||||
|
||||
$ragioneSociale = trim($company['ragione_sociale'] ?? '');
|
||||
$partitaIva = preg_replace('/[^0-9]/', '', $company['partita_iva'] ?? '');
|
||||
$adminEmail = trim($admin['email'] ?? '');
|
||||
@@ -660,7 +689,16 @@ class ServicesController extends BaseController
|
||||
performedBy: ($caller['system'] ?? 'external') . ':provision'
|
||||
);
|
||||
|
||||
// ── 9. Callback a lg231 (asincrono, non bloccante) ───────────────
|
||||
// ── 9. Segna invito come usato (se presente) ────────────────────
|
||||
if ($resolvedInvite) {
|
||||
InviteController::markUsed(
|
||||
(int)$resolvedInvite['id'],
|
||||
$orgId,
|
||||
$_SERVER['REMOTE_ADDR'] ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
// ── 10. Callback a lg231 (asincrono, non bloccante) ──────────────
|
||||
$callbackUrl = $caller['callback_url'] ?? null;
|
||||
if ($callbackUrl) {
|
||||
$callbackPayload = json_encode([
|
||||
@@ -707,6 +745,10 @@ class ServicesController extends BaseController
|
||||
// Link
|
||||
'dashboard_url' => APP_URL . '/dashboard.html',
|
||||
'settings_url' => APP_URL . '/settings.html',
|
||||
// Info invito (se usato)
|
||||
'invite_id' => $resolvedInvite ? (int)$resolvedInvite['id'] : null,
|
||||
'invite_plan' => $resolvedInvite ? $resolvedInvite['plan'] : ($license['plan'] ?? 'professional'),
|
||||
'license_expires_at' => $expiresAt,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user