@arcjet/node 1.0.0-beta.9 → 1.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/README.md +43 -71
- package/index.d.ts +147 -35
- package/index.js +62 -77
- package/package.json +20 -18
package/README.md
CHANGED
|
@@ -37,111 +37,83 @@ Visit the [quick start guide][quick-start] to get started.
|
|
|
37
37
|
Try an Arcjet protected app live at [https://example.arcjet.com][example-url]
|
|
38
38
|
([source code][example-source]).
|
|
39
39
|
|
|
40
|
-
##
|
|
40
|
+
## What is this?
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
## Rate limit example
|
|
47
|
-
|
|
48
|
-
The example below applies a token bucket rate limit rule to a route where we
|
|
49
|
-
identify the user based on their ID e.g. if they are logged in. The bucket is
|
|
50
|
-
configured with a maximum capacity of 10 tokens and refills by 5 tokens every 10
|
|
51
|
-
seconds. Each request consumes 5 tokens.
|
|
42
|
+
This is our adapter to integrate Arcjet into Node.js.
|
|
43
|
+
Arcjet helps you secure your Node server.
|
|
44
|
+
This package exists so that we can provide the best possible experience to
|
|
45
|
+
Node users.
|
|
52
46
|
|
|
53
|
-
|
|
47
|
+
## When should I use this?
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
You can use this if you are using Node.js.
|
|
50
|
+
See our [_Get started_ guide][arcjet-get-started] for other supported
|
|
51
|
+
frameworks and runtimes.
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com
|
|
61
|
-
characteristics: ["userId"], // track requests by a custom user ID
|
|
62
|
-
rules: [
|
|
63
|
-
// Create a token bucket rate limit. Other algorithms are supported.
|
|
64
|
-
tokenBucket({
|
|
65
|
-
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
|
|
66
|
-
refillRate: 5, // refill 5 tokens per interval
|
|
67
|
-
interval: 10, // refill every 10 seconds
|
|
68
|
-
capacity: 10, // bucket maximum capacity of 10 tokens
|
|
69
|
-
}),
|
|
70
|
-
detectBot({
|
|
71
|
-
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
|
|
72
|
-
// configured with a list of bots to allow from
|
|
73
|
-
// https://arcjet.com/bot-list
|
|
74
|
-
allow: [], // "allow none" will block all detected bots
|
|
75
|
-
}),
|
|
76
|
-
],
|
|
77
|
-
});
|
|
53
|
+
## Install
|
|
78
54
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
res: http.ServerResponse,
|
|
82
|
-
) {
|
|
83
|
-
const userId = "user123"; // Replace with your authenticated user ID
|
|
84
|
-
const decision = await aj.protect(req, { userId, requested: 5 }); // Deduct 5 tokens from the bucket
|
|
85
|
-
console.log("Arcjet decision", decision);
|
|
55
|
+
This package is ESM only.
|
|
56
|
+
Install with npm in Node.js:
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
90
|
-
} else {
|
|
91
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
92
|
-
res.end(JSON.stringify({ message: "Hello world" }));
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
server.listen(8000);
|
|
58
|
+
```sh
|
|
59
|
+
npm install @arcjet/node
|
|
97
60
|
```
|
|
98
61
|
|
|
99
|
-
##
|
|
100
|
-
|
|
101
|
-
[Arcjet Shield][shield-concepts-docs] protects your application against common
|
|
102
|
-
attacks, including the OWASP Top 10. You can run Shield on every request with
|
|
103
|
-
negligible performance impact.
|
|
62
|
+
## Use
|
|
104
63
|
|
|
105
64
|
```ts
|
|
106
|
-
import arcjet, { shield } from "@arcjet/node";
|
|
107
65
|
import http from "node:http";
|
|
66
|
+
import arcjet, { shield } from "@arcjet/node";
|
|
67
|
+
|
|
68
|
+
// Get your Arcjet key at <https://app.arcjet.com>.
|
|
69
|
+
// Set it as an environment variable instead of hard coding it.
|
|
70
|
+
const arcjetKey = process.env.ARCJET_KEY;
|
|
71
|
+
|
|
72
|
+
if (!arcjetKey) {
|
|
73
|
+
throw new Error("Cannot find `ARCJET_KEY` environment variable");
|
|
74
|
+
}
|
|
108
75
|
|
|
109
76
|
const aj = arcjet({
|
|
110
|
-
key:
|
|
77
|
+
key: arcjetKey,
|
|
111
78
|
rules: [
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}),
|
|
79
|
+
// Shield protects your app from common attacks.
|
|
80
|
+
// Use `DRY_RUN` instead of `LIVE` to only log.
|
|
81
|
+
shield({ mode: "LIVE" }),
|
|
115
82
|
],
|
|
116
83
|
});
|
|
117
84
|
|
|
118
85
|
const server = http.createServer(async function (
|
|
119
|
-
|
|
120
|
-
|
|
86
|
+
request: http.IncomingMessage,
|
|
87
|
+
response: http.ServerResponse,
|
|
121
88
|
) {
|
|
122
|
-
const decision = await aj.protect(
|
|
89
|
+
const decision = await aj.protect(request);
|
|
123
90
|
|
|
124
91
|
if (decision.isDenied()) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
129
|
-
res.end(JSON.stringify({ message: "Hello world" }));
|
|
92
|
+
response.writeHead(403, { "Content-Type": "application/json" });
|
|
93
|
+
response.end(JSON.stringify({ message: "Forbidden" }));
|
|
94
|
+
return;
|
|
130
95
|
}
|
|
96
|
+
|
|
97
|
+
response.writeHead(200, { "Content-Type": "application/json" });
|
|
98
|
+
response.end(JSON.stringify({ message: "Hello world" }));
|
|
131
99
|
});
|
|
132
100
|
|
|
133
101
|
server.listen(8000);
|
|
134
102
|
```
|
|
135
103
|
|
|
104
|
+
For more on how to configure Arcjet with Node.js and how to protect Node,
|
|
105
|
+
see the [Arcjet Node.js SDK reference][arcjet-reference-node] on our website.
|
|
106
|
+
|
|
136
107
|
## License
|
|
137
108
|
|
|
138
|
-
|
|
109
|
+
[Apache License, Version 2.0][apache-license] © [Arcjet Labs, Inc.][arcjet]
|
|
139
110
|
|
|
111
|
+
[arcjet-get-started]: https://docs.arcjet.com/get-started
|
|
112
|
+
[arcjet-reference-node]: https://docs.arcjet.com/reference/nodejs
|
|
140
113
|
[arcjet]: https://arcjet.com
|
|
141
114
|
[node-js]: https://nodejs.org/
|
|
142
115
|
[alt-sdk]: https://www.npmjs.com/package/@arcjet/next
|
|
143
116
|
[example-url]: https://example.arcjet.com
|
|
144
117
|
[quick-start]: https://docs.arcjet.com/get-started/nodejs
|
|
145
118
|
[example-source]: https://github.com/arcjet/arcjet-js-example
|
|
146
|
-
[shield-concepts-docs]: https://docs.arcjet.com/shield/concepts
|
|
147
119
|
[apache-license]: http://www.apache.org/licenses/LICENSE-2.0
|
package/index.d.ts
CHANGED
|
@@ -4,72 +4,184 @@ type Simplify<T> = {
|
|
|
4
4
|
[KeyType in keyof T]: T[KeyType];
|
|
5
5
|
} & {};
|
|
6
6
|
declare const emptyObjectSymbol: unique symbol;
|
|
7
|
-
type WithoutCustomProps = {
|
|
8
|
-
[emptyObjectSymbol]?: never;
|
|
9
|
-
};
|
|
10
7
|
type PlainObject = {
|
|
11
8
|
[key: string]: unknown;
|
|
12
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Dynamically generate whether zero or one `properties` object must or can be passed.
|
|
12
|
+
*/
|
|
13
|
+
type MaybeProperties<T> = {
|
|
14
|
+
[P in keyof T]?: T[P];
|
|
15
|
+
} extends T ? T extends {
|
|
16
|
+
[emptyObjectSymbol]?: never;
|
|
17
|
+
} ? [
|
|
18
|
+
] : [
|
|
19
|
+
properties?: T
|
|
20
|
+
] : [
|
|
21
|
+
properties: T
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for {@linkcode createRemoteClient}.
|
|
25
|
+
*/
|
|
13
26
|
export type RemoteClientOptions = {
|
|
27
|
+
/**
|
|
28
|
+
* Base URI for HTTP requests to Decide API (optional).
|
|
29
|
+
*
|
|
30
|
+
* Defaults to the environment variable `ARCJET_BASE_URL` (if that value
|
|
31
|
+
* is known and allowed) and the standard production API otherwise.
|
|
32
|
+
*/
|
|
14
33
|
baseUrl?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Timeout in milliseconds for the Decide API (optional).
|
|
36
|
+
*
|
|
37
|
+
* Defaults to `500` in production and `1000` in development.
|
|
38
|
+
*/
|
|
15
39
|
timeout?: number;
|
|
16
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Create a remote client.
|
|
43
|
+
*
|
|
44
|
+
* @param options
|
|
45
|
+
* Configuration (optional).
|
|
46
|
+
* @returns
|
|
47
|
+
* Client.
|
|
48
|
+
*/
|
|
17
49
|
export declare function createRemoteClient(options?: RemoteClientOptions): import("@arcjet/protocol/client.js").Client;
|
|
18
50
|
type EventHandlerLike = (event: string, listener: (...args: any[]) => void) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Request for the Node.js integration of Arcjet.
|
|
53
|
+
*
|
|
54
|
+
* This is the minimum interface similar to `http.IncomingMessage`.
|
|
55
|
+
*/
|
|
19
56
|
export interface ArcjetNodeRequest {
|
|
20
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Headers of the request.
|
|
59
|
+
*/
|
|
60
|
+
headers?: Record<string, string | string[] | undefined> | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* `net.Socket` object associated with the connection.
|
|
63
|
+
*
|
|
64
|
+
* See <https://nodejs.org/api/http.html#messagesocket>.
|
|
65
|
+
*/
|
|
21
66
|
socket?: Partial<{
|
|
22
|
-
remoteAddress
|
|
67
|
+
remoteAddress?: string | undefined;
|
|
23
68
|
encrypted: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
69
|
+
}> | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* HTTP method of the request.
|
|
72
|
+
*/
|
|
73
|
+
method?: string | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* HTTP version sent by the client.
|
|
76
|
+
*
|
|
77
|
+
* See <https://nodejs.org/api/http.html#messagehttpversion>.
|
|
78
|
+
*/
|
|
79
|
+
httpVersion?: string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* URL.
|
|
82
|
+
*/
|
|
83
|
+
url?: string | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Request body.
|
|
86
|
+
*/
|
|
28
87
|
body?: unknown;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Add event handlers.
|
|
90
|
+
*
|
|
91
|
+
* This field is available through `stream.Readable` from `EventEmitter`.
|
|
92
|
+
*
|
|
93
|
+
* See <https://nodejs.org/api/events.html#emitteroneventname-listener>.
|
|
94
|
+
*/
|
|
95
|
+
on?: EventHandlerLike | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Remove event handlers.
|
|
98
|
+
*
|
|
99
|
+
* This field is available through `stream.Readable` from `EventEmitter`.
|
|
100
|
+
*
|
|
101
|
+
* See <https://nodejs.org/api/events.html#emitterremovelistenereventname-listener>.
|
|
102
|
+
*/
|
|
103
|
+
removeListener?: EventHandlerLike | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Whether the readable stream is readable.
|
|
106
|
+
*
|
|
107
|
+
* This field is available from `stream.Readable`.
|
|
108
|
+
*
|
|
109
|
+
* See <https://nodejs.org/api/stream.html#readablereadable>.
|
|
110
|
+
*/
|
|
111
|
+
readable?: boolean | undefined;
|
|
32
112
|
}
|
|
33
113
|
/**
|
|
34
|
-
*
|
|
114
|
+
* Configuration for the Node.js integration of Arcjet.
|
|
115
|
+
*
|
|
116
|
+
* @template Rules
|
|
117
|
+
* List of rules.
|
|
118
|
+
* @template Characteristics
|
|
119
|
+
* Characteristics to track a user by.
|
|
35
120
|
*/
|
|
36
121
|
export type ArcjetOptions<Rules extends [...Array<Primitive | Product>], Characteristics extends readonly string[]> = Simplify<CoreOptions<Rules, Characteristics> & {
|
|
37
122
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
123
|
+
* IP addresses and CIDR ranges of trusted load balancers and proxies
|
|
124
|
+
* (optional, example: `["100.100.100.100", "100.100.100.0/24"]`).
|
|
40
125
|
*/
|
|
41
126
|
proxies?: Array<string>;
|
|
42
127
|
}>;
|
|
43
128
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
129
|
+
* Instance of the Node.js integration of Arcjet.
|
|
130
|
+
*
|
|
131
|
+
* Primarily has a `protect()` method to make a decision about how a Node request
|
|
132
|
+
* should be handled.
|
|
133
|
+
*
|
|
134
|
+
* @template Props
|
|
135
|
+
* Configuration.
|
|
46
136
|
*/
|
|
47
137
|
export interface ArcjetNode<Props extends PlainObject> {
|
|
48
138
|
/**
|
|
49
|
-
*
|
|
50
|
-
* analyzed and then a decision made on whether to allow, deny, or challenge
|
|
51
|
-
* the request.
|
|
139
|
+
* Make a decision about how to handle a request.
|
|
52
140
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
141
|
+
* This will analyze the request locally where possible and otherwise call
|
|
142
|
+
* the Arcjet decision API.
|
|
143
|
+
*
|
|
144
|
+
* @param request
|
|
145
|
+
* Details about the {@linkcode ArcjetNodeRequest} that Arcjet needs to make a
|
|
146
|
+
* decision.
|
|
147
|
+
* @param props
|
|
148
|
+
* Additional properties required for running rules against a request.
|
|
149
|
+
* @returns
|
|
150
|
+
* Promise that resolves to an {@linkcode ArcjetDecision} indicating
|
|
151
|
+
* Arcjet’s decision about the request.
|
|
56
152
|
*/
|
|
57
|
-
protect(request: ArcjetNodeRequest, ...props: Props
|
|
153
|
+
protect(request: ArcjetNodeRequest, ...props: MaybeProperties<Props>): Promise<ArcjetDecision>;
|
|
58
154
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
155
|
+
* Augment the client with another rule.
|
|
156
|
+
*
|
|
157
|
+
* Useful for varying rules based on criteria in your handler such as
|
|
158
|
+
* different rate limit for logged in users.
|
|
61
159
|
*
|
|
62
|
-
* @
|
|
63
|
-
*
|
|
160
|
+
* @template Rule
|
|
161
|
+
* Type of rule.
|
|
162
|
+
* @param rule
|
|
163
|
+
* Rule to add to Arcjet.
|
|
164
|
+
* @returns
|
|
165
|
+
* Arcjet instance augmented with the given rule.
|
|
64
166
|
*/
|
|
65
|
-
withRule<
|
|
167
|
+
withRule<ChildProperties extends PlainObject>(rule: Primitive<ChildProperties> | Product<ChildProperties>): ArcjetNode<Props & ChildProperties>;
|
|
66
168
|
}
|
|
67
169
|
/**
|
|
68
|
-
* Create a new
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* client
|
|
170
|
+
* Create a new Node.js integration of Arcjet.
|
|
171
|
+
*
|
|
172
|
+
* > 👉 **Tip**:
|
|
173
|
+
* > build your initial base client with as many rules as possible outside of a
|
|
174
|
+
* > request handler;
|
|
175
|
+
* > if you need more rules inside handlers later then you can call `withRule()`
|
|
176
|
+
* > on that base client.
|
|
72
177
|
*
|
|
73
|
-
* @
|
|
178
|
+
* @template Rules
|
|
179
|
+
* List of rules.
|
|
180
|
+
* @template Characteristics
|
|
181
|
+
* Characteristics to track a user by.
|
|
182
|
+
* @param options
|
|
183
|
+
* Configuration.
|
|
184
|
+
* @returns
|
|
185
|
+
* Node.js integration of Arcjet.
|
|
74
186
|
*/
|
|
75
|
-
export default function arcjet<const Rules extends (Primitive | Product)[], const Characteristics extends readonly string[]>(options: ArcjetOptions<Rules, Characteristics>): ArcjetNode<
|
|
187
|
+
export default function arcjet<const Rules extends (Primitive | Product)[], const Characteristics extends readonly string[]>(options: ArcjetOptions<Rules, Characteristics>): ArcjetNode<ExtraProps<Rules> & CharacteristicProps<Characteristics>>;
|
package/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import * as core from 'arcjet';
|
|
1
2
|
import core__default from 'arcjet';
|
|
2
3
|
export * from 'arcjet';
|
|
3
|
-
import
|
|
4
|
-
import ArcjetHeaders from '@arcjet/headers';
|
|
4
|
+
import findIp, { parseProxy } from '@arcjet/ip';
|
|
5
|
+
import { ArcjetHeaders } from '@arcjet/headers';
|
|
5
6
|
import { logLevel, isDevelopment, baseUrl, platform } from '@arcjet/env';
|
|
6
7
|
import { Logger } from '@arcjet/logger';
|
|
7
8
|
import { createClient } from '@arcjet/protocol/client.js';
|
|
@@ -27,9 +28,6 @@ const env = {
|
|
|
27
28
|
get NODE_ENV() {
|
|
28
29
|
return process.env.NODE_ENV;
|
|
29
30
|
},
|
|
30
|
-
get ARCJET_KEY() {
|
|
31
|
-
return process.env.ARCJET_KEY;
|
|
32
|
-
},
|
|
33
31
|
get ARCJET_ENV() {
|
|
34
32
|
return process.env.ARCJET_ENV;
|
|
35
33
|
},
|
|
@@ -39,32 +37,26 @@ const env = {
|
|
|
39
37
|
get ARCJET_BASE_URL() {
|
|
40
38
|
return process.env.ARCJET_BASE_URL;
|
|
41
39
|
},
|
|
40
|
+
get FIREBASE_CONFIG() {
|
|
41
|
+
return process.env.FIREBASE_CONFIG;
|
|
42
|
+
},
|
|
42
43
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return err.message;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return "Unknown problem";
|
|
56
|
-
}
|
|
44
|
+
let warnedForAutomaticBodyRead = false;
|
|
45
|
+
/**
|
|
46
|
+
* Create a remote client.
|
|
47
|
+
*
|
|
48
|
+
* @param options
|
|
49
|
+
* Configuration (optional).
|
|
50
|
+
* @returns
|
|
51
|
+
* Client.
|
|
52
|
+
*/
|
|
57
53
|
function createRemoteClient(options) {
|
|
58
|
-
// The base URL for the Arcjet API. Will default to the standard production
|
|
59
|
-
// API unless environment variable `ARCJET_BASE_URL` is set.
|
|
60
54
|
const url = options?.baseUrl ?? baseUrl(env);
|
|
61
|
-
// The timeout for the Arcjet API in milliseconds. This is set to a low value
|
|
62
|
-
// in production so calls fail open.
|
|
63
55
|
const timeout = options?.timeout ?? (isDevelopment(env) ? 1000 : 500);
|
|
64
56
|
// Transport is the HTTP client that the client uses to make requests.
|
|
65
57
|
const transport = createTransport(url);
|
|
66
58
|
const sdkStack = "NODEJS";
|
|
67
|
-
const sdkVersion = "1.0.0
|
|
59
|
+
const sdkVersion = "1.0.0";
|
|
68
60
|
return createClient({
|
|
69
61
|
transport,
|
|
70
62
|
baseUrl: url,
|
|
@@ -84,12 +76,22 @@ function cookiesToString(cookies) {
|
|
|
84
76
|
return cookies;
|
|
85
77
|
}
|
|
86
78
|
/**
|
|
87
|
-
* Create a new
|
|
88
|
-
* outside of a request handler so it persists across requests. If you need to
|
|
89
|
-
* augment a client inside a handler, call the `withRule()` function on the base
|
|
90
|
-
* client.
|
|
79
|
+
* Create a new Node.js integration of Arcjet.
|
|
91
80
|
*
|
|
92
|
-
*
|
|
81
|
+
* > 👉 **Tip**:
|
|
82
|
+
* > build your initial base client with as many rules as possible outside of a
|
|
83
|
+
* > request handler;
|
|
84
|
+
* > if you need more rules inside handlers later then you can call `withRule()`
|
|
85
|
+
* > on that base client.
|
|
86
|
+
*
|
|
87
|
+
* @template Rules
|
|
88
|
+
* List of rules.
|
|
89
|
+
* @template Characteristics
|
|
90
|
+
* Characteristics to track a user by.
|
|
91
|
+
* @param options
|
|
92
|
+
* Configuration.
|
|
93
|
+
* @returns
|
|
94
|
+
* Node.js integration of Arcjet.
|
|
93
95
|
*/
|
|
94
96
|
function arcjet(options) {
|
|
95
97
|
const client = options.client ?? createRemoteClient();
|
|
@@ -109,10 +111,14 @@ function arcjet(options) {
|
|
|
109
111
|
const cookies = cookiesToString(request.headers?.cookie);
|
|
110
112
|
// We construct an ArcjetHeaders to normalize over Headers
|
|
111
113
|
const headers = new ArcjetHeaders(request.headers);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
const xArcjetIp = isDevelopment(env)
|
|
115
|
+
? headers.get("x-arcjet-ip")
|
|
116
|
+
: undefined;
|
|
117
|
+
let ip = xArcjetIp ||
|
|
118
|
+
findIp({
|
|
119
|
+
socket: request.socket,
|
|
120
|
+
headers,
|
|
121
|
+
}, { platform: platform(env), proxies });
|
|
116
122
|
if (ip === "") {
|
|
117
123
|
// If the `ip` is empty but we're in development mode, we default the IP
|
|
118
124
|
// so the request doesn't fail.
|
|
@@ -167,62 +173,41 @@ function arcjet(options) {
|
|
|
167
173
|
};
|
|
168
174
|
}
|
|
169
175
|
function withClient(aj) {
|
|
170
|
-
|
|
176
|
+
const client = {
|
|
171
177
|
withRule(rule) {
|
|
172
178
|
const client = aj.withRule(rule);
|
|
173
179
|
return withClient(client);
|
|
174
180
|
},
|
|
175
|
-
async protect(request,
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
// the definition of `props` in the signature but it's hard to track down
|
|
179
|
-
const req = toArcjetRequest(request, props ?? {});
|
|
181
|
+
async protect(request, props) {
|
|
182
|
+
// Cast of `{}` because here we switch from `undefined` to `Properties`.
|
|
183
|
+
const req = toArcjetRequest(request, props || {});
|
|
180
184
|
const getBody = async () => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
// Read the stream if the body is not present.
|
|
186
|
+
if (request.body === null || request.body === undefined) {
|
|
187
|
+
let expectedLength;
|
|
188
|
+
// TODO: This shouldn't need to build headers again but the type
|
|
189
|
+
// for `req` above is overly relaxed
|
|
190
|
+
const headers = new ArcjetHeaders(request.headers);
|
|
191
|
+
const expectedLengthStr = headers.get("content-length");
|
|
192
|
+
if (typeof expectedLengthStr === "string") {
|
|
193
|
+
expectedLength = parseInt(expectedLengthStr, 10);
|
|
186
194
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return JSON.stringify(request.body);
|
|
195
|
+
if (!warnedForAutomaticBodyRead) {
|
|
196
|
+
warnedForAutomaticBodyRead = true;
|
|
197
|
+
log.warn("Automatically reading the request body is deprecated; please pass an explicit `sensitiveInfoValue` field. See <https://docs.arcjet.com/upgrading/sdk-migration>.");
|
|
191
198
|
}
|
|
192
|
-
|
|
193
|
-
typeof request.removeListener === "function") {
|
|
194
|
-
let expectedLength;
|
|
195
|
-
// TODO: This shouldn't need to build headers again but the type
|
|
196
|
-
// for `req` above is overly relaxed
|
|
197
|
-
const headers = new ArcjetHeaders(request.headers);
|
|
198
|
-
const expectedLengthStr = headers.get("content-length");
|
|
199
|
-
if (typeof expectedLengthStr === "string") {
|
|
200
|
-
try {
|
|
201
|
-
expectedLength = parseInt(expectedLengthStr, 10);
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
// If the expected length couldn't be parsed we'll just not set one.
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// Awaited to throw if it rejects and we'll just return undefined
|
|
208
|
-
const body = await readBody(request, {
|
|
209
|
-
// We will process 1mb bodies
|
|
210
|
-
limit: 1048576,
|
|
211
|
-
expectedLength,
|
|
212
|
-
});
|
|
213
|
-
return body;
|
|
214
|
-
}
|
|
215
|
-
log.warn("no body available");
|
|
216
|
-
return;
|
|
199
|
+
return readBody(request, { expectedLength });
|
|
217
200
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return;
|
|
201
|
+
// A package like `body-parser` was used to read the stream.
|
|
202
|
+
if (typeof request.body === "string") {
|
|
203
|
+
return request.body;
|
|
221
204
|
}
|
|
205
|
+
return JSON.stringify(request.body);
|
|
222
206
|
};
|
|
223
207
|
return aj.protect({ getBody }, req);
|
|
224
208
|
},
|
|
225
|
-
}
|
|
209
|
+
};
|
|
210
|
+
return Object.freeze(client);
|
|
226
211
|
}
|
|
227
212
|
const aj = core__default({ ...options, client, log });
|
|
228
213
|
return withClient(aj);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcjet/node",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Arcjet SDK for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"analyze",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"url": "https://arcjet.com"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
34
|
+
"node": ">=20"
|
|
35
35
|
},
|
|
36
36
|
"type": "module",
|
|
37
37
|
"main": "./index.js",
|
|
@@ -44,26 +44,28 @@
|
|
|
44
44
|
"build": "rollup --config rollup.config.js",
|
|
45
45
|
"lint": "eslint .",
|
|
46
46
|
"prepublishOnly": "npm run build",
|
|
47
|
-
"test": "
|
|
47
|
+
"test-api": "node --test -- test/*.test.js",
|
|
48
|
+
"test-coverage#": "TODO: after node 20, use: ` --test-coverage-branches=100 --test-coverage-exclude \"../{analyze-wasm,analyze,arcjet,cache,duration,env,headers,ip,logger,protocol,runtime,sprintf,stable-hash,transport}/**/*.{js,ts}\" --test-coverage-exclude \"test/**/*.{js,ts}\" --test-coverage-functions=100 --test-coverage-lines=100`",
|
|
49
|
+
"test-coverage": "node --experimental-test-coverage --test -- test/*.test.js",
|
|
50
|
+
"test": "npm run build && npm run lint && npm run test-coverage"
|
|
48
51
|
},
|
|
49
52
|
"dependencies": {
|
|
50
|
-
"@arcjet/env": "1.0.0
|
|
51
|
-
"@arcjet/headers": "1.0.0
|
|
52
|
-
"@arcjet/ip": "1.0.0
|
|
53
|
-
"@arcjet/logger": "1.0.0
|
|
54
|
-
"@arcjet/protocol": "1.0.0
|
|
55
|
-
"@arcjet/transport": "1.0.0
|
|
56
|
-
"@arcjet/body": "1.0.0
|
|
57
|
-
"arcjet": "1.0.0
|
|
53
|
+
"@arcjet/env": "1.0.0",
|
|
54
|
+
"@arcjet/headers": "1.0.0",
|
|
55
|
+
"@arcjet/ip": "1.0.0",
|
|
56
|
+
"@arcjet/logger": "1.0.0",
|
|
57
|
+
"@arcjet/protocol": "1.0.0",
|
|
58
|
+
"@arcjet/transport": "1.0.0",
|
|
59
|
+
"@arcjet/body": "1.0.0",
|
|
60
|
+
"arcjet": "1.0.0"
|
|
58
61
|
},
|
|
59
62
|
"devDependencies": {
|
|
60
|
-
"@arcjet/eslint-config": "1.0.0
|
|
61
|
-
"@arcjet/rollup-config": "1.0.0
|
|
62
|
-
"@
|
|
63
|
-
"@
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"typescript": "5.8.3"
|
|
63
|
+
"@arcjet/eslint-config": "1.0.0",
|
|
64
|
+
"@arcjet/rollup-config": "1.0.0",
|
|
65
|
+
"@types/node": "25.0.8",
|
|
66
|
+
"@rollup/wasm-node": "4.55.1",
|
|
67
|
+
"eslint": "9.39.2",
|
|
68
|
+
"typescript": "5.9.3"
|
|
67
69
|
},
|
|
68
70
|
"publishConfig": {
|
|
69
71
|
"access": "public",
|