@arcjet/node 1.0.0-beta.9 → 1.1.0-rc
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 -73
- package/index.d.ts +147 -35
- package/index.js +62 -77
- package/package.json +21 -19
package/README.md
CHANGED
|
@@ -32,116 +32,99 @@ This is the [Arcjet][arcjet] SDK for [Node.js][node-js].
|
|
|
32
32
|
|
|
33
33
|
Visit the [quick start guide][quick-start] to get started.
|
|
34
34
|
|
|
35
|
-
## Example
|
|
35
|
+
## Example
|
|
36
36
|
|
|
37
|
-
Try an Arcjet protected app live at
|
|
38
|
-
|
|
37
|
+
Try an Arcjet protected Next.js app live at
|
|
38
|
+
[`example.arcjet.com`][example-next-url]
|
|
39
|
+
([source code][example-next-source]).
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
<!--
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
npm install -S @arcjet/node
|
|
44
|
-
```
|
|
43
|
+
TODO: Node.js example.
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
See [`arcjet/example-node`][example-astro-source] for a Node.js example.
|
|
47
46
|
|
|
48
|
-
|
|
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.
|
|
47
|
+
[example-node-source]: https://github.com/arcjet/example-node
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
-->
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
import arcjet, { tokenBucket, detectBot } from "@arcjet/node";
|
|
57
|
-
import http from "node:http";
|
|
51
|
+
## What is this?
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
This is our adapter to integrate Arcjet into Node.js.
|
|
54
|
+
Arcjet helps you secure your Node server.
|
|
55
|
+
This package exists so that we can provide the best possible experience to
|
|
56
|
+
Node users.
|
|
78
57
|
|
|
79
|
-
|
|
80
|
-
req: http.IncomingMessage,
|
|
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);
|
|
58
|
+
## When should I use this?
|
|
86
59
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
} else {
|
|
91
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
92
|
-
res.end(JSON.stringify({ message: "Hello world" }));
|
|
93
|
-
}
|
|
94
|
-
});
|
|
60
|
+
You can use this if you are using Node.js.
|
|
61
|
+
See our [_Get started_ guide][arcjet-get-started] for other supported
|
|
62
|
+
frameworks and runtimes.
|
|
95
63
|
|
|
96
|
-
|
|
97
|
-
```
|
|
64
|
+
## Install
|
|
98
65
|
|
|
99
|
-
|
|
66
|
+
This package is ESM only.
|
|
67
|
+
Install with npm in Node.js:
|
|
100
68
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
69
|
+
```sh
|
|
70
|
+
npm install @arcjet/node
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Use
|
|
104
74
|
|
|
105
75
|
```ts
|
|
106
|
-
import arcjet, { shield } from "@arcjet/node";
|
|
107
76
|
import http from "node:http";
|
|
77
|
+
import arcjet, { shield } from "@arcjet/node";
|
|
78
|
+
|
|
79
|
+
// Get your Arcjet key at <https://app.arcjet.com>.
|
|
80
|
+
// Set it as an environment variable instead of hard coding it.
|
|
81
|
+
const arcjetKey = process.env.ARCJET_KEY;
|
|
82
|
+
|
|
83
|
+
if (!arcjetKey) {
|
|
84
|
+
throw new Error("Cannot find `ARCJET_KEY` environment variable");
|
|
85
|
+
}
|
|
108
86
|
|
|
109
87
|
const aj = arcjet({
|
|
110
|
-
key:
|
|
88
|
+
key: arcjetKey,
|
|
111
89
|
rules: [
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}),
|
|
90
|
+
// Shield protects your app from common attacks.
|
|
91
|
+
// Use `DRY_RUN` instead of `LIVE` to only log.
|
|
92
|
+
shield({ mode: "LIVE" }),
|
|
115
93
|
],
|
|
116
94
|
});
|
|
117
95
|
|
|
118
96
|
const server = http.createServer(async function (
|
|
119
|
-
|
|
120
|
-
|
|
97
|
+
request: http.IncomingMessage,
|
|
98
|
+
response: http.ServerResponse,
|
|
121
99
|
) {
|
|
122
|
-
const decision = await aj.protect(
|
|
100
|
+
const decision = await aj.protect(request);
|
|
123
101
|
|
|
124
102
|
if (decision.isDenied()) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
129
|
-
res.end(JSON.stringify({ message: "Hello world" }));
|
|
103
|
+
response.writeHead(403, { "Content-Type": "application/json" });
|
|
104
|
+
response.end(JSON.stringify({ message: "Forbidden" }));
|
|
105
|
+
return;
|
|
130
106
|
}
|
|
107
|
+
|
|
108
|
+
response.writeHead(200, { "Content-Type": "application/json" });
|
|
109
|
+
response.end(JSON.stringify({ message: "Hello world" }));
|
|
131
110
|
});
|
|
132
111
|
|
|
133
112
|
server.listen(8000);
|
|
134
113
|
```
|
|
135
114
|
|
|
115
|
+
For more on how to configure Arcjet with Node.js and how to protect Node,
|
|
116
|
+
see the [Arcjet Node.js SDK reference][arcjet-reference-node] on our website.
|
|
117
|
+
|
|
136
118
|
## License
|
|
137
119
|
|
|
138
|
-
|
|
120
|
+
[Apache License, Version 2.0][apache-license] © [Arcjet Labs, Inc.][arcjet]
|
|
139
121
|
|
|
122
|
+
[arcjet-get-started]: https://docs.arcjet.com/get-started
|
|
123
|
+
[arcjet-reference-node]: https://docs.arcjet.com/reference/nodejs
|
|
140
124
|
[arcjet]: https://arcjet.com
|
|
125
|
+
[example-next-source]: https://github.com/arcjet/example-nextjs
|
|
126
|
+
[example-next-url]: https://example.arcjet.com
|
|
141
127
|
[node-js]: https://nodejs.org/
|
|
142
128
|
[alt-sdk]: https://www.npmjs.com/package/@arcjet/next
|
|
143
|
-
[example-url]: https://example.arcjet.com
|
|
144
129
|
[quick-start]: https://docs.arcjet.com/get-started/nodejs
|
|
145
|
-
[example-source]: https://github.com/arcjet/arcjet-js-example
|
|
146
|
-
[shield-concepts-docs]: https://docs.arcjet.com/shield/concepts
|
|
147
130
|
[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.
|
|
59
|
+
const sdkVersion = "1.1.0-rc";
|
|
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.
|
|
3
|
+
"version": "1.1.0-rc",
|
|
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",
|
|
@@ -43,27 +43,29 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "rollup --config rollup.config.js",
|
|
45
45
|
"lint": "eslint .",
|
|
46
|
-
"
|
|
47
|
-
"test": "
|
|
46
|
+
"test-api": "node --test -- test/*.test.js",
|
|
47
|
+
"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`",
|
|
48
|
+
"test-coverage": "node --experimental-test-coverage --test -- test/*.test.js",
|
|
49
|
+
"test": "npm run build && npm run lint && npm run test-coverage"
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
|
-
"@arcjet/env": "1.
|
|
51
|
-
"@arcjet/headers": "1.
|
|
52
|
-
"@arcjet/ip": "1.
|
|
53
|
-
"@arcjet/logger": "1.
|
|
54
|
-
"@arcjet/protocol": "1.
|
|
55
|
-
"@arcjet/transport": "1.
|
|
56
|
-
"@arcjet/body": "1.
|
|
57
|
-
"arcjet": "1.
|
|
52
|
+
"@arcjet/env": "1.1.0-rc",
|
|
53
|
+
"@arcjet/headers": "1.1.0-rc",
|
|
54
|
+
"@arcjet/ip": "1.1.0-rc",
|
|
55
|
+
"@arcjet/logger": "1.1.0-rc",
|
|
56
|
+
"@arcjet/protocol": "1.1.0-rc",
|
|
57
|
+
"@arcjet/transport": "1.1.0-rc",
|
|
58
|
+
"@arcjet/body": "1.1.0-rc",
|
|
59
|
+
"arcjet": "1.1.0-rc"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
|
-
"@arcjet/
|
|
61
|
-
"@arcjet/
|
|
62
|
-
"@arcjet/
|
|
63
|
-
"@types/node": "
|
|
64
|
-
"@rollup/wasm-node": "4.
|
|
65
|
-
"eslint": "9.
|
|
66
|
-
"typescript": "5.
|
|
62
|
+
"@arcjet/cache": "1.1.0-rc",
|
|
63
|
+
"@arcjet/eslint-config": "1.1.0-rc",
|
|
64
|
+
"@arcjet/rollup-config": "1.1.0-rc",
|
|
65
|
+
"@types/node": "25.0.10",
|
|
66
|
+
"@rollup/wasm-node": "4.57.0",
|
|
67
|
+
"eslint": "9.39.2",
|
|
68
|
+
"typescript": "5.9.3"
|
|
67
69
|
},
|
|
68
70
|
"publishConfig": {
|
|
69
71
|
"access": "public",
|