@anomira/node-sdk 0.1.2 → 0.1.4
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/README.md +56 -20
- package/dist/index.cjs +30 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,43 +10,60 @@ npm install @anomira/node-sdk
|
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
-
```
|
|
13
|
+
```js
|
|
14
14
|
import { SentinelAPI } from "@anomira/node-sdk";
|
|
15
15
|
|
|
16
16
|
const sentinel = new SentinelAPI({
|
|
17
|
-
apiKey:
|
|
18
|
-
appId:
|
|
17
|
+
apiKey: process.env.SENTINEL_API_KEY,
|
|
18
|
+
appId: process.env.SENTINEL_APP_ID,
|
|
19
|
+
ingestUrl: process.env.SENTINEL_INGEST_URL,
|
|
20
|
+
debug: true,
|
|
19
21
|
});
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
## Express
|
|
23
25
|
|
|
24
|
-
```
|
|
26
|
+
```js
|
|
25
27
|
import express from "express";
|
|
26
28
|
import { SentinelAPI } from "@anomira/node-sdk";
|
|
27
29
|
|
|
28
30
|
const app = express();
|
|
31
|
+
|
|
29
32
|
const sentinel = new SentinelAPI({
|
|
30
|
-
apiKey:
|
|
31
|
-
appId:
|
|
33
|
+
apiKey: process.env.SENTINEL_API_KEY,
|
|
34
|
+
appId: process.env.SENTINEL_APP_ID,
|
|
35
|
+
ingestUrl: process.env.SENTINEL_INGEST_URL,
|
|
36
|
+
debug: true,
|
|
37
|
+
captureConsole: true, // forwards console.log/warn/error to Logs dashboard
|
|
38
|
+
service: "my-api",
|
|
39
|
+
detect: {
|
|
40
|
+
bruteForce: true,
|
|
41
|
+
rateAbuse: true,
|
|
42
|
+
pathTraversal: true,
|
|
43
|
+
xss: true,
|
|
44
|
+
scanDetection: true,
|
|
45
|
+
geoVelocity: true,
|
|
46
|
+
},
|
|
32
47
|
});
|
|
33
48
|
|
|
34
|
-
|
|
35
|
-
app.use(sentinel.express());
|
|
49
|
+
app.use(express.json());
|
|
50
|
+
app.use(sentinel.express()); // auto-instruments all routes
|
|
36
51
|
|
|
37
52
|
app.listen(3000);
|
|
38
53
|
```
|
|
39
54
|
|
|
40
55
|
## Fastify
|
|
41
56
|
|
|
42
|
-
```
|
|
57
|
+
```js
|
|
43
58
|
import Fastify from "fastify";
|
|
44
59
|
import { SentinelAPI } from "@anomira/node-sdk";
|
|
45
60
|
|
|
46
61
|
const app = Fastify();
|
|
62
|
+
|
|
47
63
|
const sentinel = new SentinelAPI({
|
|
48
|
-
apiKey:
|
|
49
|
-
appId:
|
|
64
|
+
apiKey: process.env.SENTINEL_API_KEY,
|
|
65
|
+
appId: process.env.SENTINEL_APP_ID,
|
|
66
|
+
ingestUrl: process.env.SENTINEL_INGEST_URL,
|
|
50
67
|
});
|
|
51
68
|
|
|
52
69
|
await app.register(sentinel.fastify());
|
|
@@ -56,7 +73,7 @@ app.listen({ port: 3000 });
|
|
|
56
73
|
|
|
57
74
|
## Manual Event Tracking
|
|
58
75
|
|
|
59
|
-
```
|
|
76
|
+
```js
|
|
60
77
|
// Track a failed OTP attempt
|
|
61
78
|
sentinel.track("auth.otp.failed", {
|
|
62
79
|
ip: req.ip,
|
|
@@ -73,17 +90,17 @@ sentinel.trackPhoneAuth({ ip: req.ip, userId: user.id, phone: user.phone });
|
|
|
73
90
|
|
|
74
91
|
## Structured Logging
|
|
75
92
|
|
|
76
|
-
```
|
|
77
|
-
sentinel.log("info", "User registered",
|
|
78
|
-
sentinel.log("warn", "Slow DB query",
|
|
79
|
-
sentinel.log("error", "Payment failed",
|
|
93
|
+
```js
|
|
94
|
+
sentinel.log("info", "User registered", { userId: user.id });
|
|
95
|
+
sentinel.log("warn", "Slow DB query", { queryMs: 1240 });
|
|
96
|
+
sentinel.log("error", "Payment failed", { reason: err.message });
|
|
80
97
|
```
|
|
81
98
|
|
|
82
99
|
## Blocklist & Firewall
|
|
83
100
|
|
|
84
|
-
The SDK syncs your blocklist and firewall rules from the dashboard every 60 seconds.
|
|
101
|
+
The SDK syncs your blocklist and firewall rules from the dashboard every 60 seconds. Check them in your own middleware:
|
|
85
102
|
|
|
86
|
-
```
|
|
103
|
+
```js
|
|
87
104
|
if (sentinel.isBlocked(req.ip)) {
|
|
88
105
|
return res.status(403).json({ error: "Forbidden" });
|
|
89
106
|
}
|
|
@@ -91,7 +108,7 @@ if (sentinel.isBlocked(req.ip)) {
|
|
|
91
108
|
const match = sentinel.matchFirewallRule({
|
|
92
109
|
url: req.url,
|
|
93
110
|
body: req.body,
|
|
94
|
-
headers: req.headers
|
|
111
|
+
headers: req.headers,
|
|
95
112
|
ip: req.ip,
|
|
96
113
|
});
|
|
97
114
|
if (match?.rule.action === "block") {
|
|
@@ -109,13 +126,21 @@ npx anomira scan ./src
|
|
|
109
126
|
|
|
110
127
|
Exit code `1` if violations are found — CI/CD compatible.
|
|
111
128
|
|
|
129
|
+
## Environment Variables
|
|
130
|
+
|
|
131
|
+
| Variable | Description |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `SENTINEL_API_KEY` | Your Anomira API key |
|
|
134
|
+
| `SENTINEL_APP_ID` | Your Anomira app ID |
|
|
135
|
+
| `SENTINEL_INGEST_URL` | Ingest endpoint (from your dashboard) |
|
|
136
|
+
|
|
112
137
|
## Configuration
|
|
113
138
|
|
|
114
139
|
| Option | Type | Default | Description |
|
|
115
140
|
|---|---|---|---|
|
|
116
141
|
| `apiKey` | `string` | — | Your Anomira API key (required) |
|
|
117
142
|
| `appId` | `string` | — | Your Anomira app ID (required) |
|
|
118
|
-
| `ingestUrl` | `string` | Anomira cloud |
|
|
143
|
+
| `ingestUrl` | `string` | Anomira cloud | Ingest endpoint URL |
|
|
119
144
|
| `debug` | `boolean` | `false` | Log SDK activity to console |
|
|
120
145
|
| `captureConsole` | `boolean` | `false` | Forward `console.*` calls to the Logs dashboard |
|
|
121
146
|
| `service` | `string` | `"app"` | Service name tag for logs |
|
|
@@ -126,6 +151,17 @@ Exit code `1` if violations are found — CI/CD compatible.
|
|
|
126
151
|
| `detect.scanDetection` | `boolean` | `true` | Detect scanner/bot probing |
|
|
127
152
|
| `detect.geoVelocity` | `boolean` | `true` | Detect impossible travel between logins |
|
|
128
153
|
|
|
154
|
+
## Graceful Shutdown
|
|
155
|
+
|
|
156
|
+
Always flush pending events before your process exits:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
process.on("SIGTERM", async () => {
|
|
160
|
+
await sentinel.flush();
|
|
161
|
+
process.exit(0);
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
129
165
|
## License
|
|
130
166
|
|
|
131
167
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -297,6 +297,8 @@ var SentinelClient = class {
|
|
|
297
297
|
this.logFlushTimer = null;
|
|
298
298
|
this.blocklistTimer = null;
|
|
299
299
|
this.firewallTimer = null;
|
|
300
|
+
/** True when credentials are missing — all operations become no-ops. */
|
|
301
|
+
this.disabled = false;
|
|
300
302
|
/** In-process cache of blocked IPs — refreshed every 60 s from the ingest server. */
|
|
301
303
|
this.blockedIpCache = /* @__PURE__ */ new Set();
|
|
302
304
|
/** In-process cache of firewall rules with pre-compiled regex — refreshed every 60 s. */
|
|
@@ -305,8 +307,28 @@ var SentinelClient = class {
|
|
|
305
307
|
this._origLog = console.log.bind(console);
|
|
306
308
|
this._origWarn = console.warn.bind(console);
|
|
307
309
|
this._origError = console.error.bind(console);
|
|
308
|
-
if (!config.apiKey
|
|
309
|
-
|
|
310
|
+
if (!config.apiKey || !config.appId) {
|
|
311
|
+
const missing = [!config.apiKey && "apiKey", !config.appId && "appId"].filter(Boolean).join(", ");
|
|
312
|
+
console.warn(`[Anomira] SDK disabled \u2014 missing config: ${missing}. Set SENTINEL_API_KEY and SENTINEL_APP_ID to enable monitoring.`);
|
|
313
|
+
this.disabled = true;
|
|
314
|
+
this.config = {
|
|
315
|
+
apiKey: "",
|
|
316
|
+
appId: "",
|
|
317
|
+
ingestUrl: DEFAULT_INGEST_URL,
|
|
318
|
+
geoLookupUrl: "",
|
|
319
|
+
maxBatchSize: DEFAULT_BATCH_SIZE,
|
|
320
|
+
flushIntervalMs: DEFAULT_FLUSH_MS,
|
|
321
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
322
|
+
debug: false,
|
|
323
|
+
captureConsole: false,
|
|
324
|
+
service: "app",
|
|
325
|
+
getUserId: defaultGetUserId,
|
|
326
|
+
getIp: defaultGetIp,
|
|
327
|
+
detect: { bruteForce: true, rateAbuse: true, pathTraversal: true, xss: true, scanDetection: true, geoVelocity: true }
|
|
328
|
+
};
|
|
329
|
+
this.buffer = new EventBuffer({ appId: "", apiKey: "", ingestUrl: DEFAULT_INGEST_URL, maxBatchSize: 0, flushIntervalMs: 999999999, maxRetries: 0, debug: false });
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
310
332
|
this.config = {
|
|
311
333
|
apiKey: config.apiKey,
|
|
312
334
|
appId: config.appId,
|
|
@@ -525,6 +547,7 @@ var SentinelClient = class {
|
|
|
525
547
|
*/
|
|
526
548
|
/** Returns true if the IP is in the current blocked list. Synchronous — no network call. */
|
|
527
549
|
isBlocked(ip) {
|
|
550
|
+
if (this.disabled) return false;
|
|
528
551
|
return this.blockedIpCache.has(ip);
|
|
529
552
|
}
|
|
530
553
|
/** Evaluate firewall rules against a request. Returns the matched rule or null. Synchronous. */
|
|
@@ -532,6 +555,7 @@ var SentinelClient = class {
|
|
|
532
555
|
return this.#matchFirewallRule(req);
|
|
533
556
|
}
|
|
534
557
|
track(eventName, data) {
|
|
558
|
+
if (this.disabled) return;
|
|
535
559
|
const event = {
|
|
536
560
|
name: eventName,
|
|
537
561
|
ts: Date.now(),
|
|
@@ -554,6 +578,7 @@ var SentinelClient = class {
|
|
|
554
578
|
* ```
|
|
555
579
|
*/
|
|
556
580
|
async trackLogin(data) {
|
|
581
|
+
if (this.disabled) return;
|
|
557
582
|
const tsMs = Date.now();
|
|
558
583
|
this.track(EventName.LOGIN_SUCCESS, { ...data, meta: { ...data.meta } });
|
|
559
584
|
if (!this.config.detect.geoVelocity) return;
|
|
@@ -593,6 +618,7 @@ var SentinelClient = class {
|
|
|
593
618
|
* ```
|
|
594
619
|
*/
|
|
595
620
|
trackPhoneAuth(data) {
|
|
621
|
+
if (this.disabled) return;
|
|
596
622
|
this.track(EventName.PHONE_AUTH, {
|
|
597
623
|
ip: data.ip,
|
|
598
624
|
userId: data.userId,
|
|
@@ -609,6 +635,7 @@ var SentinelClient = class {
|
|
|
609
635
|
* ```
|
|
610
636
|
*/
|
|
611
637
|
log(level, message, meta) {
|
|
638
|
+
if (this.disabled) return;
|
|
612
639
|
const { service, ...rest } = meta ?? {};
|
|
613
640
|
const leaks = scanForLeaks(message);
|
|
614
641
|
if (leaks.length > 0) {
|
|
@@ -630,6 +657,7 @@ var SentinelClient = class {
|
|
|
630
657
|
* Useful before a graceful shutdown outside of the process lifecycle hooks.
|
|
631
658
|
*/
|
|
632
659
|
async flush() {
|
|
660
|
+
if (this.disabled) return;
|
|
633
661
|
await Promise.all([this.buffer.flush(), this.#flushLogs()]);
|
|
634
662
|
}
|
|
635
663
|
/**
|