-- ============================================================================ -- Migration 034 - Portale fornitore: utenti, OTP/magic-link, sessioni revocabili -- ---------------------------------------------------------------------------- -- PROPOSTA DI DESIGN (NON ancora applicata). Da eseguire su host MySQL. -- -- Login passwordless del fornitore per compilare le campagne questionario: -- supplier_users = referenti del fornitore abilitati al portale -- supplier_otp = codici OTP + magic token (hash), tentativi, scadenza -- supplier_sessions = sessioni con jti, revocabili (revoked_at) -- -- Tutto NO-PASSWORD: l'accesso avviene via OTP/magic-link consegnato all'email. -- Sicurezza: lockout persistente su supplier_otp.attempts; sessione revocabile. -- -- CREATE TABLE IF NOT EXISTS (idempotente). DELIMITER -> usare "source". -- mysql -h localhost nis2_agile_db -e "source docs/sql/034_supplier_portal_auth.sql" -- ============================================================================ -- 1. Utenti del fornitore (referenti abilitati al portale) CREATE TABLE IF NOT EXISTS supplier_users ( id INT NOT NULL AUTO_INCREMENT, supplier_id INT NOT NULL, organization_id INT NOT NULL COMMENT 'Denormalizzato: org committente proprietaria del fornitore', email VARCHAR(255) NOT NULL, full_name VARCHAR(255) NULL, role ENUM('contact','compliance','signer') NOT NULL DEFAULT 'contact', is_active TINYINT(1) NOT NULL DEFAULT 1, last_login_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_supuser_supplier_email (supplier_id, email), KEY idx_supuser_org (organization_id), KEY idx_supuser_email (email), CONSTRAINT fk_supuser_supplier FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE CASCADE, CONSTRAINT fk_supuser_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Referenti del fornitore abilitati al portale (login passwordless)'; -- 2. OTP + magic-link (hash, tentativi, scadenza) CREATE TABLE IF NOT EXISTS supplier_otp ( id INT NOT NULL AUTO_INCREMENT, supplier_user_id INT NOT NULL, otp_hash CHAR(64) NULL COMMENT 'SHA-256 del codice OTP numerico', magic_token_hash CHAR(64) NULL COMMENT 'SHA-256 del magic-link token', purpose ENUM('login','questionnaire') NOT NULL DEFAULT 'login', attempts INT NOT NULL DEFAULT 0 COMMENT 'Tentativi di verifica falliti (lockout persistente)', max_attempts INT NOT NULL DEFAULT 5, locked_until DATETIME NULL COMMENT 'Lockout temporaneo dopo max_attempts', consumed_at DATETIME NULL COMMENT 'Quando usato (single-use)', expires_at DATETIME NOT NULL, ip_created VARCHAR(45) NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_sotp_user (supplier_user_id), KEY idx_sotp_expires (expires_at), KEY idx_sotp_magic (magic_token_hash), CONSTRAINT fk_sotp_user FOREIGN KEY (supplier_user_id) REFERENCES supplier_users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='OTP e magic-link per autenticazione passwordless del fornitore'; -- 3. Sessioni revocabili (jti, scadenza, revoca) CREATE TABLE IF NOT EXISTS supplier_sessions ( id INT NOT NULL AUTO_INCREMENT, supplier_user_id INT NOT NULL, organization_id INT NOT NULL COMMENT 'Denormalizzato per filtri/audit', jti CHAR(36) NOT NULL COMMENT 'JWT ID (UUID) della sessione', ip_address VARCHAR(45) NULL, user_agent VARCHAR(500) NULL, expires_at DATETIME NOT NULL, revoked_at DATETIME NULL COMMENT 'NOT NULL => sessione revocata', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_ssess_jti (jti), KEY idx_ssess_user (supplier_user_id), KEY idx_ssess_org (organization_id), KEY idx_ssess_expires (expires_at), CONSTRAINT fk_ssess_user FOREIGN KEY (supplier_user_id) REFERENCES supplier_users(id) ON DELETE CASCADE, CONSTRAINT fk_ssess_org FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Sessioni portale fornitore, revocabili via revoked_at'; -- ROLLBACK (manuale, ordine inverso per le FK): -- DROP TABLE IF EXISTS supplier_sessions; -- DROP TABLE IF EXISTS supplier_otp; -- DROP TABLE IF EXISTS supplier_users;