@relazio/plugin-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +255 -0
- package/dist/core/manifest.d.ts +21 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +96 -0
- package/dist/core/plugin.d.ts +110 -0
- package/dist/core/plugin.d.ts.map +1 -0
- package/dist/core/plugin.js +272 -0
- package/dist/core/types.d.ts +222 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +5 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/jobs/progress.d.ts +79 -0
- package/dist/jobs/progress.d.ts.map +1 -0
- package/dist/jobs/progress.js +165 -0
- package/dist/registry/installation.d.ts +101 -0
- package/dist/registry/installation.d.ts.map +1 -0
- package/dist/registry/installation.js +148 -0
- package/dist/security/hmac.d.ts +28 -0
- package/dist/security/hmac.d.ts.map +1 -0
- package/dist/security/hmac.js +68 -0
- package/dist/server/express.d.ts +43 -0
- package/dist/server/express.d.ts.map +1 -0
- package/dist/server/express.js +279 -0
- package/package.json +52 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JobQueue = exports.InMemorySecretProvider = exports.JobProgressTracker = void 0;
|
|
4
|
+
const hmac_1 = require("../security/hmac");
|
|
5
|
+
/**
|
|
6
|
+
* Implementazione del contesto job per transform asincrone
|
|
7
|
+
*/
|
|
8
|
+
class JobProgressTracker {
|
|
9
|
+
constructor(jobId, callbackUrl, webhookSecret) {
|
|
10
|
+
this.currentProgress = 0;
|
|
11
|
+
this.jobId = jobId;
|
|
12
|
+
this.callbackUrl = callbackUrl;
|
|
13
|
+
this.hmac = new hmac_1.HMACUtils(webhookSecret);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Aggiorna il progresso del job
|
|
17
|
+
*/
|
|
18
|
+
async updateProgress(progress, message) {
|
|
19
|
+
this.currentProgress = Math.min(100, Math.max(0, progress));
|
|
20
|
+
await this.sendWebhook({
|
|
21
|
+
jobId: this.jobId,
|
|
22
|
+
status: 'processing',
|
|
23
|
+
progress: this.currentProgress,
|
|
24
|
+
progressMessage: message,
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Marca il job come completato
|
|
30
|
+
*/
|
|
31
|
+
async complete(result) {
|
|
32
|
+
await this.sendWebhook({
|
|
33
|
+
jobId: this.jobId,
|
|
34
|
+
status: 'completed',
|
|
35
|
+
progress: 100,
|
|
36
|
+
result,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Marca il job come fallito
|
|
42
|
+
*/
|
|
43
|
+
async fail(error) {
|
|
44
|
+
await this.sendWebhook({
|
|
45
|
+
jobId: this.jobId,
|
|
46
|
+
status: 'failed',
|
|
47
|
+
error,
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Cancella il job
|
|
53
|
+
*/
|
|
54
|
+
async cancel() {
|
|
55
|
+
// TODO: Implementare logica di cancellazione
|
|
56
|
+
console.log(`Job ${this.jobId} cancellation requested`);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Invia webhook alla piattaforma
|
|
60
|
+
*/
|
|
61
|
+
async sendWebhook(payload) {
|
|
62
|
+
const body = JSON.stringify(payload);
|
|
63
|
+
const signature = this.hmac.generateHeader(body);
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(this.callbackUrl, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
'X-Plugin-Signature': signature,
|
|
70
|
+
},
|
|
71
|
+
body,
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
console.error(`Webhook failed: ${response.status} ${response.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('Failed to send webhook:', error);
|
|
79
|
+
// Non rilanciare l'errore per non bloccare il job
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.JobProgressTracker = JobProgressTracker;
|
|
84
|
+
/**
|
|
85
|
+
* Provider in-memory (per sviluppo/testing)
|
|
86
|
+
*/
|
|
87
|
+
class InMemorySecretProvider {
|
|
88
|
+
constructor() {
|
|
89
|
+
this.secrets = new Map();
|
|
90
|
+
}
|
|
91
|
+
setSecret(organizationId, secret) {
|
|
92
|
+
this.secrets.set(organizationId, secret);
|
|
93
|
+
}
|
|
94
|
+
async getSecret(organizationId) {
|
|
95
|
+
return this.secrets.get(organizationId) || null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.InMemorySecretProvider = InMemorySecretProvider;
|
|
99
|
+
/**
|
|
100
|
+
* Job queue per gestire transform asincrone (multi-tenant)
|
|
101
|
+
*/
|
|
102
|
+
class JobQueue {
|
|
103
|
+
constructor(webhookSecretOrProvider) {
|
|
104
|
+
this.activeJobs = new Map();
|
|
105
|
+
if (typeof webhookSecretOrProvider === 'string') {
|
|
106
|
+
// Single-tenant mode (legacy)
|
|
107
|
+
this.webhookSecret = webhookSecretOrProvider;
|
|
108
|
+
}
|
|
109
|
+
else if (webhookSecretOrProvider) {
|
|
110
|
+
// Multi-tenant mode
|
|
111
|
+
this.secretProvider = webhookSecretOrProvider;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Crea un nuovo job (single-tenant)
|
|
116
|
+
*/
|
|
117
|
+
createJob(jobId, callbackUrl) {
|
|
118
|
+
if (!this.webhookSecret) {
|
|
119
|
+
throw new Error('Webhook secret not configured for single-tenant mode');
|
|
120
|
+
}
|
|
121
|
+
const job = new JobProgressTracker(jobId, callbackUrl, this.webhookSecret);
|
|
122
|
+
this.activeJobs.set(jobId, job);
|
|
123
|
+
return job;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Crea un nuovo job (multi-tenant)
|
|
127
|
+
*/
|
|
128
|
+
async createJobForOrganization(jobId, callbackUrl, organizationId) {
|
|
129
|
+
if (!this.secretProvider) {
|
|
130
|
+
throw new Error('Secret provider not configured for multi-tenant mode');
|
|
131
|
+
}
|
|
132
|
+
const secret = await this.secretProvider.getSecret(organizationId);
|
|
133
|
+
if (!secret) {
|
|
134
|
+
throw new Error(`Webhook secret not found for organization: ${organizationId}`);
|
|
135
|
+
}
|
|
136
|
+
const job = new JobProgressTracker(jobId, callbackUrl, secret);
|
|
137
|
+
this.activeJobs.set(jobId, job);
|
|
138
|
+
return job;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Ottieni un job esistente
|
|
142
|
+
*/
|
|
143
|
+
getJob(jobId) {
|
|
144
|
+
return this.activeJobs.get(jobId);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Rimuovi un job completato
|
|
148
|
+
*/
|
|
149
|
+
removeJob(jobId) {
|
|
150
|
+
this.activeJobs.delete(jobId);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Conta job attivi
|
|
154
|
+
*/
|
|
155
|
+
getActiveJobCount() {
|
|
156
|
+
return this.activeJobs.size;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Verifica se è multi-tenant
|
|
160
|
+
*/
|
|
161
|
+
isMultiTenant() {
|
|
162
|
+
return !!this.secretProvider;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.JobQueue = JobQueue;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { WebhookSecretProvider } from '../jobs/progress';
|
|
2
|
+
/**
|
|
3
|
+
* Installazione di un'organizzazione
|
|
4
|
+
*/
|
|
5
|
+
export interface Installation {
|
|
6
|
+
organizationId: string;
|
|
7
|
+
organizationName?: string;
|
|
8
|
+
webhookSecret: string;
|
|
9
|
+
platformUrl: string;
|
|
10
|
+
platformVersion?: string;
|
|
11
|
+
installedAt: Date;
|
|
12
|
+
lastUsed?: Date;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Request per registrare un'installazione
|
|
16
|
+
*/
|
|
17
|
+
export interface RegistrationRequest {
|
|
18
|
+
organizationId: string;
|
|
19
|
+
organizationName?: string;
|
|
20
|
+
platformUrl: string;
|
|
21
|
+
platformVersion?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Response della registrazione
|
|
25
|
+
*/
|
|
26
|
+
export interface RegistrationResponse {
|
|
27
|
+
webhookSecret: string;
|
|
28
|
+
pluginId: string;
|
|
29
|
+
pluginVersion: string;
|
|
30
|
+
message: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Storage interface per le installazioni
|
|
34
|
+
*/
|
|
35
|
+
export interface InstallationStorage {
|
|
36
|
+
get(organizationId: string): Promise<Installation | null>;
|
|
37
|
+
set(organizationId: string, installation: Installation): Promise<void>;
|
|
38
|
+
delete(organizationId: string): Promise<boolean>;
|
|
39
|
+
getAll(): Promise<Installation[]>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* In-memory storage (default)
|
|
43
|
+
* Per produzione usare Redis, Database, etc.
|
|
44
|
+
*/
|
|
45
|
+
export declare class MemoryStorage implements InstallationStorage {
|
|
46
|
+
private storage;
|
|
47
|
+
get(organizationId: string): Promise<Installation | null>;
|
|
48
|
+
set(organizationId: string, installation: Installation): Promise<void>;
|
|
49
|
+
delete(organizationId: string): Promise<boolean>;
|
|
50
|
+
getAll(): Promise<Installation[]>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Registry per gestire installazioni multi-tenant
|
|
54
|
+
*/
|
|
55
|
+
export declare class InstallationRegistry implements WebhookSecretProvider {
|
|
56
|
+
private storage;
|
|
57
|
+
private pluginId;
|
|
58
|
+
private pluginVersion;
|
|
59
|
+
constructor(pluginId: string, pluginVersion: string, storage?: InstallationStorage);
|
|
60
|
+
/**
|
|
61
|
+
* Implementazione WebhookSecretProvider
|
|
62
|
+
* Richiesto dalla JobQueue per supporto multi-tenant
|
|
63
|
+
*/
|
|
64
|
+
getSecret(organizationId: string): Promise<string | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Versione sincrona per backward compatibility
|
|
67
|
+
*/
|
|
68
|
+
getSecretSync(organizationId: string): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Registra una nuova installazione
|
|
71
|
+
*/
|
|
72
|
+
register(req: RegistrationRequest): Promise<RegistrationResponse>;
|
|
73
|
+
/**
|
|
74
|
+
* Ottieni webhook secret per un'organizzazione
|
|
75
|
+
*/
|
|
76
|
+
getWebhookSecret(organizationId: string): Promise<string | null>;
|
|
77
|
+
/**
|
|
78
|
+
* Aggiorna timestamp ultimo uso
|
|
79
|
+
*/
|
|
80
|
+
updateLastUsed(organizationId: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Rimuovi un'installazione
|
|
83
|
+
*/
|
|
84
|
+
unregister(organizationId: string): Promise<boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Ottieni tutte le installazioni
|
|
87
|
+
*/
|
|
88
|
+
getAllInstallations(): Promise<Installation[]>;
|
|
89
|
+
/**
|
|
90
|
+
* Ottieni statistiche
|
|
91
|
+
*/
|
|
92
|
+
getStats(): Promise<{
|
|
93
|
+
totalInstallations: number;
|
|
94
|
+
activeInstallations: number;
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Genera secret sicuro
|
|
98
|
+
*/
|
|
99
|
+
private generateSecret;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=installation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installation.d.ts","sourceRoot":"","sources":["../../src/registry/installation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.InstallationRegistry = exports.MemoryStorage = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
/**
|
|
9
|
+
* In-memory storage (default)
|
|
10
|
+
* Per produzione usare Redis, Database, etc.
|
|
11
|
+
*/
|
|
12
|
+
class MemoryStorage {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.storage = new Map();
|
|
15
|
+
}
|
|
16
|
+
async get(organizationId) {
|
|
17
|
+
return this.storage.get(organizationId) || null;
|
|
18
|
+
}
|
|
19
|
+
async set(organizationId, installation) {
|
|
20
|
+
this.storage.set(organizationId, installation);
|
|
21
|
+
}
|
|
22
|
+
async delete(organizationId) {
|
|
23
|
+
return this.storage.delete(organizationId);
|
|
24
|
+
}
|
|
25
|
+
async getAll() {
|
|
26
|
+
return Array.from(this.storage.values());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.MemoryStorage = MemoryStorage;
|
|
30
|
+
/**
|
|
31
|
+
* Registry per gestire installazioni multi-tenant
|
|
32
|
+
*/
|
|
33
|
+
class InstallationRegistry {
|
|
34
|
+
constructor(pluginId, pluginVersion, storage) {
|
|
35
|
+
this.pluginId = pluginId;
|
|
36
|
+
this.pluginVersion = pluginVersion;
|
|
37
|
+
this.storage = storage || new MemoryStorage();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Implementazione WebhookSecretProvider
|
|
41
|
+
* Richiesto dalla JobQueue per supporto multi-tenant
|
|
42
|
+
*/
|
|
43
|
+
async getSecret(organizationId) {
|
|
44
|
+
return this.getSecretSync(organizationId);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Versione sincrona per backward compatibility
|
|
48
|
+
*/
|
|
49
|
+
getSecretSync(organizationId) {
|
|
50
|
+
// Per MemoryStorage possiamo fare una chiamata sincrona
|
|
51
|
+
const installation = this.storage.storage?.get(organizationId);
|
|
52
|
+
return installation?.webhookSecret || null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Registra una nuova installazione
|
|
56
|
+
*/
|
|
57
|
+
async register(req) {
|
|
58
|
+
const { organizationId, organizationName, platformUrl, platformVersion } = req;
|
|
59
|
+
console.log(`[REGISTRY] Registration request from org: ${organizationId}`);
|
|
60
|
+
// Verifica se già registrato
|
|
61
|
+
const existing = await this.storage.get(organizationId);
|
|
62
|
+
if (existing) {
|
|
63
|
+
console.log(`[REGISTRY] Organization ${organizationId} already registered`);
|
|
64
|
+
return {
|
|
65
|
+
webhookSecret: existing.webhookSecret,
|
|
66
|
+
pluginId: this.pluginId,
|
|
67
|
+
pluginVersion: this.pluginVersion,
|
|
68
|
+
message: 'Organization already registered',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Genera webhook secret univoco
|
|
72
|
+
const webhookSecret = this.generateSecret();
|
|
73
|
+
// Crea installazione
|
|
74
|
+
const installation = {
|
|
75
|
+
organizationId,
|
|
76
|
+
organizationName,
|
|
77
|
+
webhookSecret,
|
|
78
|
+
platformUrl,
|
|
79
|
+
platformVersion,
|
|
80
|
+
installedAt: new Date(),
|
|
81
|
+
};
|
|
82
|
+
// Salva
|
|
83
|
+
await this.storage.set(organizationId, installation);
|
|
84
|
+
console.log(`[REGISTRY] Successfully registered org ${organizationId}`);
|
|
85
|
+
return {
|
|
86
|
+
webhookSecret,
|
|
87
|
+
pluginId: this.pluginId,
|
|
88
|
+
pluginVersion: this.pluginVersion,
|
|
89
|
+
message: 'Organization registered successfully',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Ottieni webhook secret per un'organizzazione
|
|
94
|
+
*/
|
|
95
|
+
async getWebhookSecret(organizationId) {
|
|
96
|
+
const installation = await this.storage.get(organizationId);
|
|
97
|
+
return installation?.webhookSecret || null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Aggiorna timestamp ultimo uso
|
|
101
|
+
*/
|
|
102
|
+
async updateLastUsed(organizationId) {
|
|
103
|
+
const installation = await this.storage.get(organizationId);
|
|
104
|
+
if (installation) {
|
|
105
|
+
installation.lastUsed = new Date();
|
|
106
|
+
await this.storage.set(organizationId, installation);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Rimuovi un'installazione
|
|
111
|
+
*/
|
|
112
|
+
async unregister(organizationId) {
|
|
113
|
+
const deleted = await this.storage.delete(organizationId);
|
|
114
|
+
if (deleted) {
|
|
115
|
+
console.log(`[REGISTRY] Organization ${organizationId} unregistered`);
|
|
116
|
+
}
|
|
117
|
+
return deleted;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Ottieni tutte le installazioni
|
|
121
|
+
*/
|
|
122
|
+
async getAllInstallations() {
|
|
123
|
+
return this.storage.getAll();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Ottieni statistiche
|
|
127
|
+
*/
|
|
128
|
+
async getStats() {
|
|
129
|
+
const all = await this.storage.getAll();
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1000;
|
|
132
|
+
const active = all.filter((inst) => {
|
|
133
|
+
const lastUsed = inst.lastUsed?.getTime() || inst.installedAt.getTime();
|
|
134
|
+
return lastUsed > thirtyDaysAgo;
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
totalInstallations: all.length,
|
|
138
|
+
activeInstallations: active.length,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Genera secret sicuro
|
|
143
|
+
*/
|
|
144
|
+
generateSecret() {
|
|
145
|
+
return `whs_${crypto_1.default.randomBytes(32).toString('hex')}`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.InstallationRegistry = InstallationRegistry;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility per gestire la firma HMAC-SHA256
|
|
3
|
+
*/
|
|
4
|
+
export declare class HMACUtils {
|
|
5
|
+
private secret;
|
|
6
|
+
constructor(secret: string);
|
|
7
|
+
/**
|
|
8
|
+
* Genera signature HMAC-SHA256 per un payload
|
|
9
|
+
*/
|
|
10
|
+
sign(payload: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Verifica una signature HMAC (timing-safe)
|
|
13
|
+
*/
|
|
14
|
+
verify(payload: string, signature: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Genera header signature per webhook
|
|
17
|
+
*/
|
|
18
|
+
generateHeader(payload: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Estrae signature da header
|
|
21
|
+
*/
|
|
22
|
+
static extractSignature(header: string | null): string | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Verifica signature da webhook ricevuto
|
|
26
|
+
*/
|
|
27
|
+
export declare function verifyWebhookSignature(payload: string, signatureHeader: string | null, secret: string): boolean;
|
|
28
|
+
//# sourceMappingURL=hmac.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmac.d.ts","sourceRoot":"","sources":["../../src/security/hmac.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAO1B;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAO7B;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAcnD;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAKvC;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI;CAM9D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,MAAM,EAAE,MAAM,GACb,OAAO,CAMT"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HMACUtils = void 0;
|
|
7
|
+
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
/**
|
|
10
|
+
* Utility per gestire la firma HMAC-SHA256
|
|
11
|
+
*/
|
|
12
|
+
class HMACUtils {
|
|
13
|
+
constructor(secret) {
|
|
14
|
+
if (!secret) {
|
|
15
|
+
throw new Error('HMAC secret is required');
|
|
16
|
+
}
|
|
17
|
+
this.secret = secret;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Genera signature HMAC-SHA256 per un payload
|
|
21
|
+
*/
|
|
22
|
+
sign(payload) {
|
|
23
|
+
return crypto_1.default
|
|
24
|
+
.createHmac('sha256', this.secret)
|
|
25
|
+
.update(payload)
|
|
26
|
+
.digest('hex');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Verifica una signature HMAC (timing-safe)
|
|
30
|
+
*/
|
|
31
|
+
verify(payload, signature) {
|
|
32
|
+
const expected = this.sign(payload);
|
|
33
|
+
try {
|
|
34
|
+
// Timing-safe comparison
|
|
35
|
+
return crypto_1.default.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Genera header signature per webhook
|
|
43
|
+
*/
|
|
44
|
+
generateHeader(payload) {
|
|
45
|
+
const signature = this.sign(payload);
|
|
46
|
+
return `sha256=${signature}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Estrae signature da header
|
|
50
|
+
*/
|
|
51
|
+
static extractSignature(header) {
|
|
52
|
+
if (!header)
|
|
53
|
+
return null;
|
|
54
|
+
const match = header.match(/^sha256=([a-f0-9]+)$/i);
|
|
55
|
+
return match ? match[1] : null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.HMACUtils = HMACUtils;
|
|
59
|
+
/**
|
|
60
|
+
* Verifica signature da webhook ricevuto
|
|
61
|
+
*/
|
|
62
|
+
function verifyWebhookSignature(payload, signatureHeader, secret) {
|
|
63
|
+
const signature = HMACUtils.extractSignature(signatureHeader);
|
|
64
|
+
if (!signature)
|
|
65
|
+
return false;
|
|
66
|
+
const hmac = new HMACUtils(secret);
|
|
67
|
+
return hmac.verify(payload, signature);
|
|
68
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Express } from 'express';
|
|
2
|
+
import type { RelazioPlugin } from '../core/plugin';
|
|
3
|
+
import type { StartOptions } from '../core/types';
|
|
4
|
+
/**
|
|
5
|
+
* Server Express per il plugin
|
|
6
|
+
*/
|
|
7
|
+
export declare class ExpressServer {
|
|
8
|
+
private app;
|
|
9
|
+
private server?;
|
|
10
|
+
private plugin;
|
|
11
|
+
private options;
|
|
12
|
+
constructor(plugin: RelazioPlugin, options: StartOptions);
|
|
13
|
+
/**
|
|
14
|
+
* Setup middleware
|
|
15
|
+
*/
|
|
16
|
+
private setupMiddleware;
|
|
17
|
+
/**
|
|
18
|
+
* Setup routes
|
|
19
|
+
*/
|
|
20
|
+
private setupRoutes;
|
|
21
|
+
/**
|
|
22
|
+
* Handler per transform
|
|
23
|
+
*/
|
|
24
|
+
private handleTransform;
|
|
25
|
+
/**
|
|
26
|
+
* Ottieni base URL del server
|
|
27
|
+
*/
|
|
28
|
+
private getBaseUrl;
|
|
29
|
+
/**
|
|
30
|
+
* Avvia il server
|
|
31
|
+
*/
|
|
32
|
+
start(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Ferma il server
|
|
35
|
+
*/
|
|
36
|
+
stop(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Ottieni Express app (per customizzazione)
|
|
39
|
+
*/
|
|
40
|
+
getApp(): Express;
|
|
41
|
+
}
|
|
42
|
+
export type Server = ExpressServer;
|
|
43
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/server/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,OAAO,EAAmC,MAAM,SAAS,CAAC;AAK5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAuC,MAAM,eAAe,CAAC;AAEvF;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,MAAM,CAAC,CAA2B;IAC1C,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAAe;gBAElB,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY;IAQxD;;OAEG;IACH,OAAO,CAAC,eAAe;IAiCvB;;OAEG;IACH,OAAO,CAAC,WAAW;IAmHnB;;OAEG;YACW,eAAe;IA+D7B;;OAEG;IACH,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B;;OAEG;IACH,MAAM,IAAI,OAAO;CAGlB;AAED,MAAM,MAAM,MAAM,GAAG,aAAa,CAAC"}
|