Porting voce TRPG/AllTax (ElevenLabs "Sarah") sull'ARIA chat NIS2: - AiController::tts() + route POST /api/ai/tts: proxy same-origin a nexus-voice-ms (config VOICE_MS_URL=172.21.0.1:4215, voce TTS_VOICE_ID=EXAVITQu4vr4xnSDxMaL, eleven_turbo_v2_5). requireAuth, cache MP3 sha256 (sys_get_temp_dir/nis2-tts-cache), validazioni (text<=800, voice_id allowlist), stream audio/mpeg, 502 se upstream non-audio. - common.js: speak(answer) dopo ogni risposta ARIA (voce discreta vol .55, markdown strip), toggle 🔊/🔇 nell'header (muto persistito localStorage nis2_aria_voice), autoplay best-effort. Smoke: voice-ms da nis2-app HTTP 200 audio/mpeg 19KB; /api/ai/tts 401 senza auth. Reachability OK (no allowlist VOX necessaria). Cache-buster common.js -> 20260624g. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
142 lines
12 KiB
PHP
142 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* NIS2 Agile - Configurazione Principale
|
|
*/
|
|
|
|
require_once __DIR__ . '/env.php';
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// APPLICAZIONE
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('APP_ENV', Env::get('APP_ENV', 'development'));
|
|
define('APP_DEBUG', APP_ENV === 'development');
|
|
define('APP_NAME', Env::get('APP_NAME', 'NIS2 Agile'));
|
|
define('APP_VERSION', Env::get('APP_VERSION', '1.0.0'));
|
|
define('APP_URL', Env::get('APP_URL', 'http://localhost:8080'));
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// KILL-SWITCH INVIO EMAIL (ambiente con soli dati demo)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Quando false (default), EmailService NON effettua NESSUNA chiamata HTTP al relay:
|
|
// ogni invio (incidenti, training, inviti, reminder, OTP portale fornitori, ecc.)
|
|
// viene registrato a log e scartato.
|
|
// FAIL-SAFE: l'invio email e' DISABILITATO di default a prescindere da APP_ENV
|
|
// (l'ambiente contiene solo dati DEMO). Le email partono SOLO con override
|
|
// esplicito EMAIL_SENDING_ENABLED=true (env/vault). Cosi nessun invio accidentale
|
|
// a indirizzi reali finche' non si decide consapevolmente di attivarli.
|
|
define('EMAIL_SENDING_ENABLED', filter_var(
|
|
Env::get('EMAIL_SENDING_ENABLED', 'false'),
|
|
FILTER_VALIDATE_BOOLEAN
|
|
));
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// PERCORSI
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('BASE_PATH', dirname(dirname(__DIR__)));
|
|
define('APP_PATH', BASE_PATH . '/application');
|
|
define('PUBLIC_PATH', BASE_PATH . '/public');
|
|
define('UPLOAD_PATH', PUBLIC_PATH . '/uploads');
|
|
define('DATA_PATH', APP_PATH . '/data');
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// AUTENTICAZIONE JWT
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('JWT_SECRET', APP_ENV === 'production'
|
|
? Env::getRequired('JWT_SECRET')
|
|
: Env::get('JWT_SECRET', 'nis2_dev_jwt_secret'));
|
|
define('JWT_ALGORITHM', 'HS256');
|
|
define('JWT_EXPIRES_IN', Env::int('JWT_EXPIRES_IN', 7200));
|
|
define('JWT_REFRESH_EXPIRES_IN', Env::int('JWT_REFRESH_EXPIRES_IN', 604800));
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// PROVISIONING (B2B — lg231 e altri sistemi Agile)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// Secret master per provisioning automatico da sistemi Agile partner.
|
|
// lg231 lo usa per POST /api/services/provision (onboarding automatico tenant).
|
|
define('PROVISION_SECRET', Env::get('PROVISION_SECRET', 'nis2_prov_dev_secret'));
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// PASSWORD POLICY
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('PASSWORD_MIN_LENGTH', Env::int('PASSWORD_MIN_LENGTH', 8));
|
|
define('PASSWORD_REQUIRE_UPPERCASE', Env::bool('PASSWORD_REQUIRE_UPPERCASE', true));
|
|
define('PASSWORD_REQUIRE_NUMBER', Env::bool('PASSWORD_REQUIRE_NUMBER', true));
|
|
define('PASSWORD_REQUIRE_SPECIAL', Env::bool('PASSWORD_REQUIRE_SPECIAL', true));
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// CORS
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('CORS_ALLOWED_ORIGINS', array_filter([
|
|
APP_URL,
|
|
APP_ENV !== 'production' ? 'http://localhost:8080' : null,
|
|
APP_ENV !== 'production' ? 'http://localhost:3000' : null,
|
|
]));
|
|
define('CORS_ALLOWED_METHODS', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
define('CORS_ALLOWED_HEADERS', 'Content-Type, Authorization, X-Organization-Id, X-Requested-With');
|
|
define('CORS_MAX_AGE', '86400');
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// RATE LIMITING
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('RATE_LIMIT_AUTH_LOGIN', [
|
|
['max' => 5, 'window_seconds' => 60],
|
|
['max' => 20, 'window_seconds' => 3600],
|
|
]);
|
|
define('RATE_LIMIT_AUTH_REGISTER', [
|
|
['max' => 3, 'window_seconds' => 600],
|
|
]);
|
|
// Password reset (Fase 3 / G08): 3 richieste/h per IP+email
|
|
define('RATE_LIMIT_AUTH_FORGOT', [
|
|
['max' => 3, 'window_seconds' => 3600],
|
|
]);
|
|
// TTL token reset password (decisione utente §10.4: 30 min)
|
|
define('PASSWORD_RESET_TTL_SECONDS', 1800);
|
|
define('RATE_LIMIT_AI', [
|
|
['max' => 10, 'window_seconds' => 60],
|
|
['max' => 100, 'window_seconds' => 3600],
|
|
]);
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// AI (ANTHROPIC)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('ANTHROPIC_API_KEY', Env::get('ANTHROPIC_API_KEY', ''));
|
|
define('ANTHROPIC_MODEL', Env::get('ANTHROPIC_MODEL', 'claude-sonnet-4-5-20250929'));
|
|
define('ANTHROPIC_MAX_TOKENS', Env::int('ANTHROPIC_MAX_TOKENS', 4096));
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// VOCEA (Whistleblowing zero-knowledge — integrazione, DISABILITATA di default)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// SCAFFOLD DORMIENTE: con VOCEA_ENABLED=false (default) VoceaService non effettua
|
|
// nessuna chiamata di rete. Attivazione a MS live: applicare migrate_063 +
|
|
// VOCEA_ENABLED=true + VOCEA_API_KEY (vault tier1__nis2-app__vocea__*) +
|
|
// slug del canale su organizations.vocea_tenant_slug (status='active').
|
|
define('VOCEA_ENABLED', filter_var(Env::get('VOCEA_ENABLED', 'false'), FILTER_VALIDATE_BOOLEAN));
|
|
define('VOCEA_BASE_URL', Env::get('VOCEA_BASE_URL', 'https://api.vocea.cloud/api/wb/v1')); // API integratori (X-API-Key)
|
|
define('VOCEA_API_KEY', Env::get('VOCEA_API_KEY', '')); // wbk_... per-tenant (da vault)
|
|
define('VOCEA_PORTAL_URL', Env::get('VOCEA_PORTAL_URL', 'https://app.vocea.cloud')); // portale embed via wb-link.js
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// VOICE / TTS (ARIA voce naturale — proxy same-origin a nexus-voice-ms AgileHub)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('VOICE_MS_URL', Env::get('VOICE_MS_URL', 'http://172.21.0.1:4215/tts/speak')); // gateway nis2-network → host:4215
|
|
define('TTS_VOICE_ID', Env::get('TTS_VOICE_ID', 'EXAVITQu4vr4xnSDxMaL')); // voce "Sarah" (come AllTax/TRPG)
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// CERTISOURCE (atti-service.php)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('CERTISOURCE_API_URL', Env::get('CERTISOURCE_API_URL', 'https://certisource.it/atti-service.php'));
|
|
define('CERTISOURCE_API_KEY', Env::get('CERTISOURCE_API_KEY', '')); // cs_pat_...
|
|
define('CERTISOURCE_POLL_MAX', Env::int('CERTISOURCE_POLL_MAX', 30)); // max tentativi polling
|
|
define('CERTISOURCE_POLL_SEC', Env::int('CERTISOURCE_POLL_SEC', 3)); // secondi tra poll
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// FEEDBACK & SEGNALAZIONI
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
define('FEEDBACK_RESOLVE_PASSWORD', Env::get('FEEDBACK_RESOLVE_PASSWORD', ''));
|
|
define('FEEDBACK_WORKER_LOG', Env::get('FEEDBACK_WORKER_LOG', '/var/log/nis2/feedback-worker.log'));
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// TIMEZONE
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
date_default_timezone_set('Europe/Rome');
|