nis2-agile/docs/DESIGN_MODULO_QUESTIONARI_FORNITORI.md
2026-05-31 09:02:33 +02:00

9.4 KiB

Design — Modulo Questionari Fornitori + Portale Fornitore

Stato: DESIGN DEFINITIVO da approvare (nessun codice scritto). Da rivedere prima dell'implementazione. Autore: sessione 2026-05-31. Origine: richiesta utente (Cristiano Benassati). Decisioni utente confermate: (1) categorie predefinite + personalizzabili; (2) auth fornitore via magic-link / OTP email (NO password); (3) scadenze/ricorrenze configurabili (nessun default imposto); (4) portale sullo stesso dominio nis2.agile.software → pagina supplier-portal.html. Base esistente riusata: SupplyChainController (self-assessment link-token, P3), supplier-assessment.html, tabella supplier_questionnaires (mig. 027), relay email AgileHub (/api/emails/send-raw, X-Internal-Key).


1. Obiettivo

Evolvere il self-assessment fornitori (oggi: 8 domande hardcoded, link usa-e-getta) in un modulo questionari configurabile con portale fornitore ad accesso OTP, che gestisce categorie, domande estensibili (anche per procedure interne), scadenze/ricorrenze e aggiornamenti nel tempo.

Valore: due-diligence continua dei fornitori (Art. 21.2(d) NIS2; coerente con ENISA supply chain / NIST 800-161), riutilizzabile anche per procedure interne (privacy, 231, ESG, qualità).


2. Cosa esiste già (riuso)

Esistente Riuso nel nuovo modulo
SupplyChainController::sendQuestionnaire (link-token sha256 + scadenza) base per generare l'accesso OTP/campagna
publicQuestionnaire / submitPublicQuestionnaire (no-auth via token) base compilazione online
supplier-assessment.html (pagina pubblica) diventa il portale fornitore dinamico
relay email /api/emails/send-raw + X-Internal-Key invio OTP e notifiche scadenza
pattern token sq_ + sha256 + scadenza + single-use riusato per OTP e accesso
costante QUESTIONNAIRE (8 domande) migrata in DB come template "NIS2 base" di default

Principio: nessuna password fornitore (zero credenziali da custodire/rubare). Accesso a ogni sessione tramite codice/temporale via email.

Flusso

  1. L'azienda registra il fornitore (anagrafica) con email referente.
  2. Quando serve accesso (campagna o login spontaneo), il fornitore va su supplier-portal.html e inserisce la propria email → riceve un magic-link (token sp_ + 32 byte) e/o un codice OTP a 6 cifre, validi ~15 minuti, single-use.
  3. Cliccando il link / inserendo l'OTP, si apre una sessione fornitore (token di sessione separato, durata breve, es. 2h) limitata al solo profilo di quel fornitore.
  4. Nel portale il fornitore vede: anagrafica propria, questionari assegnati (aperti/scaduti/completati), scadenze, storico.

