@j3r3mcdev/oast-server 1.1.6 → 1.1.8
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/.github/workflows/ci.yml +29 -29
- package/.github/workflows/publish.yml +31 -31
- package/README.md +192 -192
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -1
- package/jest.config.js +14 -14
- package/package.json +45 -45
- package/sadmin list shadows +9 -9
- package/src/api/controllers/__tests__/tasks.controller.test.ts +74 -74
- package/src/api/controllers/events.controller.ts +10 -10
- package/src/api/controllers/health.controller.ts +7 -7
- package/src/api/controllers/tasks.controller.ts +41 -41
- package/src/api/dto/__tests__/create-task.dto.test.ts +41 -41
- package/src/api/dto/__tests__/filter-tasks.dto.test.ts +35 -35
- package/src/api/dto/create-task.dto.ts +33 -33
- package/src/api/dto/filter-tasks.dto.ts +33 -33
- package/src/api/services/__tests__/events.service.test.ts +41 -41
- package/src/api/services/__tests__/tasks.service.test.ts +41 -41
- package/src/api/services/events.service.ts +17 -17
- package/src/api/services/tasks.service.ts +79 -79
- package/src/api/sse/events.stream.ts +90 -90
- package/src/bootstrap.ts +89 -89
- package/src/core/__tests__/core-router.test.ts +30 -30
- package/src/core/__tests__/core-server.test.ts +44 -44
- package/src/core/__tests__/event.normalizer.test.ts +56 -56
- package/src/core/__tests__/event.router.test.ts +89 -89
- package/src/core/__tests__/logger.test.ts +32 -32
- package/src/core/__tests__/storage-manager.test.ts +74 -74
- package/src/core/event.normalizer.ts +147 -147
- package/src/core/event.router.ts +13 -13
- package/src/core/http/__tests__/adapter-node.test.ts +52 -52
- package/src/core/http/__tests__/body-parser-multipart.test.ts +41 -41
- package/src/core/http/__tests__/body-parser-raw.test.ts +28 -28
- package/src/core/http/__tests__/body-parser-text.test.ts +28 -28
- package/src/core/http/__tests__/compile-path.test.ts +39 -39
- package/src/core/http/__tests__/middleware-pipeline.test.ts +51 -51
- package/src/core/http/__tests__/request.test.ts +34 -34
- package/src/core/http/__tests__/response.test.ts +35 -35
- package/src/core/http/__tests__/router-match.test.ts +171 -171
- package/src/core/http/adapter-node.ts +51 -51
- package/src/core/http/buildRequest.ts +18 -18
- package/src/core/http/compile-path.ts +32 -32
- package/src/core/http/errors.ts +37 -37
- package/src/core/http/http-server.ts +52 -52
- package/src/core/http/middleware.ts +160 -160
- package/src/core/http/request.ts +55 -55
- package/src/core/http/response.ts +93 -93
- package/src/core/http/router.ts +138 -138
- package/src/core/id-generator.ts +8 -8
- package/src/core/logger.ts +113 -113
- package/src/core/router.ts +44 -44
- package/src/core/server.ts +85 -85
- package/src/core/storage.ts +64 -64
- package/src/index.ts +14 -14
- package/src/listeners/api/__tests__/api.controller.test.ts +116 -116
- package/src/listeners/api/__tests__/api.extractor.test.ts +46 -46
- package/src/listeners/api/__tests__/api.listener.test.ts +82 -82
- package/src/listeners/api/__tests__/api.routes.test.ts +155 -155
- package/src/listeners/api/__tests__/api.sse.test.ts +105 -105
- package/src/listeners/api/api.controllers.ts +67 -67
- package/src/listeners/api/api.extractor.ts +43 -43
- package/src/listeners/api/api.listener.ts +50 -50
- package/src/listeners/api/api.routes.ts +76 -76
- package/src/listeners/api/api.sse.ts +38 -38
- package/src/listeners/dns/__tests__/dns.test.ts +118 -118
- package/src/listeners/dns/dns.extractor.ts +14 -14
- package/src/listeners/dns/dns.listener.ts +61 -61
- package/src/listeners/http/__tests__/http.extractor.test.ts +59 -59
- package/src/listeners/http/__tests__/http.listener.test.ts +133 -133
- package/src/listeners/http/http.extractor.ts +15 -15
- package/src/listeners/http/http.listener.ts +110 -110
- package/src/listeners/listener.interface.ts +4 -4
- package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +69 -69
- package/src/listeners/smtp/__tests__/smtp.listener.test.ts +150 -150
- package/src/listeners/smtp/smtp.extractor.ts +18 -18
- package/src/listeners/smtp/smtp.listener.ts +60 -60
- package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +41 -41
- package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +87 -87
- package/src/listeners/ssrf/ssrf.extractor.ts +14 -14
- package/src/listeners/ssrf/ssrf.listener.ts +37 -37
- package/src/listeners/tcp/tcp.extractor.ts +16 -16
- package/src/listeners/tcp/tcp.listener.ts +61 -61
- package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +35 -35
- package/src/listeners/webhook/__tests__/webhook.listener.test.ts +122 -122
- package/src/listeners/webhook/webhook.extractor.ts +12 -12
- package/src/listeners/webhook/webhook.listener.ts +58 -58
- package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +33 -33
- package/src/listeners/websocket/__tests__/websocket.listener.test.ts +90 -90
- package/src/listeners/websocket/websocket.extractor.ts +11 -11
- package/src/listeners/websocket/websocket.listener.ts +40 -40
- package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +75 -75
- package/src/storage-adapters/adapters/memory.storage.ts +64 -64
- package/src/storage-adapters/storage.interface.ts +26 -26
- package/src/types/event.types.ts +147 -147
- package/tsconfig.json +20 -21
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
import http, { IncomingMessage, ServerResponse } from "http";
|
|
2
|
-
import { Logger } from "../../core/logger";
|
|
3
|
-
import { CoreRouter } from "../../core/router";
|
|
4
|
-
import { EventNormalizer } from "../../core/event.normalizer";
|
|
5
|
-
|
|
6
|
-
export interface HttpListenerOptions {
|
|
7
|
-
port: number;
|
|
8
|
-
logger?: Logger;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class HttpListener {
|
|
12
|
-
private server?: http.Server;
|
|
13
|
-
private logger: Logger;
|
|
14
|
-
|
|
15
|
-
constructor(
|
|
16
|
-
private router: CoreRouter,
|
|
17
|
-
private options: HttpListenerOptions,
|
|
18
|
-
) {
|
|
19
|
-
this.logger = options.logger ?? new Logger({ context: "HttpListener" });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async start(): Promise<void> {
|
|
23
|
-
return new Promise((resolve) => {
|
|
24
|
-
this.server = http.createServer(async (req, res) => {
|
|
25
|
-
await this.handleRequest(req, res);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
this.server.listen(this.options.port, () => {
|
|
29
|
-
this.logger.info(`HTTP Listener started on port ${this.options.port}`);
|
|
30
|
-
resolve();
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async stop(): Promise<void> {
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
if (!this.server) return resolve();
|
|
38
|
-
|
|
39
|
-
this.server.close((err) => {
|
|
40
|
-
if (err) return reject(err);
|
|
41
|
-
this.logger.info("HTTP Listener stopped");
|
|
42
|
-
resolve();
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private async handleRequest(req: IncomingMessage, res: ServerResponse) {
|
|
48
|
-
try {
|
|
49
|
-
const body = await this.readBody(req);
|
|
50
|
-
|
|
51
|
-
const ipHeader = req.headers["x-forwarded-for"];
|
|
52
|
-
const ip =
|
|
53
|
-
typeof ipHeader === "string"
|
|
54
|
-
? ipHeader
|
|
55
|
-
: Array.isArray(ipHeader)
|
|
56
|
-
? ipHeader[0]
|
|
57
|
-
: (req.socket.remoteAddress ?? "");
|
|
58
|
-
|
|
59
|
-
const rawEvent = {
|
|
60
|
-
ip,
|
|
61
|
-
method: req.method ?? "",
|
|
62
|
-
path: req.url ?? "",
|
|
63
|
-
headers: req.headers,
|
|
64
|
-
query: {}, // parsing optionnel
|
|
65
|
-
body,
|
|
66
|
-
raw: req,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const normalized = EventNormalizer.normalizeHttp(rawEvent);
|
|
70
|
-
|
|
71
|
-
await this.router.dispatch(normalized);
|
|
72
|
-
|
|
73
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
74
|
-
res.end(
|
|
75
|
-
JSON.stringify({
|
|
76
|
-
success: true,
|
|
77
|
-
eventId: normalized.id,
|
|
78
|
-
}),
|
|
79
|
-
);
|
|
80
|
-
} catch (err: any) {
|
|
81
|
-
this.logger.error("HTTP Listener error", err);
|
|
82
|
-
|
|
83
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
84
|
-
res.end(
|
|
85
|
-
JSON.stringify({
|
|
86
|
-
success: false,
|
|
87
|
-
error: err.message,
|
|
88
|
-
}),
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private readBody(req: IncomingMessage): Promise<any> {
|
|
94
|
-
return new Promise((resolve) => {
|
|
95
|
-
let data = "";
|
|
96
|
-
|
|
97
|
-
req.on("data", (chunk) => {
|
|
98
|
-
data += chunk;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
req.on("end", () => {
|
|
102
|
-
try {
|
|
103
|
-
resolve(JSON.parse(data || "{}"));
|
|
104
|
-
} catch {
|
|
105
|
-
resolve(data);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
1
|
+
import http, { IncomingMessage, ServerResponse } from "http";
|
|
2
|
+
import { Logger } from "../../core/logger";
|
|
3
|
+
import { CoreRouter } from "../../core/router";
|
|
4
|
+
import { EventNormalizer } from "../../core/event.normalizer";
|
|
5
|
+
|
|
6
|
+
export interface HttpListenerOptions {
|
|
7
|
+
port: number;
|
|
8
|
+
logger?: Logger;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class HttpListener {
|
|
12
|
+
private server?: http.Server;
|
|
13
|
+
private logger: Logger;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private router: CoreRouter,
|
|
17
|
+
private options: HttpListenerOptions,
|
|
18
|
+
) {
|
|
19
|
+
this.logger = options.logger ?? new Logger({ context: "HttpListener" });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async start(): Promise<void> {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
this.server = http.createServer(async (req, res) => {
|
|
25
|
+
await this.handleRequest(req, res);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
this.server.listen(this.options.port, () => {
|
|
29
|
+
this.logger.info(`HTTP Listener started on port ${this.options.port}`);
|
|
30
|
+
resolve();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async stop(): Promise<void> {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
if (!this.server) return resolve();
|
|
38
|
+
|
|
39
|
+
this.server.close((err) => {
|
|
40
|
+
if (err) return reject(err);
|
|
41
|
+
this.logger.info("HTTP Listener stopped");
|
|
42
|
+
resolve();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async handleRequest(req: IncomingMessage, res: ServerResponse) {
|
|
48
|
+
try {
|
|
49
|
+
const body = await this.readBody(req);
|
|
50
|
+
|
|
51
|
+
const ipHeader = req.headers["x-forwarded-for"];
|
|
52
|
+
const ip =
|
|
53
|
+
typeof ipHeader === "string"
|
|
54
|
+
? ipHeader
|
|
55
|
+
: Array.isArray(ipHeader)
|
|
56
|
+
? ipHeader[0]
|
|
57
|
+
: (req.socket.remoteAddress ?? "");
|
|
58
|
+
|
|
59
|
+
const rawEvent = {
|
|
60
|
+
ip,
|
|
61
|
+
method: req.method ?? "",
|
|
62
|
+
path: req.url ?? "",
|
|
63
|
+
headers: req.headers,
|
|
64
|
+
query: {}, // parsing optionnel
|
|
65
|
+
body,
|
|
66
|
+
raw: req,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const normalized = EventNormalizer.normalizeHttp(rawEvent);
|
|
70
|
+
|
|
71
|
+
await this.router.dispatch(normalized);
|
|
72
|
+
|
|
73
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
74
|
+
res.end(
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
success: true,
|
|
77
|
+
eventId: normalized.id,
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
} catch (err: any) {
|
|
81
|
+
this.logger.error("HTTP Listener error", err);
|
|
82
|
+
|
|
83
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
84
|
+
res.end(
|
|
85
|
+
JSON.stringify({
|
|
86
|
+
success: false,
|
|
87
|
+
error: err.message,
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private readBody(req: IncomingMessage): Promise<any> {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
let data = "";
|
|
96
|
+
|
|
97
|
+
req.on("data", (chunk) => {
|
|
98
|
+
data += chunk;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
req.on("end", () => {
|
|
102
|
+
try {
|
|
103
|
+
resolve(JSON.parse(data || "{}"));
|
|
104
|
+
} catch {
|
|
105
|
+
resolve(data);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface Listener {
|
|
2
|
-
start(): Promise<void>;
|
|
3
|
-
stop(): Promise<void>;
|
|
4
|
-
}
|
|
1
|
+
export interface Listener {
|
|
2
|
+
start(): Promise<void>;
|
|
3
|
+
stop(): Promise<void>;
|
|
4
|
+
}
|
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import { describe, it, expect } from "@jest/globals";
|
|
2
|
-
import { SmtpExtractor } from "../smtp.extractor";
|
|
3
|
-
|
|
4
|
-
describe("SmtpExtractor", () => {
|
|
5
|
-
it("extrait correctement un RawSmtpEvent valide", () => {
|
|
6
|
-
const session = {
|
|
7
|
-
remoteAddress: "1.2.3.4",
|
|
8
|
-
envelope: {
|
|
9
|
-
mailFrom: { address: "attacker@example.com" },
|
|
10
|
-
rcptTo: [{ address: "victim@example.com" }],
|
|
11
|
-
},
|
|
12
|
-
headers: { subject: "Hello World" },
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const body = "This is the email body";
|
|
16
|
-
|
|
17
|
-
const result = SmtpExtractor.extract(session, body);
|
|
18
|
-
|
|
19
|
-
expect(result).toEqual({
|
|
20
|
-
ip: "1.2.3.4",
|
|
21
|
-
from: "attacker@example.com",
|
|
22
|
-
to: "victim@example.com",
|
|
23
|
-
subject: "Hello World",
|
|
24
|
-
raw: {
|
|
25
|
-
session,
|
|
26
|
-
body,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("gère les champs manquants proprement", () => {
|
|
32
|
-
const session = {
|
|
33
|
-
remoteAddress: undefined,
|
|
34
|
-
envelope: {
|
|
35
|
-
mailFrom: {},
|
|
36
|
-
rcptTo: [{}],
|
|
37
|
-
},
|
|
38
|
-
headers: {},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const result = SmtpExtractor.extract(session, "");
|
|
42
|
-
|
|
43
|
-
expect(result).toEqual({
|
|
44
|
-
ip: "",
|
|
45
|
-
from: "",
|
|
46
|
-
to: "",
|
|
47
|
-
subject: "",
|
|
48
|
-
raw: {
|
|
49
|
-
session,
|
|
50
|
-
body: "",
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("ne plante pas si session est vide", () => {
|
|
56
|
-
const result = SmtpExtractor.extract({}, "BODY");
|
|
57
|
-
|
|
58
|
-
expect(result).toEqual({
|
|
59
|
-
ip: "",
|
|
60
|
-
from: "",
|
|
61
|
-
to: "",
|
|
62
|
-
subject: "",
|
|
63
|
-
raw: {
|
|
64
|
-
session: {},
|
|
65
|
-
body: "BODY",
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
});
|
|
1
|
+
import { describe, it, expect } from "@jest/globals";
|
|
2
|
+
import { SmtpExtractor } from "../smtp.extractor";
|
|
3
|
+
|
|
4
|
+
describe("SmtpExtractor", () => {
|
|
5
|
+
it("extrait correctement un RawSmtpEvent valide", () => {
|
|
6
|
+
const session = {
|
|
7
|
+
remoteAddress: "1.2.3.4",
|
|
8
|
+
envelope: {
|
|
9
|
+
mailFrom: { address: "attacker@example.com" },
|
|
10
|
+
rcptTo: [{ address: "victim@example.com" }],
|
|
11
|
+
},
|
|
12
|
+
headers: { subject: "Hello World" },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const body = "This is the email body";
|
|
16
|
+
|
|
17
|
+
const result = SmtpExtractor.extract(session, body);
|
|
18
|
+
|
|
19
|
+
expect(result).toEqual({
|
|
20
|
+
ip: "1.2.3.4",
|
|
21
|
+
from: "attacker@example.com",
|
|
22
|
+
to: "victim@example.com",
|
|
23
|
+
subject: "Hello World",
|
|
24
|
+
raw: {
|
|
25
|
+
session,
|
|
26
|
+
body,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("gère les champs manquants proprement", () => {
|
|
32
|
+
const session = {
|
|
33
|
+
remoteAddress: undefined,
|
|
34
|
+
envelope: {
|
|
35
|
+
mailFrom: {},
|
|
36
|
+
rcptTo: [{}],
|
|
37
|
+
},
|
|
38
|
+
headers: {},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const result = SmtpExtractor.extract(session, "");
|
|
42
|
+
|
|
43
|
+
expect(result).toEqual({
|
|
44
|
+
ip: "",
|
|
45
|
+
from: "",
|
|
46
|
+
to: "",
|
|
47
|
+
subject: "",
|
|
48
|
+
raw: {
|
|
49
|
+
session,
|
|
50
|
+
body: "",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("ne plante pas si session est vide", () => {
|
|
56
|
+
const result = SmtpExtractor.extract({}, "BODY");
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual({
|
|
59
|
+
ip: "",
|
|
60
|
+
from: "",
|
|
61
|
+
to: "",
|
|
62
|
+
subject: "",
|
|
63
|
+
raw: {
|
|
64
|
+
session: {},
|
|
65
|
+
body: "BODY",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
import { describe, it, expect, jest, beforeEach } from "@jest/globals";
|
|
2
|
-
import { SmtpListener } from "../smtp.listener";
|
|
3
|
-
import { EventNormalizer } from "../../../core/event.normalizer";
|
|
4
|
-
import { CoreRouter } from "../../../core/router";
|
|
5
|
-
|
|
6
|
-
// Mock EventNormalizer
|
|
7
|
-
jest.mock("../../../core/event.normalizer", () => ({
|
|
8
|
-
EventNormalizer: {
|
|
9
|
-
normalizeSmtp: jest.fn(),
|
|
10
|
-
},
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
describe("SmtpListener", () => {
|
|
14
|
-
let router: CoreRouter;
|
|
15
|
-
let server: any;
|
|
16
|
-
let listener: SmtpListener;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
router = {
|
|
20
|
-
dispatch: jest.fn(async (_event: any) => {}),
|
|
21
|
-
} as any;
|
|
22
|
-
|
|
23
|
-
server = {
|
|
24
|
-
onData: null,
|
|
25
|
-
close: jest.fn(),
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
listener = new SmtpListener(router, { server });
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("traite un email SMTP valide et appelle dispatch", async () => {
|
|
32
|
-
const callback = jest.fn();
|
|
33
|
-
|
|
34
|
-
// NormalizedSmtpEvent VALIDE
|
|
35
|
-
(EventNormalizer.normalizeSmtp as jest.Mock).mockReturnValue({
|
|
36
|
-
id: "smtp-123",
|
|
37
|
-
type: "smtp",
|
|
38
|
-
timestamp: Date.now(),
|
|
39
|
-
ip: "1.2.3.4",
|
|
40
|
-
from: "attacker@example.com",
|
|
41
|
-
to: ["victim@example.com"],
|
|
42
|
-
subject: "Test Email",
|
|
43
|
-
body: "Hello world",
|
|
44
|
-
raw: {},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
await listener.start();
|
|
48
|
-
|
|
49
|
-
const stream = {
|
|
50
|
-
on: jest.fn((event: string, handler: (chunk?: any) => void) => {
|
|
51
|
-
if (event === "data") handler("Hello world");
|
|
52
|
-
if (event === "end") handler();
|
|
53
|
-
}),
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const session = {
|
|
57
|
-
remoteAddress: "1.2.3.4",
|
|
58
|
-
envelope: {
|
|
59
|
-
mailFrom: { address: "attacker@example.com" },
|
|
60
|
-
rcptTo: [{ address: "victim@example.com" }],
|
|
61
|
-
},
|
|
62
|
-
headers: { subject: "Test Email" },
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
await server.onData(stream, session, callback);
|
|
66
|
-
|
|
67
|
-
expect(EventNormalizer.normalizeSmtp).toHaveBeenCalled();
|
|
68
|
-
expect(router.dispatch).toHaveBeenCalled();
|
|
69
|
-
expect(callback).toHaveBeenCalledWith(null);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("renvoie une erreur si normalizeSmtp échoue", async () => {
|
|
73
|
-
const callback = jest.fn();
|
|
74
|
-
|
|
75
|
-
(EventNormalizer.normalizeSmtp as jest.Mock).mockImplementation(() => {
|
|
76
|
-
throw new Error("bad smtp");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
await listener.start();
|
|
80
|
-
|
|
81
|
-
const stream = {
|
|
82
|
-
on: jest.fn((event: string, handler: (chunk?: any) => void) => {
|
|
83
|
-
if (event === "data") handler("DATA");
|
|
84
|
-
if (event === "end") handler();
|
|
85
|
-
}),
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const session = {
|
|
89
|
-
remoteAddress: "1.2.3.4",
|
|
90
|
-
envelope: {
|
|
91
|
-
mailFrom: { address: "a@b.com" },
|
|
92
|
-
rcptTo: [{ address: "c@d.com" }],
|
|
93
|
-
},
|
|
94
|
-
headers: { subject: "X" },
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
await server.onData(stream, session, callback);
|
|
98
|
-
|
|
99
|
-
expect(callback).toHaveBeenCalledWith(new Error("bad smtp"));
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("renvoie une erreur si router.dispatch échoue", async () => {
|
|
103
|
-
const callback = jest.fn();
|
|
104
|
-
|
|
105
|
-
(EventNormalizer.normalizeSmtp as jest.Mock).mockReturnValue({
|
|
106
|
-
id: "smtp-999",
|
|
107
|
-
type: "smtp",
|
|
108
|
-
timestamp: Date.now(),
|
|
109
|
-
ip: "1.2.3.4",
|
|
110
|
-
from: "a@b.com",
|
|
111
|
-
to: ["c@d.com"],
|
|
112
|
-
subject: "X",
|
|
113
|
-
body: "DATA",
|
|
114
|
-
raw: {},
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
listener["router"] = {
|
|
118
|
-
dispatch: jest.fn(async () => {
|
|
119
|
-
throw new Error("dispatch failed");
|
|
120
|
-
}),
|
|
121
|
-
} as any;
|
|
122
|
-
|
|
123
|
-
await listener.start();
|
|
124
|
-
|
|
125
|
-
const stream = {
|
|
126
|
-
on: jest.fn((event: string, handler: (chunk?: any) => void) => {
|
|
127
|
-
if (event === "data") handler("DATA");
|
|
128
|
-
if (event === "end") handler();
|
|
129
|
-
}),
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const session = {
|
|
133
|
-
remoteAddress: "1.2.3.4",
|
|
134
|
-
envelope: {
|
|
135
|
-
mailFrom: { address: "a@b.com" },
|
|
136
|
-
rcptTo: [{ address: "c@d.com" }],
|
|
137
|
-
},
|
|
138
|
-
headers: { subject: "X" },
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
await server.onData(stream, session, callback);
|
|
142
|
-
|
|
143
|
-
expect(callback).toHaveBeenCalledWith(new Error("dispatch failed"));
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("stop() appelle server.close si disponible", async () => {
|
|
147
|
-
await listener.stop();
|
|
148
|
-
expect(server.close).toHaveBeenCalled();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
1
|
+
import { describe, it, expect, jest, beforeEach } from "@jest/globals";
|
|
2
|
+
import { SmtpListener } from "../smtp.listener";
|
|
3
|
+
import { EventNormalizer } from "../../../core/event.normalizer";
|
|
4
|
+
import { CoreRouter } from "../../../core/router";
|
|
5
|
+
|
|
6
|
+
// Mock EventNormalizer
|
|
7
|
+
jest.mock("../../../core/event.normalizer", () => ({
|
|
8
|
+
EventNormalizer: {
|
|
9
|
+
normalizeSmtp: jest.fn(),
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe("SmtpListener", () => {
|
|
14
|
+
let router: CoreRouter;
|
|
15
|
+
let server: any;
|
|
16
|
+
let listener: SmtpListener;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
router = {
|
|
20
|
+
dispatch: jest.fn(async (_event: any) => {}),
|
|
21
|
+
} as any;
|
|
22
|
+
|
|
23
|
+
server = {
|
|
24
|
+
onData: null,
|
|
25
|
+
close: jest.fn(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
listener = new SmtpListener(router, { server });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("traite un email SMTP valide et appelle dispatch", async () => {
|
|
32
|
+
const callback = jest.fn();
|
|
33
|
+
|
|
34
|
+
// NormalizedSmtpEvent VALIDE
|
|
35
|
+
(EventNormalizer.normalizeSmtp as jest.Mock).mockReturnValue({
|
|
36
|
+
id: "smtp-123",
|
|
37
|
+
type: "smtp",
|
|
38
|
+
timestamp: Date.now(),
|
|
39
|
+
ip: "1.2.3.4",
|
|
40
|
+
from: "attacker@example.com",
|
|
41
|
+
to: ["victim@example.com"],
|
|
42
|
+
subject: "Test Email",
|
|
43
|
+
body: "Hello world",
|
|
44
|
+
raw: {},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await listener.start();
|
|
48
|
+
|
|
49
|
+
const stream = {
|
|
50
|
+
on: jest.fn((event: string, handler: (chunk?: any) => void) => {
|
|
51
|
+
if (event === "data") handler("Hello world");
|
|
52
|
+
if (event === "end") handler();
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const session = {
|
|
57
|
+
remoteAddress: "1.2.3.4",
|
|
58
|
+
envelope: {
|
|
59
|
+
mailFrom: { address: "attacker@example.com" },
|
|
60
|
+
rcptTo: [{ address: "victim@example.com" }],
|
|
61
|
+
},
|
|
62
|
+
headers: { subject: "Test Email" },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await server.onData(stream, session, callback);
|
|
66
|
+
|
|
67
|
+
expect(EventNormalizer.normalizeSmtp).toHaveBeenCalled();
|
|
68
|
+
expect(router.dispatch).toHaveBeenCalled();
|
|
69
|
+
expect(callback).toHaveBeenCalledWith(null);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("renvoie une erreur si normalizeSmtp échoue", async () => {
|
|
73
|
+
const callback = jest.fn();
|
|
74
|
+
|
|
75
|
+
(EventNormalizer.normalizeSmtp as jest.Mock).mockImplementation(() => {
|
|
76
|
+
throw new Error("bad smtp");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await listener.start();
|
|
80
|
+
|
|
81
|
+
const stream = {
|
|
82
|
+
on: jest.fn((event: string, handler: (chunk?: any) => void) => {
|
|
83
|
+
if (event === "data") handler("DATA");
|
|
84
|
+
if (event === "end") handler();
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const session = {
|
|
89
|
+
remoteAddress: "1.2.3.4",
|
|
90
|
+
envelope: {
|
|
91
|
+
mailFrom: { address: "a@b.com" },
|
|
92
|
+
rcptTo: [{ address: "c@d.com" }],
|
|
93
|
+
},
|
|
94
|
+
headers: { subject: "X" },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
await server.onData(stream, session, callback);
|
|
98
|
+
|
|
99
|
+
expect(callback).toHaveBeenCalledWith(new Error("bad smtp"));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("renvoie une erreur si router.dispatch échoue", async () => {
|
|
103
|
+
const callback = jest.fn();
|
|
104
|
+
|
|
105
|
+
(EventNormalizer.normalizeSmtp as jest.Mock).mockReturnValue({
|
|
106
|
+
id: "smtp-999",
|
|
107
|
+
type: "smtp",
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
ip: "1.2.3.4",
|
|
110
|
+
from: "a@b.com",
|
|
111
|
+
to: ["c@d.com"],
|
|
112
|
+
subject: "X",
|
|
113
|
+
body: "DATA",
|
|
114
|
+
raw: {},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
listener["router"] = {
|
|
118
|
+
dispatch: jest.fn(async () => {
|
|
119
|
+
throw new Error("dispatch failed");
|
|
120
|
+
}),
|
|
121
|
+
} as any;
|
|
122
|
+
|
|
123
|
+
await listener.start();
|
|
124
|
+
|
|
125
|
+
const stream = {
|
|
126
|
+
on: jest.fn((event: string, handler: (chunk?: any) => void) => {
|
|
127
|
+
if (event === "data") handler("DATA");
|
|
128
|
+
if (event === "end") handler();
|
|
129
|
+
}),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const session = {
|
|
133
|
+
remoteAddress: "1.2.3.4",
|
|
134
|
+
envelope: {
|
|
135
|
+
mailFrom: { address: "a@b.com" },
|
|
136
|
+
rcptTo: [{ address: "c@d.com" }],
|
|
137
|
+
},
|
|
138
|
+
headers: { subject: "X" },
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
await server.onData(stream, session, callback);
|
|
142
|
+
|
|
143
|
+
expect(callback).toHaveBeenCalledWith(new Error("dispatch failed"));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("stop() appelle server.close si disponible", async () => {
|
|
147
|
+
await listener.stop();
|
|
148
|
+
expect(server.close).toHaveBeenCalled();
|
|
149
|
+
});
|
|
150
|
+
});
|