const crypto = require('crypto'); class VaultifyManager { constructor({ vaultModel, publicKey }) { this.Vault = vaultModel; this.publicKey = publicKey; this.cache = new Map(); // feature cache per customer } createMiddleware() { return async (req, res, next) => { const customerId = req.user?.Customer_ID; if (!customerId) { return res.status(403).send('No customer'); } await this.loadCustomer(customerId); req.vault = { has: (f) => this.has(customerId, f), get: (f, p) => this.get(customerId, f, p) }; next(); }; } // ========================================================= // LOAD ALL LICENSES FOR CUSTOMER // ========================================================= async loadCustomer(customerId) { const records = await this.Vault.findAll({ where: { Customer_ID: customerId, Active: true } }); const resultMap = new Map(); for (const record of records) { const valid = this.verify(record); if (!valid) continue; const payload = this.parsePayload(record.Payload); resultMap.set(record.Feature, { payload, expiresAt: record.ExpiresAt }); } this.cache.set(customerId, resultMap); return { customerId, features: [...resultMap.keys()] }; } // ========================================================= // VERIFY SIGNATURE // ========================================================= verify(record) { try { const payload = this.parsePayload(record.Payload); const data = { Customer_ID: record.Customer_ID, Feature: record.Feature, Payload: payload, ExpiresAt: record.ExpiresAt ?? null }; const verifier = crypto.createVerify('RSA-SHA256'); verifier.update(JSON.stringify(data)); verifier.end(); const isValid = verifier.verify( this.publicKey, record.Signature, 'base64' ); if (!isValid) return false; // 🔥 WICHTIG: Ablaufdatum HIER erzwingen if (payload.expiresAt && record.ExpiresAt) { if (payload.expiresAt !== record.ExpiresAt) { return false; } } if (record.ExpiresAt && new Date(record.ExpiresAt) < new Date()) { return false; } return true; } catch { return false; } } // ========================================================= // SAFE JSON PARSER // ========================================================= parsePayload(payload) { try { return typeof payload === 'string' ? JSON.parse(payload) : payload; } catch { return {}; } } // ========================================================= // FEATURE CHECK // ========================================================= has(customerId, feature) { const customer = this.cache.get(customerId); if (!customer) return false; const entry = customer.get(feature); if (!entry) return false; if (entry.expiresAt && new Date(entry.expiresAt) < new Date()) { return false; } return true; } // ========================================================= // GET FEATURE CONFIG // ========================================================= get(customerId, feature, path = null) { const customer = this.cache.get(customerId); if (!customer) return undefined; const entry = customer.get(feature); if (!entry) return undefined; if (!path) return entry.payload; return path .split('.') .reduce((obj, key) => obj?.[key], entry.payload); } // ========================================================= // REFRESH SINGLE FEATURE // ========================================================= async refreshFeature(customerId, feature) { const record = await this.Vault.findOne({ where: { Customer_ID: customerId, Feature: feature, Active: true } }); if (!record) return false; if (!this.verify(record)) return false; const customer = this.cache.get(customerId) || new Map(); customer.set(feature, { payload: this.parsePayload(record.Payload), expiresAt: record.ExpiresAt }); this.cache.set(customerId, customer); return true; } // ========================================================= // STATUS // ========================================================= status(customerId) { const customer = this.cache.get(customerId); return { customerId, features: customer ? [...customer.keys()] : [] }; } } module.exports = VaultifyManager;