Sicurezza (vincolante)

  • Tabella separata supplier_users — MAI in users interni. Nessun ruolo interno, nessun accesso a dati di altri fornitori o dell'organizzazione cliente.
  • OTP: hash in DB (mai in chiaro), scadenza 15 min, max 5 tentativi, rate-limit per email/IP, invalidazione dopo uso.
  • Sessione fornitore: JWT dedicato con claim supplier_user_id + supplier_id (NON user_id); scope whitelisted ai soli endpoint /api/supplier-portal/*.
  • Enumerazione: risposta sempre "se l'email è registrata riceverai il codice" (opaca).
  • Audit: login OTP, apertura/compilazione questionario loggati (logAudit).
  • GDPR: l'email del referente è dato personale → retention + cancellazione su richiesta.

4. Schema dati (migrazioni nuove, additive)

supplier_categories
  id, organization_id (NULL = preset di sistema), name, slug, description, active
  -- SEED predefinito (personalizzabile): Cloud/IaaS, MSP/MSSP, Software/SaaS,
  --   Hardware/Manutenzione, Consulenza, Logistica, Telecomunicazioni, Altro

questionnaire_templates
  id, organization_id, category_id (FK, NULL = tutte le categorie),
  name, version, scope ENUM('nis2','privacy','quality','231','esg','custom'),
  recurrence_months (NULL = una tantum, configurabile),
  due_days_default (NULL = nessun default), is_active, created_by, created_at
  -- versioning come policy_versions: snapshot a ogni pubblicazione

questionnaire_questions
  id, template_id, order_index, code, text,
  type ENUM('yes_no_partial','scale_1_5','single_choice','multi_choice','text','number','file'),
  options JSON, weight, required, help_text

questionnaire_campaigns
  id, organization_id, supplier_id, template_id, template_version,
  status ENUM('draft','sent','in_progress','completed','expired','overdue'),
  access_token_hash, sent_to_email, sent_at,
  due_at (NULL ammesso), recurrence_months, next_recurrence_at,
  reminder_offsets JSON (es. [-14,-7,-1]; configurabile), next_reminder_at, reminder_count,
  score, risk_level, completed_at

questionnaire_answers
  id, campaign_id, question_id, answer_value, answer_text, file_ref, answered_at

supplier_users           -- account fornitore (OTP, no password)
  id, supplier_id, email, display_name, status, last_login_at, created_at

supplier_otp
  id, supplier_user_id, otp_hash, magic_token_hash, purpose,
  expires_at, used_at, attempts, ip_created

suppliers esistente: aggiungere category_id (FK) — contact_email già presente.


5. API

Lato azienda (JWT interno)

  • GET/POST/PUT/DELETE /api/supply-chain/categories
  • GET/POST/PUT /api/supply-chain/templates (+ versioning)
  • GET/POST/PUT/DELETE /api/supply-chain/templates/{id}/questions
  • POST /api/supply-chain/{supplierId}/campaigns (scegli template per categoria, due_at, ricorrenza, reminder)
  • GET /api/supply-chain/campaigns (cruscotto stato/scadenze)

Lato fornitore (portale, sessione OTP — namespace dedicato /api/supplier-portal/*)

  • POST /api/supplier-portal/request-otp (body: email) → invia OTP/magic-link (risposta opaca)
  • POST /api/supplier-portal/verify-otp (email + codice) → sessione fornitore
  • GET /api/supplier-portal/me → anagrafica + questionari assegnati + scadenze
  • GET /api/supplier-portal/questionnaire/{campaignId} → template dinamico da compilare
  • POST /api/supplier-portal/questionnaire/{campaignId}/submit → salva risposte + scoring

Cron scadenze (scripts/supplier-questionnaire-cron.sh, TZ Europe/Rome)

  • invia reminder agli offset configurati, marca overdue, apre la ricorrenza alla scadenza.

6. UI

  • supply-chain.html (lato azienda): tab "Categorie", "Template questionari" (editor domande ordinabile), "Campagne/scadenze" (cruscotto con semaforo in-regola/in-scadenza/scaduto). Sulla scheda fornitore: dropdown categoria + "Avvia campagna".
  • supplier-portal.html (NUOVA, stesso dominio): step OTP (inserisci email → inserisci codice) → dashboard fornitore (questionari assegnati, scadenze, storico) → compilazione dinamica del template. noindex,nofollow, nessun link all'app interna.
  • supplier-assessment.html: confluisce nel portale o resta come fallback link-token diretto.

7. Fasi incrementali

Fase Contenuto Rischio
1 Categorie (seed + CRUD) + template/domande configurabili in DB (migra le 8 domande) + render dinamico. Riusa il link-token attuale. Basso
2 Campagne con scadenze/ricorrenze configurabili + reminder (cron) + cruscotto. Medio
3 Portale fornitore + auth OTP/magic-link (supplier_users, supplier_otp, /api/supplier-portal/*, supplier-portal.html). Alto (auth esterna)
4 Tipi domanda avanzati (allegati/scale/multi) + scoring configurabile per peso. Medio

Fasi 1-2 danno già categorie + questionari configurabili + scadenze. Fase 3 aggiunge il portale OTP persistente.


8. Punti di sicurezza non negoziabili

  • supplier_users/sessione fornitore totalmente isolati da users interni (no IDOR cross-fornitore, no accesso dati org).
  • OTP hashed, scadenza breve, rate-limit, risposta opaca.
  • Segreti/eventuali integrazioni nel vault, non in DB/Git.
  • Tutti gli endpoint /api/supplier-portal/* validano che il campaignId/risorsa appartenga al supplier_id della sessione.

9. Stima (multi-sessione)

  • Fase 1: ~1 sessione · Fase 2: ~1 sessione · Fase 3 (portale+OTP): ~1.5-2 sessioni · Fase 4: ~0.5. Tutto additivo; nessuna modifica distruttiva ai moduli esistenti.

10. Decisioni APPROVATE (utente, 2026-05-31)

  1. Set categorie predefinite confermato (Cloud/IaaS, MSP/MSSP, Software/SaaS, Hardware/Manutenzione, Consulenza, Logistica, Telecomunicazioni, Altro) — personalizzabili.
  2. Auth: OTP a 6 cifre + magic-link, ENTRAMBI (il fornitore può usare l'uno o l'altro).
  3. Sessione fornitore = 4 ore; validità OTP/magic-link = 15 minuti, single-use, max 5 tentativi, rate-limit.
  4. Processo: prima review del design da 5 agenti, poi implementazione partendo dalla Fase 1.

11. Promemoria post-implementazione (regola progetto — VINCOLANTE per ogni fase)

Ad ogni fase che cambia funzionalità, aggiornare SEMPRE:

  • Guida (public/guida.html): nuovo capitolo/sezione "Portale fornitori" + àncora; allineare help.js _guideAnchor.
  • Help online (public/js/help.js): sezione contestuale per supply-chain (questionari/portale) e per la pagina portale.
  • Traduzioni (public/js/i18n.js): chiavi IT + EN per le nuove UI (categorie, template, campagne, portale, OTP).
  • KB AI / product_knowledge: aggiornare la knowledge base AI (RAG + product_knowledge AgileHub) con la nuova funzionalità, così l'assistente la conosce.
  • version.json bump, commit per fase, push da host.