@hardlydifficult/http 1.0.4 → 1.0.6
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 +115 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/http
|
|
2
2
|
|
|
3
|
-
HTTP utilities for safe request/response handling
|
|
3
|
+
HTTP utilities for safe request/response handling: body reading with size limits, constant-time comparison, and JSON responses with CORS.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -11,75 +11,149 @@ npm install @hardlydifficult/http
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import {
|
|
14
|
+
import { createServer } from "http";
|
|
15
|
+
import { readBody, sendJson, safeCompare } from "@hardlydifficult/http";
|
|
16
|
+
|
|
17
|
+
const server = createServer(async (req, res) => {
|
|
18
|
+
// Read request body safely
|
|
19
|
+
const body = await readBody(req);
|
|
20
|
+
const token = JSON.parse(body).token;
|
|
21
|
+
|
|
22
|
+
// Compare tokens securely
|
|
23
|
+
const isMatch = safeCompare(token, process.env.SECRET_TOKEN ?? "");
|
|
24
|
+
if (!isMatch) {
|
|
25
|
+
sendJson(res, 401, { error: "Unauthorized" }, "https://example.com");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Send JSON response with CORS headers
|
|
30
|
+
sendJson(res, 200, { message: "Access granted" }, "https://example.com");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
server.listen(3000);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Core HTTP Utilities
|
|
37
|
+
|
|
38
|
+
### Reading Request Body
|
|
15
39
|
|
|
16
|
-
|
|
17
|
-
const isMatch = safeCompare('secret', 'secret'); // true
|
|
40
|
+
Safely reads request body with a configurable size limit (default 1 MB) to prevent memory exhaustion.
|
|
18
41
|
|
|
19
|
-
|
|
20
|
-
|
|
42
|
+
```typescript
|
|
43
|
+
import { readBody, MAX_BODY_BYTES } from '@hardlydifficult/http';
|
|
44
|
+
import { IncomingMessage } from 'http';
|
|
21
45
|
|
|
22
|
-
//
|
|
23
|
-
|
|
46
|
+
// Default: 1,048,576 bytes (1 MB)
|
|
47
|
+
const body = await readBody(req as IncomingMessage);
|
|
48
|
+
const text = body.toString();
|
|
49
|
+
|
|
50
|
+
// Explicit limit
|
|
51
|
+
const body2 = await readBody(req as IncomingMessage, 500_000); // 500 KB limit
|
|
24
52
|
```
|
|
25
53
|
|
|
26
|
-
|
|
54
|
+
| Parameter | Type | Description |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| req | `IncomingMessage` | HTTP request stream |
|
|
57
|
+
| maxBytes? | `number` | Maximum body size in bytes (default: `MAX_BODY_BYTES`) |
|
|
58
|
+
|
|
59
|
+
**Throws:** `Error` if body exceeds `maxBytes`.
|
|
27
60
|
|
|
28
|
-
###
|
|
61
|
+
### Sending JSON Responses
|
|
29
62
|
|
|
30
|
-
|
|
63
|
+
Sends JSON with `Content-Type: application/json` and CORS headers.
|
|
31
64
|
|
|
32
65
|
```typescript
|
|
33
|
-
import {
|
|
66
|
+
import { sendJson } from '@hardlydifficult/http';
|
|
67
|
+
import { ServerResponse } from 'http';
|
|
34
68
|
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
sendJson(res as ServerResponse, { success: true });
|
|
70
|
+
// Sets headers: Content-Type: application/json, Access-Control-Allow-Origin: *
|
|
37
71
|
```
|
|
38
72
|
|
|
39
|
-
| Parameter | Type
|
|
40
|
-
|
|
41
|
-
|
|
|
42
|
-
|
|
|
73
|
+
| Parameter | Type | Description |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| res | `ServerResponse` | HTTP response object |
|
|
76
|
+
| data | `any` | Serializable data to send as JSON |
|
|
43
77
|
|
|
44
|
-
|
|
78
|
+
## Response Handling
|
|
45
79
|
|
|
46
|
-
|
|
80
|
+
### `sendJson`
|
|
81
|
+
|
|
82
|
+
Sends a JSON response with CORS headers.
|
|
47
83
|
|
|
48
84
|
```typescript
|
|
49
|
-
import {
|
|
85
|
+
import { sendJson } from "@hardlydifficult/http";
|
|
50
86
|
|
|
51
|
-
|
|
52
|
-
// Throws Error if body exceeds MAX_BODY_BYTES
|
|
87
|
+
sendJson(res, 200, { data: "example" }, "https://example.com");
|
|
53
88
|
```
|
|
54
89
|
|
|
55
|
-
| Parameter | Type
|
|
56
|
-
|
|
57
|
-
|
|
|
90
|
+
| Parameter | Type | Description |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `res` | `ServerResponse` | Node.js HTTP response |
|
|
93
|
+
| `status` | `number` | HTTP status code |
|
|
94
|
+
| `body` | `unknown` | Serializable data to send |
|
|
95
|
+
| `corsOrigin` | `string` | `Access-Control-Allow-Origin` value |
|
|
58
96
|
|
|
59
|
-
|
|
97
|
+
Sets headers:
|
|
98
|
+
- `Content-Type: application/json`
|
|
99
|
+
- `Access-Control-Allow-Origin: corsOrigin`
|
|
100
|
+
- `Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS`
|
|
101
|
+
- `Access-Control-Allow-Headers: Content-Type, Authorization`
|
|
102
|
+
|
|
103
|
+
## Security
|
|
60
104
|
|
|
61
|
-
|
|
105
|
+
### `safeCompare`
|
|
106
|
+
|
|
107
|
+
Constant-time string comparison to prevent timing attacks.
|
|
62
108
|
|
|
63
109
|
```typescript
|
|
64
|
-
import {
|
|
110
|
+
import { safeCompare } from "@hardlydifficult/http";
|
|
65
111
|
|
|
66
|
-
|
|
67
|
-
// Sends: {"message":"Hello world"} with CORS headers
|
|
112
|
+
const isValid = safeCompare(userInput, secretToken);
|
|
68
113
|
```
|
|
69
114
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
115
|
+
Returns `true` for identical strings (including empty strings) and `false` otherwise, regardless of string length or content differences.
|
|
116
|
+
|
|
117
|
+
Handles:
|
|
118
|
+
- Equal/unequal strings
|
|
119
|
+
- Different-length strings
|
|
120
|
+
- Unicode characters
|
|
121
|
+
- Empty strings
|
|
122
|
+
|
|
123
|
+
All comparisons run in time proportional to the first string's length.
|
|
74
124
|
|
|
75
|
-
|
|
125
|
+
```typescript
|
|
126
|
+
import { safeCompare } from '@hardlydifficult/http';
|
|
76
127
|
|
|
77
|
-
|
|
128
|
+
// True (same content)
|
|
129
|
+
const match1 = safeCompare('abc', 'abc'); // => true
|
|
78
130
|
|
|
79
|
-
|
|
131
|
+
// False (different content)
|
|
132
|
+
const match2 = safeCompare('abc', 'abd'); // => false
|
|
133
|
+
|
|
134
|
+
// False (different length)
|
|
135
|
+
const match3 = safeCompare('abc', 'abcd'); // => false
|
|
136
|
+
|
|
137
|
+
// Works with unicode
|
|
138
|
+
const match4 = safeCompare('你好', '你好'); // => true
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| Parameters | Type | Description |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| a | `string` | First string |
|
|
144
|
+
| b | `string` | Second string |
|
|
145
|
+
|
|
146
|
+
**Returns:** `boolean` — `true` if strings are identical, `false` otherwise.
|
|
147
|
+
|
|
148
|
+
## Constants
|
|
149
|
+
|
|
150
|
+
### `MAX_BODY_BYTES`
|
|
151
|
+
|
|
152
|
+
Default maximum body size in bytes (1,048,576 = 1 MB).
|
|
80
153
|
|
|
81
154
|
```typescript
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
155
|
+
import { MAX_BODY_BYTES } from '@hardlydifficult/http';
|
|
156
|
+
|
|
157
|
+
// MAX_BODY_BYTES === 1024 * 1024
|
|
158
|
+
console.log(MAX_BODY_BYTES); // 1048576
|
|
85
159
|
```
|