@botbye/nest-express 2.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BotBye!
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,428 @@
1
+ # @botbye/nest-express
2
+
3
+ [BotBye!](https://botbye.com) integration for [NestJS](https://nestjs.com/) with [Express](https://expressjs.com/) applications.
4
+
5
+ Full documentation: https://botbye.com/docs/server-side/node-js/nestjs/express
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i @botbye/nest-express
11
+ ```
12
+
13
+ ```bash
14
+ yarn add @botbye/nest-express
15
+ ```
16
+
17
+ Requires `@nestjs/common >= 10` and `express >= 4` as peer dependencies.
18
+
19
+ ## Configuration
20
+
21
+ Register `BotByeModule` once in your root module:
22
+
23
+ ```typescript
24
+ import { Module } from "@nestjs/common";
25
+ import { BotByeModule } from "@botbye/nest-express";
26
+
27
+ @Module({
28
+ imports: [
29
+ BotByeModule.register({
30
+ // Use your project server-key
31
+ serverKey: "00000000-0000-0000-0000-000000000000",
32
+ }),
33
+ ],
34
+ })
35
+ export class AppModule {}
36
+ ```
37
+
38
+ ### `init` options
39
+
40
+ | Option | Type | Required | Description |
41
+ |---|---|---|---|
42
+ | `serverKey` | `string` | Yes | Server key from your BotBye project |
43
+ | `url` | `string` | No | Override BotBye API endpoint (default: `https://verify.botbye.com`) |
44
+ | `logger.level` | `"error" \| "warn" \| "info" \| "debug" \| "log"` | No | Log level (default: `"info"`) |
45
+ | `logger.logger` | `TLogger` | No | Custom logger instance implementing `{ error, warn, info, debug, log }` |
46
+ | `timeouts.evaluate` | `number` | No | Timeout in milliseconds for each `evaluate` call |
47
+ | `tokenExtractor` | `(request: Request) => string \| null \| undefined` | No* | Extracts the BotBye token from the Express request. *Required when using `BotByeMiddleware` — without it the middleware skips evaluation and returns an error result. |
48
+
49
+ ## Usage
50
+
51
+ After registering `BotByeModule`, inject `TBotByeService` using `BOTBYE_SERVICE_DI_TOKEN` to call `evaluate` from guards, controllers, or services.
52
+
53
+ ```typescript
54
+ import { Controller, Post, Inject, Req, ForbiddenException } from "@nestjs/common";
55
+ import { Request } from "express";
56
+ import { BOTBYE_SERVICE_DI_TOKEN, TBotByeService } from "@botbye/nest-express";
57
+
58
+ @Controller()
59
+ export class AppController {
60
+ constructor(
61
+ @Inject(BOTBYE_SERVICE_DI_TOKEN) private readonly botbye: TBotByeService
62
+ ) {}
63
+
64
+ @Post("/api/submit")
65
+ async submit(@Req() req: Request) {
66
+ const result = await this.botbye.evaluate({
67
+ type: "validate",
68
+ request: {
69
+ request: req,
70
+ // "x-botbye-token" is an example — pass the token from wherever you store it
71
+ token: req.headers["x-botbye-token"] as string | null,
72
+ },
73
+ });
74
+
75
+ if (result.decision === "BLOCK") {
76
+ throw new ForbiddenException();
77
+ }
78
+
79
+ // proceed normally
80
+ }
81
+ }
82
+ ```
83
+
84
+ There are three event types — `validate`, `risk`, and `full` — each suited for a different layer of your application.
85
+
86
+ ---
87
+
88
+ ### `validate` — edge-level bot check
89
+
90
+ Use at the edge — guard, route handler, middleware — when you just want to know: **was this request made by a bot?** No user or domain context needed.
91
+
92
+ **Event fields:**
93
+
94
+ ```typescript
95
+ {
96
+ type: "validate";
97
+
98
+ request:
99
+ // Option A: pass the Express request object directly — SDK extracts everything automatically
100
+ | { request: Request; token?: string | null }
101
+ // Option B: construct request info manually
102
+ | { ip: string; headers: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null };
103
+
104
+ customFields?: Record<string, string>;
105
+
106
+ config?: { bypassBotValidation?: boolean | null };
107
+ }
108
+ ```
109
+
110
+ The SDK extracts IP, headers, method, and URI from the Express request automatically. You can also pass request info manually — see Option B above. The `token` is a one-time token generated by the [BotBye client-side SDK](https://botbye.com/docs/client-side/npm-module) that contains information about the user's device. Pass whatever the client sent; if no token is received, the decision will be `"BLOCK"`.
111
+
112
+ ```typescript
113
+ import { Injectable, CanActivate, ExecutionContext, Inject } from "@nestjs/common";
114
+ import { Request } from "express";
115
+ import { BOTBYE_SERVICE_DI_TOKEN, TBotByeService } from "@botbye/nest-express";
116
+
117
+ @Injectable()
118
+ export class BotProtectionGuard implements CanActivate {
119
+ constructor(
120
+ @Inject(BOTBYE_SERVICE_DI_TOKEN) private readonly botbye: TBotByeService
121
+ ) {}
122
+
123
+ async canActivate(context: ExecutionContext): Promise<boolean> {
124
+ const request = context.switchToHttp().getRequest<Request>();
125
+
126
+ const result = await this.botbye.evaluate({
127
+ type: "validate",
128
+ request: {
129
+ request,
130
+ // "x-botbye-token" is an example — pass the token from wherever you store it
131
+ token: request.headers["x-botbye-token"] as string | null,
132
+ },
133
+ });
134
+
135
+ return result.decision !== "BLOCK";
136
+ }
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ### `risk` — domain-level risk scoring
143
+
144
+ Use inside services that already know the user: auth, payments, account management. The purpose shifts from "is this a bot?" to **"is something suspicious happening for this user?"** — credential stuffing, account takeover, account sharing, logins from a new geo.
145
+
146
+ **Event fields:**
147
+
148
+ ```typescript
149
+ {
150
+ type: "risk";
151
+
152
+ request:
153
+ // Only ip is needed at this level
154
+ | { ip: string; headers?: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null }
155
+ // Express request object also accepted if convenient
156
+ | { request: Request };
157
+
158
+ event: {
159
+ type: string; // e.g. "login", "password_change", "checkout"
160
+ status: "ATTEMPTED" | "SUCCESSFUL" | "FAILED" | "UNKNOWN";
161
+ };
162
+
163
+ user: {
164
+ accountId: string;
165
+ username?: string | null;
166
+ email?: string | null;
167
+ phone?: string | null;
168
+ };
169
+
170
+ customFields?: Record<string, string>;
171
+ botbyeResult?: string;
172
+
173
+ config?: { bypassBotValidation?: boolean | null };
174
+ }
175
+ ```
176
+
177
+ `event` and `user` are the key fields here — they define what action is being performed and who is performing it, which is what drives the risk score. `ip` is equally important: BotBye tracks which IPs access the account to detect patterns like account sharing, credential stuffing, and suspicious geo logins. Pass it directly as `{ ip }`, or pass the Express request object if that's more convenient.
178
+
179
+ ```typescript
180
+ import { Injectable, Inject } from "@nestjs/common";
181
+ import { BOTBYE_SERVICE_DI_TOKEN, TBotByeService } from "@botbye/nest-express";
182
+
183
+ @Injectable()
184
+ export class AuthService {
185
+ constructor(
186
+ @Inject(BOTBYE_SERVICE_DI_TOKEN) private readonly botbye: TBotByeService
187
+ ) {}
188
+
189
+ async onLoginAttempt(ip: string, userId: string, email: string, loginSucceeded: boolean) {
190
+ const result = await this.botbye.evaluate({
191
+ type: "risk",
192
+ request: { ip },
193
+ event: {
194
+ type: "login",
195
+ status: loginSucceeded ? "SUCCESSFUL" : "FAILED",
196
+ // "SUCCESSFUL" | "FAILED" | "ATTEMPTED" | "UNKNOWN"
197
+ },
198
+ user: {
199
+ accountId: userId,
200
+ email,
201
+ },
202
+ });
203
+
204
+ if (result.decision === "BLOCK") {
205
+ // Lock account, trigger MFA, send alert, etc.
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ### `full` — edge check and domain scoring in one call
214
+
215
+ Use when you have all context at once: raw request, token, user, and event. Equivalent to running `validate` and `risk` in a single call. A login endpoint is a typical example — it receives the HTTP request and immediately knows the user and outcome.
216
+
217
+ **Event fields:**
218
+
219
+ ```typescript
220
+ {
221
+ type: "full";
222
+
223
+ request:
224
+ | { request: Request; token?: string | null }
225
+ | { ip: string; headers: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null };
226
+
227
+ event: {
228
+ type: string;
229
+ status: "ATTEMPTED" | "SUCCESSFUL" | "FAILED" | "UNKNOWN";
230
+ };
231
+
232
+ user: {
233
+ accountId: string;
234
+ username?: string | null;
235
+ email?: string | null;
236
+ phone?: string | null;
237
+ };
238
+
239
+ customFields?: Record<string, string>;
240
+
241
+ config?: { bypassBotValidation?: boolean | null };
242
+ }
243
+ ```
244
+
245
+ ```typescript
246
+ import { Injectable, Inject, ForbiddenException } from "@nestjs/common";
247
+ import { Request } from "express";
248
+ import { BOTBYE_SERVICE_DI_TOKEN, TBotByeService } from "@botbye/nest-express";
249
+
250
+ @Injectable()
251
+ export class AuthService {
252
+ constructor(
253
+ @Inject(BOTBYE_SERVICE_DI_TOKEN) private readonly botbye: TBotByeService
254
+ ) {}
255
+
256
+ async login(request: Request, email: string, password: string) {
257
+ const user = await this.findUser(email);
258
+ const loginSucceeded = user && (await this.checkPassword(user, password));
259
+
260
+ const result = await this.botbye.evaluate({
261
+ type: "full",
262
+ request: {
263
+ request,
264
+ // "x-botbye-token" is an example — pass the token from wherever you store it
265
+ token: request.headers["x-botbye-token"] as string | null,
266
+ },
267
+ event: {
268
+ type: "login",
269
+ status: loginSucceeded ? "SUCCESSFUL" : "FAILED",
270
+ },
271
+ user: {
272
+ accountId: user?.id ?? "unknown",
273
+ email,
274
+ },
275
+ });
276
+
277
+ if (result.decision === "BLOCK") {
278
+ throw new ForbiddenException();
279
+ }
280
+
281
+ // proceed normally
282
+ }
283
+ }
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Response
289
+
290
+ `evaluate` always returns a `Promise<TEvaluationResult>`:
291
+
292
+ ```typescript
293
+ type TEvaluationResult =
294
+ | {
295
+ decision: "ALLOW" | "BLOCK" | "CHALLENGE";
296
+ request_id: string;
297
+ risk_score: number;
298
+ scores: Record<string, number>;
299
+ signals: string[];
300
+ config: { bypass_bot_validation: boolean };
301
+ }
302
+ | {
303
+ decision: "ALLOW" | "BLOCK" | "CHALLENGE";
304
+ config: { bypass_bot_validation: boolean };
305
+ error: { message: string };
306
+ };
307
+ ```
308
+
309
+ Check `result.decision` to decide how to handle the request:
310
+
311
+ - `"ALLOW"` — request appears legitimate, proceed normally
312
+ - `"BLOCK"` — bot or suspicious activity detected, block the request
313
+ - `"CHALLENGE"` — uncertain, consider issuing a CAPTCHA, MFA, or additional verification step
314
+
315
+ When the response contains an `error` field, BotBye could not evaluate the request (e.g. invalid server key). In that case `decision` defaults to `"ALLOW"` so that a misconfiguration does not block real users — but you should monitor and fix the underlying error.
316
+
317
+ ### Response examples
318
+
319
+ Blocked (bot detected):
320
+
321
+ ```json
322
+ {
323
+ "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
324
+ "decision": "BLOCK",
325
+ "risk_score": 0.95,
326
+ "scores": { "bot": 0.95 },
327
+ "signals": ["AutomationTool"],
328
+ "config": { "bypass_bot_validation": false }
329
+ }
330
+ ```
331
+
332
+ Allowed:
333
+
334
+ ```json
335
+ {
336
+ "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
337
+ "decision": "ALLOW",
338
+ "risk_score": 0.05,
339
+ "scores": { "bot": 0.05, "ato": 0.02 },
340
+ "signals": [],
341
+ "config": { "bypass_bot_validation": false }
342
+ }
343
+ ```
344
+
345
+ Challenge:
346
+
347
+ ```json
348
+ {
349
+ "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
350
+ "decision": "CHALLENGE",
351
+ "risk_score": 0.65,
352
+ "scores": { "bot": 0.65 },
353
+ "signals": ["SuspiciousFingerprint"],
354
+ "challenge": { "type": "captcha", "token": "..." },
355
+ "config": { "bypass_bot_validation": false }
356
+ }
357
+ ```
358
+
359
+ Invalid `serverKey`:
360
+
361
+ ```json
362
+ {
363
+ "decision": "ALLOW",
364
+ "config": { "bypass_bot_validation": true },
365
+ "error": { "message": "[BotBye] Bad Request: Invalid Server Key" }
366
+ }
367
+ ```
368
+
369
+ ## Middleware usage
370
+
371
+ `BotByeMiddleware` evaluates every incoming request automatically and stores the result on the request object. Use the `@BotByeResponse()` parameter decorator to access the result in a controller without calling `evaluate` manually.
372
+
373
+ Provide a `tokenExtractor` in module options so the middleware knows where to find the BotBye token:
374
+
375
+ ```typescript
376
+ // app.module.ts
377
+ import { Module, NestModule, MiddlewareConsumer } from "@nestjs/common";
378
+ import { BotByeModule, BotByeMiddleware } from "@botbye/nest-express";
379
+
380
+ @Module({
381
+ imports: [
382
+ BotByeModule.register({
383
+ // Use your project server-key
384
+ serverKey: "00000000-0000-0000-0000-000000000000",
385
+ // "x-botbye-token" is an example — pass the token from wherever you store it
386
+ tokenExtractor: (req) => req.headers["x-botbye-token"] as string | undefined,
387
+ }),
388
+ ],
389
+ })
390
+ export class AppModule implements NestModule {
391
+ configure(consumer: MiddlewareConsumer) {
392
+ // Global: protect all routes
393
+ consumer.apply(BotByeMiddleware).forRoutes("*");
394
+ }
395
+ }
396
+ ```
397
+
398
+ Access the result in a controller with `@BotByeResponse()`:
399
+
400
+ ```typescript
401
+ // app.controller.ts
402
+ import { Controller, Post, ForbiddenException } from "@nestjs/common";
403
+ import { BotByeResponse } from "@botbye/nest-express";
404
+ import type { TEvaluationResult } from "@botbye/nest-express";
405
+
406
+ @Controller()
407
+ export class AppController {
408
+ @Post("/api/submit")
409
+ async submit(@BotByeResponse() result: TEvaluationResult) {
410
+ if (result.decision === "BLOCK") {
411
+ throw new ForbiddenException();
412
+ }
413
+ // proceed normally
414
+ }
415
+ }
416
+ ```
417
+
418
+ To apply middleware only to specific routes, replace `.forRoutes("*")` with a path or route descriptor:
419
+
420
+ ```typescript
421
+ // Scoped: protect only routes registered under "protected"
422
+ consumer.apply(BotByeMiddleware).forRoutes("protected");
423
+ ```
424
+
425
+ ## Documentation
426
+
427
+ - Web: https://botbye.com/docs/server-side/node-js/nestjs/express
428
+ - Markdown (for AI tools and agents): https://botbye.com/docs/server-side/node-js/nestjs/express.md
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@botbye/nest-express",
3
+ "version": "2.0.0",
4
+ "description": "BotBye! integration for NestJS with Express",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.js",
10
+ "require": "./src/index.js",
11
+ "types": "./src/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "src",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "license": "MIT",
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "homepage": "https://botbye.com",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/botbye/botbye-nest-express"
27
+ },
28
+ "dependencies": {
29
+ "@botbye/node-core": "^2.0.0",
30
+ "@botbye/nest-core": "^2.0.0",
31
+ "@botbye/express": "^2.0.1"
32
+ },
33
+ "peerDependencies": {
34
+ "@nestjs/common": ">=10",
35
+ "express": ">=4"
36
+ }
37
+ }
@@ -0,0 +1,6 @@
1
+ import { DynamicModule } from "@nestjs/common";
2
+ import { type TBotByeModuleOptions } from "./botbye.types";
3
+ declare class BotByeModule {
4
+ static register(options: TBotByeModuleOptions): DynamicModule;
5
+ }
6
+ export { BotByeModule };
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var BotByeModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.BotByeModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const nest_core_1 = require("@botbye/nest-core");
13
+ const express_1 = require("@botbye/express");
14
+ const constants_1 = require("./constants");
15
+ let BotByeModule = BotByeModule_1 = class BotByeModule {
16
+ static register(options) {
17
+ return {
18
+ module: BotByeModule_1,
19
+ providers: (0, nest_core_1.getProvidersList)(options, (0, express_1.factory)({ module: { name: constants_1.MODULE_NAME, version: constants_1.MODULE_VERSION } })),
20
+ exports: (0, nest_core_1.getExports)(),
21
+ };
22
+ }
23
+ };
24
+ exports.BotByeModule = BotByeModule;
25
+ exports.BotByeModule = BotByeModule = BotByeModule_1 = __decorate([
26
+ (0, common_1.Module)({})
27
+ ], BotByeModule);
@@ -0,0 +1,5 @@
1
+ import type { Request } from "express";
2
+ import type { IBotByeService, TBotByeModuleOptions as TBotByeModuleCoreOptions } from "@botbye/nest-core";
3
+ type TBotByeModuleOptions = TBotByeModuleCoreOptions<Request>;
4
+ type TBotByeService = IBotByeService<Request>;
5
+ export type { TBotByeModuleOptions, TBotByeService };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ declare const MODULE_NAME = "NESTJS_EXPRESS";
2
+ declare const MODULE_VERSION = "2.0.0";
3
+ export { MODULE_NAME, MODULE_VERSION };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MODULE_VERSION = exports.MODULE_NAME = void 0;
4
+ const MODULE_NAME = "NESTJS_EXPRESS";
5
+ exports.MODULE_NAME = MODULE_NAME;
6
+ const MODULE_VERSION = "2.0.0";
7
+ exports.MODULE_VERSION = MODULE_VERSION;
package/src/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { BotByeResponse, BotByeMiddleware, BOTBYE_SERVICE_DI_TOKEN } from "@botbye/nest-core";
2
+ export { BotByeModule } from "./botbye.module";
3
+ export type { TBotByeModuleOptions, TBotByeService } from "./botbye.types";
4
+ export type * from "@botbye/nest-core";
5
+ export type * from "@botbye/node-core";
package/src/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BotByeModule = exports.BOTBYE_SERVICE_DI_TOKEN = exports.BotByeMiddleware = exports.BotByeResponse = void 0;
4
+ var nest_core_1 = require("@botbye/nest-core");
5
+ Object.defineProperty(exports, "BotByeResponse", { enumerable: true, get: function () { return nest_core_1.BotByeResponse; } });
6
+ Object.defineProperty(exports, "BotByeMiddleware", { enumerable: true, get: function () { return nest_core_1.BotByeMiddleware; } });
7
+ Object.defineProperty(exports, "BOTBYE_SERVICE_DI_TOKEN", { enumerable: true, get: function () { return nest_core_1.BOTBYE_SERVICE_DI_TOKEN; } });
8
+ var botbye_module_1 = require("./botbye.module");
9
+ Object.defineProperty(exports, "BotByeModule", { enumerable: true, get: function () { return botbye_module_1.BotByeModule; } });