hapi-terminator 0.0.1

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 Kamaal Farah
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,105 @@
1
+ # hapi-terminator
2
+
3
+ A Hapi plugin that terminates requests with payloads that exceed a specified size limit. This plugin helps protect your server from excessively large payloads by destroying the socket connection before the entire payload is processed.
4
+
5
+ ## Features
6
+
7
+ - 🛡️ Protects against large payload attacks
8
+ - 🔀 Different limits for registered vs unregistered routes
9
+ - 🎯 Configurable thresholds using numbers or custom functions
10
+ - ⚡ Terminates connections early to save resources
11
+ - 📦 TypeScript support included
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install hapi-terminator
17
+ ```
18
+
19
+ or with other package managers:
20
+
21
+ ```bash
22
+ yarn add hapi-terminator
23
+ bun add hapi-terminator
24
+ pnpm add hapi-terminator
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Basic Example
30
+
31
+ ```typescript
32
+ import Hapi from '@hapi/hapi';
33
+ import terminatorPlugin, { type TerminatorOptions } from 'hapi-terminator';
34
+
35
+ const server = Hapi.server({ port: 3000, host: '127.0.0.1' });
36
+
37
+ const requestTerminateOptions: TerminatorOptions = {
38
+ unregisteredLimit: 500 * 1024, // 500KB for unregistered routes
39
+ registeredLimit: 500 * 1024, // 500KB for registered routes
40
+ };
41
+
42
+ await server.register({
43
+ plugin: terminatorPlugin,
44
+ options: requestTerminateOptions,
45
+ });
46
+
47
+ server.route({
48
+ method: ['GET', 'POST'],
49
+ path: '/',
50
+ handler: () => 'Hello World!',
51
+ });
52
+
53
+ await server.start();
54
+ console.log('Server running on %s', server.info.uri);
55
+ ```
56
+
57
+ ### Using Custom Functions
58
+
59
+ You can provide custom functions to determine whether a request should be terminated:
60
+
61
+ ```typescript
62
+ const requestTerminateOptions: TerminatorOptions = {
63
+ registeredLimit: (request, size) => {
64
+ // Custom logic based on request properties
65
+ if (request.path === '/upload') {
66
+ return size > 10 * 1024 * 1024; // 10MB for upload route
67
+ }
68
+ return size > 500 * 1024; // 500KB for other routes
69
+ },
70
+ unregisteredLimit: (request, size) => {
71
+ return size > 100 * 1024; // 100KB for unregistered routes
72
+ },
73
+ };
74
+ ```
75
+
76
+ ## Configuration
77
+
78
+ ### TerminatorOptions
79
+
80
+ | Option | Type | Description |
81
+ | ------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
82
+ | `registeredLimit` | `number \| ((request: Request, size: number) => boolean)` | Maximum payload size for registered routes. Can be a number in bytes or a function that returns `true` if the request should be terminated. |
83
+ | `unregisteredLimit` | `number \| ((request: Request, size: number) => boolean)` | Maximum payload size for unregistered routes. Can be a number in bytes or a function that returns `true` if the request should be terminated. |
84
+
85
+ ### Behavior
86
+
87
+ - **Registered Routes**: When a payload exceeds the limit on a registered route, the socket is destroyed and a `413 Payload Too Large` error is thrown.
88
+ - **Unregistered Routes**: When a payload exceeds the limit on an unregistered route, the socket is destroyed and a `404 Not Found` error is thrown.
89
+ - **Disabled**: Set to `null`, `undefined`, or a negative number to disable termination for that category.
90
+
91
+ ## How It Works
92
+
93
+ The plugin hooks into Hapi's `onRequest` extension point and checks the `Content-Length` header of incoming requests. If the content length exceeds the configured threshold:
94
+
95
+ 1. The socket connection is immediately destroyed
96
+ 2. An appropriate error response is thrown (413 for registered routes, 404 for unregistered routes)
97
+ 3. No further processing occurs, saving server resources
98
+
99
+ ## License
100
+
101
+ MIT
102
+
103
+ ## Author
104
+
105
+ Kamaal Farah
@@ -0,0 +1,111 @@
1
+ import type { Server, Request } from '@hapi/hapi';
2
+ export type TerminatorOptions = {
3
+ registeredLimit?: number | ((request: Request, size: number) => boolean);
4
+ unregisteredLimit?: number | ((request: Request, size: number) => boolean);
5
+ };
6
+ export declare const plugin: {
7
+ pkg: {
8
+ name: string;
9
+ main: string;
10
+ typings: string;
11
+ type: string;
12
+ version: string;
13
+ license: string;
14
+ author: {
15
+ name: string;
16
+ };
17
+ devDependencies: {
18
+ "@eslint/js": string;
19
+ "@hapi/hapi": string;
20
+ "@kamaalio/prettier-config": string;
21
+ "@types/bun": string;
22
+ eslint: string;
23
+ globals: string;
24
+ husky: string;
25
+ jiti: string;
26
+ "lint-staged": string;
27
+ prettier: string;
28
+ typescript: string;
29
+ "typescript-eslint": string;
30
+ };
31
+ dependencies: {
32
+ "@hapi/boom": string;
33
+ };
34
+ prettier: string;
35
+ "lint-staged": {
36
+ "**/*.{js,ts,tsx}": string[];
37
+ "**/*": string;
38
+ };
39
+ scripts: {
40
+ compile: string;
41
+ format: string;
42
+ "format:check": string;
43
+ lint: string;
44
+ prepare: string;
45
+ prepack: string;
46
+ quality: string;
47
+ test: string;
48
+ typecheck: string;
49
+ };
50
+ publishConfig: {
51
+ access: string;
52
+ };
53
+ files: string[];
54
+ };
55
+ register: typeof register;
56
+ };
57
+ declare function register(server: Server, options: TerminatorOptions): Promise<void>;
58
+ declare const _default: {
59
+ plugin: {
60
+ pkg: {
61
+ name: string;
62
+ main: string;
63
+ typings: string;
64
+ type: string;
65
+ version: string;
66
+ license: string;
67
+ author: {
68
+ name: string;
69
+ };
70
+ devDependencies: {
71
+ "@eslint/js": string;
72
+ "@hapi/hapi": string;
73
+ "@kamaalio/prettier-config": string;
74
+ "@types/bun": string;
75
+ eslint: string;
76
+ globals: string;
77
+ husky: string;
78
+ jiti: string;
79
+ "lint-staged": string;
80
+ prettier: string;
81
+ typescript: string;
82
+ "typescript-eslint": string;
83
+ };
84
+ dependencies: {
85
+ "@hapi/boom": string;
86
+ };
87
+ prettier: string;
88
+ "lint-staged": {
89
+ "**/*.{js,ts,tsx}": string[];
90
+ "**/*": string;
91
+ };
92
+ scripts: {
93
+ compile: string;
94
+ format: string;
95
+ "format:check": string;
96
+ lint: string;
97
+ prepare: string;
98
+ prepack: string;
99
+ quality: string;
100
+ test: string;
101
+ typecheck: string;
102
+ };
103
+ publishConfig: {
104
+ access: string;
105
+ };
106
+ files: string[];
107
+ };
108
+ register: typeof register;
109
+ };
110
+ };
111
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ import Boom from '@hapi/boom';
2
+ import pkg from './package.json';
3
+ export const plugin = { pkg, register };
4
+ async function register(server, options) {
5
+ server.ext('onRequest', onRequest(options));
6
+ }
7
+ function onRequest(options) {
8
+ return (request, h) => {
9
+ const unregisteredRouteHandler = handleUnregisteredRoute(request, h, options);
10
+ const registeredRouteHandler = handleRegisteredRoute(request, h, options);
11
+ const rawContentLength = request.headers['content-length'];
12
+ if (!rawContentLength) {
13
+ return h.continue;
14
+ }
15
+ const contentLength = Number.parseInt(rawContentLength, 10);
16
+ if (Number.isNaN(contentLength)) {
17
+ return h.continue;
18
+ }
19
+ const matchedRoute = request.server.match(request.method, request.path);
20
+ if (matchedRoute != null) {
21
+ return registeredRouteHandler(contentLength);
22
+ }
23
+ return unregisteredRouteHandler(contentLength);
24
+ };
25
+ }
26
+ function handleUnregisteredRoute(request, h, options) {
27
+ return (contentLength) => {
28
+ if (options.unregisteredLimit == null ||
29
+ (typeof options.unregisteredLimit === 'number' && options.unregisteredLimit < 0)) {
30
+ return h.continue;
31
+ }
32
+ if ((typeof options.unregisteredLimit === 'number' && contentLength > options.unregisteredLimit) ||
33
+ (typeof options.unregisteredLimit === 'function' && options.unregisteredLimit(request, contentLength))) {
34
+ request.raw.req.socket?.destroy();
35
+ throw Boom.notFound();
36
+ }
37
+ return h.continue;
38
+ };
39
+ }
40
+ function handleRegisteredRoute(request, h, options) {
41
+ return (contentLength) => {
42
+ if (options.registeredLimit == null ||
43
+ (typeof options.registeredLimit === 'number' && options.registeredLimit < 0)) {
44
+ return h.continue;
45
+ }
46
+ if ((typeof options.registeredLimit === 'number' && contentLength > options.registeredLimit) ||
47
+ (typeof options.registeredLimit === 'function' && options.registeredLimit(request, contentLength))) {
48
+ request.raw.req.socket?.destroy();
49
+ throw Boom.entityTooLarge(typeof options.registeredLimit === 'number'
50
+ ? `Payload content length greater than maximum allowed: ${options.registeredLimit}`
51
+ : undefined);
52
+ }
53
+ return h.continue;
54
+ };
55
+ }
56
+ export default { plugin };
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "hapi-terminator",
3
+ "main": "index.js",
4
+ "typings": "index.d.ts",
5
+ "type": "module",
6
+ "version": "0.0.1",
7
+ "license": "MIT",
8
+ "author": {
9
+ "name": "Kamaal Farah"
10
+ },
11
+ "devDependencies": {
12
+ "@eslint/js": "^9.39.2",
13
+ "@hapi/hapi": "^21.4.4",
14
+ "@kamaalio/prettier-config": "^0.1.2",
15
+ "@types/bun": "latest",
16
+ "eslint": "^9.39.2",
17
+ "globals": "^17.0.0",
18
+ "husky": "^9.1.7",
19
+ "jiti": "^2.6.1",
20
+ "lint-staged": "^16.2.7",
21
+ "prettier": "^3.7.4",
22
+ "typescript": "^5.9.3",
23
+ "typescript-eslint": "^8.52.0"
24
+ },
25
+ "dependencies": {
26
+ "@hapi/boom": "^10.0.1"
27
+ },
28
+ "prettier": "@kamaalio/prettier-config",
29
+ "lint-staged": {
30
+ "**/*.{js,ts,tsx}": [
31
+ "eslint --fix"
32
+ ],
33
+ "**/*": "prettier --write --ignore-unknown"
34
+ },
35
+ "scripts": {
36
+ "compile": "tsc --project tsconfig.build.json",
37
+ "format": "prettier --write .",
38
+ "format:check": "prettier --check .",
39
+ "lint": "eslint .",
40
+ "prepare": "bunx husky",
41
+ "prepack": "rm -rf dist && bun run compile",
42
+ "quality": "bun run format:check && bun run lint && bun run typecheck",
43
+ "test": "bun test",
44
+ "typecheck": "tsc --noEmit"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "files": [
50
+ "dist"
51
+ ]
52
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "hapi-terminator",
3
+ "main": "index.js",
4
+ "typings": "index.d.ts",
5
+ "type": "module",
6
+ "version": "0.0.1",
7
+ "license": "MIT",
8
+ "author": {
9
+ "name": "Kamaal Farah"
10
+ },
11
+ "devDependencies": {
12
+ "@eslint/js": "^9.39.2",
13
+ "@hapi/hapi": "^21.4.4",
14
+ "@kamaalio/prettier-config": "^0.1.2",
15
+ "@types/bun": "latest",
16
+ "eslint": "^9.39.2",
17
+ "globals": "^17.0.0",
18
+ "husky": "^9.1.7",
19
+ "jiti": "^2.6.1",
20
+ "lint-staged": "^16.2.7",
21
+ "prettier": "^3.7.4",
22
+ "typescript": "^5.9.3",
23
+ "typescript-eslint": "^8.52.0"
24
+ },
25
+ "dependencies": {
26
+ "@hapi/boom": "^10.0.1"
27
+ },
28
+ "prettier": "@kamaalio/prettier-config",
29
+ "lint-staged": {
30
+ "**/*.{js,ts,tsx}": [
31
+ "eslint --fix"
32
+ ],
33
+ "**/*": "prettier --write --ignore-unknown"
34
+ },
35
+ "scripts": {
36
+ "compile": "tsc --project tsconfig.build.json",
37
+ "format": "prettier --write .",
38
+ "format:check": "prettier --check .",
39
+ "lint": "eslint .",
40
+ "prepare": "bunx husky",
41
+ "prepack": "rm -rf dist && bun run compile",
42
+ "quality": "bun run format:check && bun run lint && bun run typecheck",
43
+ "test": "bun test",
44
+ "typecheck": "tsc --noEmit"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "files": [
50
+ "dist"
51
+ ]
52
+ }