@arcjet/node 1.0.0-beta.1 → 1.0.0-beta.10
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 +46 -71
- package/index.js +46 -9
- package/package.json +31 -23
package/README.md
CHANGED
|
@@ -25,6 +25,9 @@ This is the [Arcjet][arcjet] SDK for [Node.js][node-js].
|
|
|
25
25
|
**Looking for our Next.js framework SDK?** Check out the
|
|
26
26
|
[`@arcjet/next`][alt-sdk] package.
|
|
27
27
|
|
|
28
|
+
- [npm package (`@arcjet/node`)](https://www.npmjs.com/package/@arcjet/node)
|
|
29
|
+
- [GitHub source code (`arcjet-node/` in `arcjet/arcjet-js`)](https://github.com/arcjet/arcjet-js/tree/main/arcjet-node)
|
|
30
|
+
|
|
28
31
|
## Getting started
|
|
29
32
|
|
|
30
33
|
Visit the [quick start guide][quick-start] to get started.
|
|
@@ -34,111 +37,83 @@ Visit the [quick start guide][quick-start] to get started.
|
|
|
34
37
|
Try an Arcjet protected app live at [https://example.arcjet.com][example-url]
|
|
35
38
|
([source code][example-source]).
|
|
36
39
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
```shell
|
|
40
|
-
npm install -S @arcjet/node
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Rate limit example
|
|
44
|
-
|
|
45
|
-
The example below applies a token bucket rate limit rule to a route where we
|
|
46
|
-
identify the user based on their ID e.g. if they are logged in. The bucket is
|
|
47
|
-
configured with a maximum capacity of 10 tokens and refills by 5 tokens every 10
|
|
48
|
-
seconds. Each request consumes 5 tokens.
|
|
40
|
+
## What is this?
|
|
49
41
|
|
|
50
|
-
|
|
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.
|
|
51
46
|
|
|
52
|
-
|
|
53
|
-
import arcjet, { tokenBucket, detectBot } from "@arcjet/node";
|
|
54
|
-
import http from "node:http";
|
|
47
|
+
## When should I use this?
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
rules: [
|
|
60
|
-
// Create a token bucket rate limit. Other algorithms are supported.
|
|
61
|
-
tokenBucket({
|
|
62
|
-
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
|
|
63
|
-
refillRate: 5, // refill 5 tokens per interval
|
|
64
|
-
interval: 10, // refill every 10 seconds
|
|
65
|
-
capacity: 10, // bucket maximum capacity of 10 tokens
|
|
66
|
-
}),
|
|
67
|
-
detectBot({
|
|
68
|
-
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
|
|
69
|
-
// configured with a list of bots to allow from
|
|
70
|
-
// https://arcjet.com/bot-list
|
|
71
|
-
allow: [], // "allow none" will block all detected bots
|
|
72
|
-
}),
|
|
73
|
-
],
|
|
74
|
-
});
|
|
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.
|
|
75
52
|
|
|
76
|
-
|
|
77
|
-
req: http.IncomingMessage,
|
|
78
|
-
res: http.ServerResponse,
|
|
79
|
-
) {
|
|
80
|
-
const userId = "user123"; // Replace with your authenticated user ID
|
|
81
|
-
const decision = await aj.protect(req, { userId, requested: 5 }); // Deduct 5 tokens from the bucket
|
|
82
|
-
console.log("Arcjet decision", decision);
|
|
53
|
+
## Install
|
|
83
54
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
87
|
-
} else {
|
|
88
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
89
|
-
res.end(JSON.stringify({ message: "Hello world" }));
|
|
90
|
-
}
|
|
91
|
-
});
|
|
55
|
+
This package is ESM only.
|
|
56
|
+
Install with npm in Node.js:
|
|
92
57
|
|
|
93
|
-
|
|
58
|
+
```sh
|
|
59
|
+
npm install @arcjet/node
|
|
94
60
|
```
|
|
95
61
|
|
|
96
|
-
##
|
|
97
|
-
|
|
98
|
-
[Arcjet Shield][shield-concepts-docs] protects your application against common
|
|
99
|
-
attacks, including the OWASP Top 10. You can run Shield on every request with
|
|
100
|
-
negligible performance impact.
|
|
62
|
+
## Use
|
|
101
63
|
|
|
102
64
|
```ts
|
|
103
|
-
import arcjet, { shield } from "@arcjet/node";
|
|
104
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
|
+
}
|
|
105
75
|
|
|
106
76
|
const aj = arcjet({
|
|
107
|
-
key:
|
|
77
|
+
key: arcjetKey,
|
|
108
78
|
rules: [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}),
|
|
79
|
+
// Shield protects your app from common attacks.
|
|
80
|
+
// Use `DRY_RUN` instead of `LIVE` to only log.
|
|
81
|
+
shield({ mode: "LIVE" }),
|
|
112
82
|
],
|
|
113
83
|
});
|
|
114
84
|
|
|
115
85
|
const server = http.createServer(async function (
|
|
116
|
-
|
|
117
|
-
|
|
86
|
+
request: http.IncomingMessage,
|
|
87
|
+
response: http.ServerResponse,
|
|
118
88
|
) {
|
|
119
|
-
const decision = await aj.protect(
|
|
89
|
+
const decision = await aj.protect(request);
|
|
120
90
|
|
|
121
91
|
if (decision.isDenied()) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
126
|
-
res.end(JSON.stringify({ message: "Hello world" }));
|
|
92
|
+
response.writeHead(403, { "Content-Type": "application/json" });
|
|
93
|
+
response.end(JSON.stringify({ message: "Forbidden" }));
|
|
94
|
+
return;
|
|
127
95
|
}
|
|
96
|
+
|
|
97
|
+
response.writeHead(200, { "Content-Type": "application/json" });
|
|
98
|
+
response.end(JSON.stringify({ message: "Hello world" }));
|
|
128
99
|
});
|
|
129
100
|
|
|
130
101
|
server.listen(8000);
|
|
131
102
|
```
|
|
132
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
|
+
|
|
133
107
|
## License
|
|
134
108
|
|
|
135
|
-
|
|
109
|
+
[Apache License, Version 2.0][apache-license] © [Arcjet Labs, Inc.][arcjet]
|
|
136
110
|
|
|
111
|
+
[arcjet-get-started]: https://docs.arcjet.com/get-started
|
|
112
|
+
[arcjet-reference-node]: https://docs.arcjet.com/reference/nodejs
|
|
137
113
|
[arcjet]: https://arcjet.com
|
|
138
114
|
[node-js]: https://nodejs.org/
|
|
139
115
|
[alt-sdk]: https://www.npmjs.com/package/@arcjet/next
|
|
140
116
|
[example-url]: https://example.arcjet.com
|
|
141
117
|
[quick-start]: https://docs.arcjet.com/get-started/nodejs
|
|
142
118
|
[example-source]: https://github.com/arcjet/arcjet-js-example
|
|
143
|
-
[shield-concepts-docs]: https://docs.arcjet.com/shield/concepts
|
|
144
119
|
[apache-license]: http://www.apache.org/licenses/LICENSE-2.0
|
package/index.js
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
import core__default from 'arcjet';
|
|
2
2
|
export * from 'arcjet';
|
|
3
|
-
import findIP from '@arcjet/ip';
|
|
3
|
+
import findIP, { parseProxy } from '@arcjet/ip';
|
|
4
4
|
import ArcjetHeaders from '@arcjet/headers';
|
|
5
|
-
import { logLevel,
|
|
5
|
+
import { logLevel, isDevelopment, baseUrl, platform } from '@arcjet/env';
|
|
6
6
|
import { Logger } from '@arcjet/logger';
|
|
7
7
|
import { createClient } from '@arcjet/protocol/client.js';
|
|
8
8
|
import { createTransport } from '@arcjet/transport';
|
|
9
9
|
import { readBody } from '@arcjet/body';
|
|
10
10
|
|
|
11
|
+
// An object with getters that access the `process.env.SOMEVAR` values directly.
|
|
12
|
+
// This allows bundlers to replace the dot-notation access with string literals
|
|
13
|
+
// while still allowing dynamic access in runtime environments.
|
|
14
|
+
const env = {
|
|
15
|
+
get FLY_APP_NAME() {
|
|
16
|
+
return process.env.FLY_APP_NAME;
|
|
17
|
+
},
|
|
18
|
+
get VERCEL() {
|
|
19
|
+
return process.env.VERCEL;
|
|
20
|
+
},
|
|
21
|
+
get RENDER() {
|
|
22
|
+
return process.env.RENDER;
|
|
23
|
+
},
|
|
24
|
+
get MODE() {
|
|
25
|
+
return process.env.MODE;
|
|
26
|
+
},
|
|
27
|
+
get NODE_ENV() {
|
|
28
|
+
return process.env.NODE_ENV;
|
|
29
|
+
},
|
|
30
|
+
get ARCJET_KEY() {
|
|
31
|
+
return process.env.ARCJET_KEY;
|
|
32
|
+
},
|
|
33
|
+
get ARCJET_ENV() {
|
|
34
|
+
return process.env.ARCJET_ENV;
|
|
35
|
+
},
|
|
36
|
+
get ARCJET_LOG_LEVEL() {
|
|
37
|
+
return process.env.ARCJET_LOG_LEVEL;
|
|
38
|
+
},
|
|
39
|
+
get ARCJET_BASE_URL() {
|
|
40
|
+
return process.env.ARCJET_BASE_URL;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
11
43
|
// TODO: Deduplicate with other packages
|
|
12
44
|
function errorMessage(err) {
|
|
13
45
|
if (err) {
|
|
@@ -25,14 +57,14 @@ function errorMessage(err) {
|
|
|
25
57
|
function createRemoteClient(options) {
|
|
26
58
|
// The base URL for the Arcjet API. Will default to the standard production
|
|
27
59
|
// API unless environment variable `ARCJET_BASE_URL` is set.
|
|
28
|
-
const url = options?.baseUrl ?? baseUrl(
|
|
60
|
+
const url = options?.baseUrl ?? baseUrl(env);
|
|
29
61
|
// The timeout for the Arcjet API in milliseconds. This is set to a low value
|
|
30
62
|
// in production so calls fail open.
|
|
31
|
-
const timeout = options?.timeout ?? (isDevelopment(
|
|
63
|
+
const timeout = options?.timeout ?? (isDevelopment(env) ? 1000 : 500);
|
|
32
64
|
// Transport is the HTTP client that the client uses to make requests.
|
|
33
65
|
const transport = createTransport(url);
|
|
34
66
|
const sdkStack = "NODEJS";
|
|
35
|
-
const sdkVersion = "1.0.0-beta.
|
|
67
|
+
const sdkVersion = "1.0.0-beta.10";
|
|
36
68
|
return createClient({
|
|
37
69
|
transport,
|
|
38
70
|
baseUrl: url,
|
|
@@ -64,8 +96,14 @@ function arcjet(options) {
|
|
|
64
96
|
const log = options.log
|
|
65
97
|
? options.log
|
|
66
98
|
: new Logger({
|
|
67
|
-
level: logLevel(
|
|
99
|
+
level: logLevel(env),
|
|
68
100
|
});
|
|
101
|
+
const proxies = Array.isArray(options.proxies)
|
|
102
|
+
? options.proxies.map(parseProxy)
|
|
103
|
+
: undefined;
|
|
104
|
+
if (isDevelopment(env)) {
|
|
105
|
+
log.warn("Arcjet will use 127.0.0.1 when missing public IP address in development mode");
|
|
106
|
+
}
|
|
69
107
|
function toArcjetRequest(request, props) {
|
|
70
108
|
// We pull the cookies from the request before wrapping them in ArcjetHeaders
|
|
71
109
|
const cookies = cookiesToString(request.headers?.cookie);
|
|
@@ -74,12 +112,11 @@ function arcjet(options) {
|
|
|
74
112
|
let ip = findIP({
|
|
75
113
|
socket: request.socket,
|
|
76
114
|
headers,
|
|
77
|
-
}, { platform: platform(
|
|
115
|
+
}, { platform: platform(env), proxies });
|
|
78
116
|
if (ip === "") {
|
|
79
117
|
// If the `ip` is empty but we're in development mode, we default the IP
|
|
80
118
|
// so the request doesn't fail.
|
|
81
|
-
if (isDevelopment(
|
|
82
|
-
log.warn("Using 127.0.0.1 as IP address in development mode");
|
|
119
|
+
if (isDevelopment(env)) {
|
|
83
120
|
ip = "127.0.0.1";
|
|
84
121
|
}
|
|
85
122
|
else {
|
package/package.json
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcjet/node",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.10",
|
|
4
4
|
"description": "Arcjet SDK for Node.js",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"analyze",
|
|
7
|
+
"arcjet",
|
|
8
|
+
"attack",
|
|
9
|
+
"limit",
|
|
10
|
+
"nodejs",
|
|
11
|
+
"node",
|
|
12
|
+
"protect",
|
|
13
|
+
"secure",
|
|
14
|
+
"security",
|
|
15
|
+
"verify"
|
|
16
|
+
],
|
|
5
17
|
"license": "Apache-2.0",
|
|
6
18
|
"homepage": "https://arcjet.com",
|
|
7
19
|
"repository": {
|
|
@@ -25,37 +37,33 @@
|
|
|
25
37
|
"main": "./index.js",
|
|
26
38
|
"types": "./index.d.ts",
|
|
27
39
|
"files": [
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"*.js",
|
|
31
|
-
"*.d.ts",
|
|
32
|
-
"!*.config.js"
|
|
40
|
+
"index.d.ts",
|
|
41
|
+
"index.js"
|
|
33
42
|
],
|
|
34
43
|
"scripts": {
|
|
35
|
-
"prepublishOnly": "npm run build",
|
|
36
44
|
"build": "rollup --config rollup.config.js",
|
|
37
45
|
"lint": "eslint .",
|
|
38
|
-
"
|
|
39
|
-
"test": "
|
|
46
|
+
"prepublishOnly": "npm run build",
|
|
47
|
+
"test": "npm run build && npm run lint"
|
|
40
48
|
},
|
|
41
49
|
"dependencies": {
|
|
42
|
-
"@arcjet/env": "1.0.0-beta.
|
|
43
|
-
"@arcjet/headers": "1.0.0-beta.
|
|
44
|
-
"@arcjet/ip": "1.0.0-beta.
|
|
45
|
-
"@arcjet/logger": "1.0.0-beta.
|
|
46
|
-
"@arcjet/protocol": "1.0.0-beta.
|
|
47
|
-
"@arcjet/transport": "1.0.0-beta.
|
|
48
|
-
"@arcjet/body": "1.0.0-beta.
|
|
49
|
-
"arcjet": "1.0.0-beta.
|
|
50
|
+
"@arcjet/env": "1.0.0-beta.10",
|
|
51
|
+
"@arcjet/headers": "1.0.0-beta.10",
|
|
52
|
+
"@arcjet/ip": "1.0.0-beta.10",
|
|
53
|
+
"@arcjet/logger": "1.0.0-beta.10",
|
|
54
|
+
"@arcjet/protocol": "1.0.0-beta.10",
|
|
55
|
+
"@arcjet/transport": "1.0.0-beta.10",
|
|
56
|
+
"@arcjet/body": "1.0.0-beta.10",
|
|
57
|
+
"arcjet": "1.0.0-beta.10"
|
|
50
58
|
},
|
|
51
59
|
"devDependencies": {
|
|
52
|
-
"@arcjet/eslint-config": "1.0.0-beta.
|
|
53
|
-
"@arcjet/rollup-config": "1.0.0-beta.
|
|
54
|
-
"@arcjet/tsconfig": "1.0.0-beta.
|
|
60
|
+
"@arcjet/eslint-config": "1.0.0-beta.10",
|
|
61
|
+
"@arcjet/rollup-config": "1.0.0-beta.10",
|
|
62
|
+
"@arcjet/tsconfig": "1.0.0-beta.10",
|
|
55
63
|
"@types/node": "18.18.0",
|
|
56
|
-
"@rollup/wasm-node": "4.
|
|
57
|
-
"
|
|
58
|
-
"typescript": "5.
|
|
64
|
+
"@rollup/wasm-node": "4.46.2",
|
|
65
|
+
"eslint": "9.32.0",
|
|
66
|
+
"typescript": "5.9.2"
|
|
59
67
|
},
|
|
60
68
|
"publishConfig": {
|
|
61
69
|
"access": "public",
|