@arcjet/astro 1.7.0 → 1.8.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/index.js DELETED
@@ -1,681 +0,0 @@
1
- import { z } from 'astro/zod';
2
- import fs from 'node:fs/promises';
3
- export { cloudflare } from '@arcjet/ip';
4
-
5
- const resolvedVirtualClientId = "\0ARCJET_VIRTUAL_CLIENT";
6
- const validateMode = z.enum(["LIVE", "DRY_RUN"]);
7
- // A proxy service (such as the one created by `cloudflare()`) is a plain,
8
- // JSON-serializable object, so it survives being injected into the generated
9
- // client config. Ranges must be strings here because parsed `Cidr` objects
10
- // would not serialize; `findIp` parses these strings at runtime.
11
- const validateProxyService = z.object({
12
- kind: z.literal("service"),
13
- name: z.string(),
14
- ranges: z.array(z.string()),
15
- clientIp: z.array(z.object({ header: z.string(), format: z.enum(["ip", "ips"]) })),
16
- });
17
- const validateProxies = z.array(z.union([z.string(), validateProxyService]));
18
- const validateCharacteristics = z.array(z.string());
19
- const validateClientOptions = z
20
- .object({
21
- baseUrl: z.string().optional(),
22
- timeout: z.number().optional(),
23
- })
24
- .strict()
25
- .optional();
26
- // TODO: once `arcjet` core has `exactOptionalProperties` we can use
27
- // `satisfies z.ZodType<ShieldOptions>` and such here.
28
- const validateShieldOptions = z
29
- .object({
30
- mode: validateMode.optional(),
31
- })
32
- .strict();
33
- const validateBotOptions = z.union([
34
- z
35
- .object({
36
- mode: validateMode.optional(),
37
- allow: z.array(z.string()),
38
- })
39
- .strict(),
40
- z
41
- .object({
42
- mode: validateMode.optional(),
43
- deny: z.array(z.string()),
44
- })
45
- .strict(),
46
- ]);
47
- const validateEmailOptions = z.union([
48
- z
49
- .object({
50
- mode: validateMode.optional(),
51
- allow: z.array(z.string()),
52
- requireTopLevelDomain: z.boolean().optional(),
53
- allowDomainLiteral: z.boolean().optional(),
54
- })
55
- .strict(),
56
- z
57
- .object({
58
- mode: validateMode.optional(),
59
- deny: z.array(z.string()),
60
- requireTopLevelDomain: z.boolean().optional(),
61
- allowDomainLiteral: z.boolean().optional(),
62
- })
63
- .strict(),
64
- ]);
65
- const validateFilterOptions = z.union([
66
- z
67
- .object({
68
- mode: validateMode.optional(),
69
- allow: z.array(z.string()),
70
- })
71
- .strict(),
72
- z
73
- .object({
74
- mode: validateMode.optional(),
75
- deny: z.array(z.string()),
76
- })
77
- .strict(),
78
- ]);
79
- const validateSensitiveInfoOptions = z.union([
80
- z
81
- .object({
82
- mode: validateMode.optional(),
83
- allow: z.array(z.string()),
84
- contextWindowSize: z.number().optional(),
85
- })
86
- .strict(),
87
- z
88
- .object({
89
- mode: validateMode.optional(),
90
- deny: z.array(z.string()),
91
- contextWindowSize: z.number().optional(),
92
- })
93
- .strict(),
94
- ]);
95
- const validateFixedWindowOptions = z
96
- .object({
97
- mode: validateMode.optional(),
98
- characteristics: validateCharacteristics.optional(),
99
- window: z.union([z.string(), z.number()]),
100
- max: z.number(),
101
- })
102
- .strict();
103
- const validateSlidingWindowOptions = z
104
- .object({
105
- mode: validateMode.optional(),
106
- characteristics: validateCharacteristics.optional(),
107
- interval: z.union([z.string(), z.number()]),
108
- max: z.number(),
109
- })
110
- .strict();
111
- const validateTokenBucketOptions = z
112
- .object({
113
- mode: validateMode.optional(),
114
- characteristics: validateCharacteristics.optional(),
115
- refillRate: z.number(),
116
- interval: z.union([z.string(), z.number()]),
117
- capacity: z.number(),
118
- })
119
- .strict();
120
- const validateProtectSignupOptions = z
121
- .object({
122
- rateLimit: validateSlidingWindowOptions,
123
- bots: validateBotOptions,
124
- email: validateEmailOptions,
125
- })
126
- .strict();
127
- const validateDetectPromptInjectionOptions = z
128
- .object({
129
- mode: validateMode.optional(),
130
- threshold: z.number().optional(),
131
- })
132
- .strict();
133
- function validateAndSerialize(schema, value) {
134
- const v = schema.parse(value);
135
- return v ? JSON.stringify(v) : "";
136
- }
137
- function integrationRuleToClientRule(rule) {
138
- switch (rule.type) {
139
- case "shield": {
140
- const serializedOpts = validateAndSerialize(validateShieldOptions, rule.options);
141
- return {
142
- importName: `shield`,
143
- code: `shield(${serializedOpts})`,
144
- };
145
- }
146
- case "bot": {
147
- const serializedOpts = validateAndSerialize(validateBotOptions, rule.options);
148
- return {
149
- importName: `detectBot`,
150
- code: `detectBot(${serializedOpts})`,
151
- };
152
- }
153
- case "email": {
154
- const serializedOpts = validateAndSerialize(validateEmailOptions, rule.options);
155
- return {
156
- importName: `validateEmail`,
157
- code: `validateEmail(${serializedOpts})`,
158
- };
159
- }
160
- case "filter": {
161
- const serializedOpts = validateAndSerialize(validateFilterOptions, rule.options);
162
- return {
163
- importName: `filter`,
164
- code: `filter(${serializedOpts})`,
165
- };
166
- }
167
- case "sensitiveInfo": {
168
- const serializedOpts = validateAndSerialize(validateSensitiveInfoOptions, rule.options);
169
- return {
170
- importName: `sensitiveInfo`,
171
- code: `sensitiveInfo(${serializedOpts})`,
172
- };
173
- }
174
- case "fixedWindow": {
175
- const serializedOpts = validateAndSerialize(validateFixedWindowOptions, rule.options);
176
- return {
177
- importName: `fixedWindow`,
178
- code: `fixedWindow(${serializedOpts})`,
179
- };
180
- }
181
- case "slidingWindow": {
182
- const serializedOpts = validateAndSerialize(validateSlidingWindowOptions, rule.options);
183
- return {
184
- importName: `slidingWindow`,
185
- code: `slidingWindow(${serializedOpts})`,
186
- };
187
- }
188
- case "tokenBucket": {
189
- const serializedOpts = validateAndSerialize(validateTokenBucketOptions, rule.options);
190
- return {
191
- importName: `tokenBucket`,
192
- code: `tokenBucket(${serializedOpts})`,
193
- };
194
- }
195
- case "protectSignup": {
196
- const serializedOpts = validateAndSerialize(validateProtectSignupOptions, rule.options);
197
- return {
198
- importName: `protectSignup`,
199
- code: `protectSignup(${serializedOpts})`,
200
- };
201
- }
202
- case "detectPromptInjection": {
203
- const serializedOpts = validateAndSerialize(validateDetectPromptInjectionOptions, rule.options);
204
- return {
205
- importName: `detectPromptInjection`,
206
- code: `detectPromptInjection(${serializedOpts})`,
207
- };
208
- }
209
- default: {
210
- throw new Error("Cannot convert rule via integration");
211
- }
212
- }
213
- }
214
- // Mirror the rule functions in the SDK but produce serializable rules
215
- // Note: please keep JSDocs in sync with `arcjet` core.
216
- /**
217
- * Arcjet Shield WAF rule.
218
- *
219
- * Applying this rule protects your application against common attacks,
220
- * including the OWASP Top 10.
221
- *
222
- * The Arcjet Shield WAF analyzes every request to your application to detect
223
- * suspicious activity.
224
- * Once a certain suspicion threshold is reached,
225
- * subsequent requests from that client are blocked for a period of time.
226
- *
227
- * @param options
228
- * Configuration for the Shield rule.
229
- * @returns
230
- * Astro integration Shield rule to provide to the SDK in the `rules` field.
231
- */
232
- function shield(options) {
233
- return { type: "shield", options };
234
- }
235
- // Note: please keep JSDocs in sync with `arcjet` core.
236
- /**
237
- * Arcjet bot detection rule.
238
- *
239
- * Applying this rule allows you to manage traffic by automated clients and
240
- * bots.
241
- *
242
- * Bots can be good (such as search engine crawlers or monitoring agents) or bad
243
- * (such as scrapers or automated scripts).
244
- * Arcjet allows you to configure which bots you want to allow or deny by
245
- * specific bot names such as curl, as well as by category such as search
246
- * engine bots.
247
- *
248
- * Bots are detected based on various signals such as the user agent, IP
249
- * address, DNS records, and more.
250
- *
251
- * @param options
252
- * Configuration for the bot rule (required).
253
- * @returns
254
- * Astro integration Bot rule to provide to the SDK in the `rules` field.
255
- */
256
- function detectBot(options) {
257
- return { type: "bot", options };
258
- }
259
- // Note: please keep JSDocs in sync with `arcjet` core.
260
- /**
261
- * Arcjet email validation rule.
262
- *
263
- * Applying this rule allows you to validate and verify an email address.
264
- *
265
- * The first step of the analysis is to validate the email address syntax.
266
- * This runs locally within the SDK and validates the email address is in the
267
- * correct format.
268
- * If the email syntax is valid, the SDK will pass the email address to the
269
- * Arcjet cloud API to verify the email address.
270
- * This performs several checks, depending on the rule configuration.
271
- *
272
- * @param options
273
- * Configuration for the email validation rule (required).
274
- * @returns
275
- * Astro integration Email rule to provide to the SDK in the `rules` field.
276
- */
277
- function validateEmail(options) {
278
- return { type: "email", options };
279
- }
280
- // Note: please keep JSDocs in sync with `arcjet` core.
281
- /**
282
- * Arcjet filter rule.
283
- *
284
- * Applying this rule lets you block requests using Wireshark-like display
285
- * filter expressions over HTTP headers, IP addresses, and other request
286
- * fields.
287
- * You can quickly enforce rules like allow/deny by country, network, or
288
- * `user-agent` pattern.
289
- *
290
- * See the [reference guide](https://docs.arcjet.com/filters/reference) for
291
- * more info on the expression language fields, functions, and values.
292
- *
293
- * @param options
294
- * Configuration (required).
295
- * @returns
296
- * Astro integration Filter rule to provide to the SDK in the `rules` field.
297
- *
298
- * @example
299
- * In this example, the expression matches non-VPN GET requests from the US.
300
- * Requests matching the expression are allowed, all others are denied.
301
- *
302
- * ```ts
303
- * filter({
304
- * allow: [
305
- * 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn',
306
- * ],
307
- * mode: "LIVE",
308
- * })
309
- * ```
310
- *
311
- * @link https://docs.arcjet.com/filters/reference
312
- */
313
- function filter(options) {
314
- return { type: "filter", options };
315
- }
316
- // Note: please keep JSDocs in sync with `arcjet` core.
317
- /**
318
- * Arcjet sensitive information detection rule.
319
- *
320
- * Applying this rule protects against clients sending you sensitive information
321
- * such as personally identifiable information (PII) that you do not wish to
322
- * handle.
323
- * The rule runs entirely locally so no data ever leaves your environment.
324
- *
325
- * This rule includes built-in detections for email addresses, credit/debit card
326
- * numbers, IP addresses, and phone numbers.
327
- * You can also provide a custom detection function to identify additional
328
- * sensitive information.
329
- *
330
- * @param options
331
- * Configuration for the sensitive information detection rule (required).
332
- * @returns
333
- * Astro integration Sensitive information rule to provide to the SDK in the `rules` field.
334
- */
335
- function sensitiveInfo(options) {
336
- return { type: "sensitiveInfo", options };
337
- }
338
- // Note: please keep JSDocs in sync with `arcjet` core.
339
- /**
340
- * Arcjet fixed window rate limiting rule.
341
- *
342
- * Applying this rule sets a fixed window rate limit which tracks the number of
343
- * requests made by a client over a fixed time window.
344
- *
345
- * This is the simplest algorithm.
346
- * It tracks the number of requests made by a client over a fixed time window
347
- * such as 60 seconds.
348
- * If the client exceeds the limit, they are blocked until the window expires.
349
- *
350
- * This algorithm is useful when you want to apply a simple fixed limit in a
351
- * fixed time window.
352
- * For example, a simple limit on the total number of requests a client can make.
353
- * However, it can be susceptible to the stampede problem where a client makes
354
- * a burst of requests at the start of a window and then is blocked for the rest
355
- * of the window.
356
- * The sliding window algorithm can be used to avoid this.
357
- *
358
- * @template Characteristics
359
- * Characteristics to track a user by.
360
- * @param options
361
- * Configuration for the fixed window rate limiting rule (required).
362
- * @returns
363
- * Astro integration Fixed window rule to provide to the SDK in the `rules` field.
364
- */
365
- function fixedWindow(options) {
366
- return {
367
- type: "fixedWindow",
368
- options,
369
- };
370
- }
371
- // Note: please keep JSDocs in sync with `arcjet` core.
372
- /**
373
- * Arcjet sliding window rate limiting rule.
374
- *
375
- * Applying this rule sets a sliding window rate limit which tracks the number
376
- * of requests made by a client over a sliding window so that the window moves
377
- * with time.
378
- *
379
- * This algorithm is useful to avoid the stampede problem of the fixed window.
380
- * It provides smoother rate limiting over time and can prevent a client from
381
- * making a burst of requests at the start of a window and then being blocked
382
- * for the rest of the window.
383
- *
384
- * @template Characteristics
385
- * Characteristics to track a user by.
386
- * @param options
387
- * Configuration for the sliding window rate limiting rule (required).
388
- * @returns
389
- * Astro integration Sliding window rule to provide to the SDK in the `rules` field.
390
- */
391
- function slidingWindow(options) {
392
- return {
393
- type: "slidingWindow",
394
- options,
395
- };
396
- }
397
- // Note: please keep JSDocs in sync with `arcjet` core.
398
- /**
399
- * Arcjet token bucket rate limiting rule.
400
- *
401
- * Applying this rule sets a token bucket rate limit.
402
- *
403
- * This algorithm is based on a bucket filled with a specific number of tokens.
404
- * Each request withdraws some amount of tokens from the bucket and the bucket
405
- * is refilled at a fixed rate.
406
- * Once the bucket is empty, the client is blocked until the bucket refills.
407
- *
408
- * This algorithm is useful when you want to allow clients to make a burst of
409
- * requests and then still be able to make requests at a slower rate.
410
- *
411
- * @template Characteristics
412
- * Characteristics to track a user by.
413
- * @param options
414
- * Configuration for the token bucket rate limiting rule (required).
415
- * @returns
416
- * Astro integration Token bucket rule to provide to the SDK in the `rules` field.
417
- */
418
- function tokenBucket(options) {
419
- return {
420
- type: "tokenBucket",
421
- options,
422
- };
423
- }
424
- // Note: please keep JSDocs in sync with `arcjet` core.
425
- /**
426
- * Arcjet signup form protection rule.
427
- *
428
- * Applying this rule combines rate limiting, bot protection, and email
429
- * validation to protect your signup forms from abuse.
430
- * Using this rule will configure the following:
431
- *
432
- * - Rate limiting - signup forms are a common target for bots. Arcjet’s rate
433
- * limiting helps to prevent bots and other automated or malicious clients
434
- * from submitting your signup form too many times in a short period of time.
435
- * - Bot protection - signup forms are usually exclusively used by humans, which
436
- * means that any automated submissions to the form are likely to be
437
- * fraudulent.
438
- * - Email validation - email addresses should be validated to ensure the signup
439
- * is coming from a legitimate user with a real email address that can
440
- * actually receive messages.
441
- *
442
- * @template Characteristics
443
- * Characteristics to track a user by.
444
- * @param options
445
- * Configuration for the signup form protection rule.
446
- * @returns
447
- * Astro integration Signup form protection rule to provide to the SDK in the `rules` field.
448
- */
449
- function protectSignup(options) {
450
- return {
451
- type: "protectSignup",
452
- options,
453
- };
454
- }
455
- // Note: please keep JSDocs in sync with `arcjet` core.
456
- /**
457
- * Arcjet prompt injection detection rule.
458
- *
459
- * Analyzes LLM prompts to detect prompt injection attempts.
460
- *
461
- * The Arcjet prompt injection detection rule analyzes prompts sent to LLM
462
- * applications to detect jailbreak and injection attempts.
463
- * The analysis is performed through Arcjet's Cloud API.
464
- *
465
- * @param options
466
- * Configuration for the prompt injection detection rule.
467
- * @returns
468
- * Astro integration Prompt injection detection rule to provide to the SDK in the `rules` field.
469
- */
470
- function detectPromptInjection(options = {}) {
471
- return {
472
- type: "detectPromptInjection",
473
- options,
474
- };
475
- }
476
- /**
477
- * Arcjet prompt injection detection rule.
478
- *
479
- * @deprecated
480
- * Use `detectPromptInjection` instead.
481
- */
482
- const experimental_detectPromptInjection = detectPromptInjection;
483
- /**
484
- * Create a remote client.
485
- *
486
- * @param options
487
- * Configuration (optional).
488
- * @returns
489
- * Client.
490
- */
491
- function createRemoteClient(options) {
492
- const settings = options ?? {};
493
- return { baseUrl: settings.baseUrl, timeout: settings.timeout };
494
- }
495
- /**
496
- * Create a new Astro integration of Arcjet.
497
- *
498
- * @template Characteristics
499
- * Characteristics to track a user by.
500
- * @param options
501
- * Configuration.
502
- * @returns
503
- * Astro integration of Arcjet.
504
- */
505
- function arcjet(options = { rules: [] }) {
506
- const { rules, characteristics, client, proxies } = options;
507
- const arcjetImports = new Set();
508
- const arcjetRules = [];
509
- for (const rule of rules) {
510
- const { importName, code } = integrationRuleToClientRule(rule);
511
- arcjetImports.add(importName);
512
- arcjetRules.push(code);
513
- }
514
- const characteristicsInjection = characteristics
515
- ? `characteristics: ${validateAndSerialize(validateCharacteristics, characteristics)},`
516
- : "";
517
- const proxiesInjection = proxies
518
- ? `proxies: ${validateAndSerialize(validateProxies, proxies)},`
519
- : "";
520
- const clientInjection = client
521
- ? `client: createRemoteClient(${validateAndSerialize(validateClientOptions, client)}),`
522
- : "";
523
- return {
524
- name: "@arcjet/astro",
525
- hooks: {
526
- "astro:config:setup"({ updateConfig, addMiddleware }) {
527
- updateConfig({
528
- env: {
529
- schema: {
530
- ARCJET_KEY: {
531
- type: "string",
532
- context: "server",
533
- access: "secret",
534
- startsWith: "ajkey_",
535
- },
536
- ARCJET_ENV: {
537
- type: "enum",
538
- context: "server",
539
- access: "public",
540
- values: ["production", "development"],
541
- optional: true,
542
- },
543
- ARCJET_BASE_URL: {
544
- type: "string",
545
- context: "server",
546
- access: "public",
547
- startsWith: "https://",
548
- optional: true,
549
- },
550
- ARCJET_LOG_LEVEL: {
551
- type: "enum",
552
- context: "server",
553
- access: "public",
554
- values: ["debug", "info", "warn", "error"],
555
- optional: true,
556
- },
557
- FLY_APP_NAME: {
558
- type: "string",
559
- context: "server",
560
- access: "public",
561
- optional: true,
562
- },
563
- VERCEL: {
564
- type: "string",
565
- context: "server",
566
- access: "public",
567
- optional: true,
568
- },
569
- FIREBASE_CONFIG: {
570
- access: "public",
571
- context: "server",
572
- optional: true,
573
- type: "string",
574
- },
575
- // No `MODE`, that is a vite value on `import.meta.env.MODE`,
576
- // it is inferred in `internal.ts` directly.
577
- // No `NODE_ENV`.
578
- RENDER: {
579
- access: "public",
580
- context: "server",
581
- optional: true,
582
- type: "string",
583
- },
584
- },
585
- },
586
- vite: {
587
- plugins: [
588
- {
589
- name: "@arcjet/astro",
590
- resolveId(id) {
591
- if (id === "arcjet:client") {
592
- return resolvedVirtualClientId;
593
- }
594
- return;
595
- },
596
- async load(id) {
597
- if (id === resolvedVirtualClientId) {
598
- const implSource = await fs.readFile(new URL("./internal.js", import.meta.url), { encoding: "utf-8" });
599
- return {
600
- code: `
601
- ${implSource}
602
-
603
- import {
604
- ${Array.from(arcjetImports).join(",\n")}
605
- } from "arcjet"
606
-
607
- // Construct an Arcjet client for the virtual module
608
- const aj = createArcjetClient({
609
- key: ARCJET_KEY,
610
- rules: [
611
- ${arcjetRules.join(",\n")}
612
- ],
613
- ${characteristicsInjection}
614
- ${proxiesInjection}
615
- ${clientInjection}
616
- })
617
-
618
- export default aj;
619
- `,
620
- };
621
- }
622
- },
623
- },
624
- ],
625
- },
626
- });
627
- addMiddleware({
628
- entrypoint: new URL("./middleware.js", import.meta.url),
629
- order: "pre",
630
- });
631
- },
632
- "astro:config:done": async ({ buildOutput, injectTypes, logger }) => {
633
- if (buildOutput === "static") {
634
- logger.warn("✦ Arcjet can only protect Dynamic routes.\n\n" +
635
- " Configure at least 1 Dynamic route to use the Arcjet integration, see Astro's\n" +
636
- " Dynamic routes documentation for configuration details:\n" +
637
- " https://docs.astro.build/en/guides/routing/#dynamic-routes\n");
638
- }
639
- const implTypes = await fs.readFile(new URL("./internal.d.ts", import.meta.url), { encoding: "utf-8" });
640
- injectTypes({
641
- content: `
642
- declare module "arcjet:client" {
643
- ${implTypes}
644
-
645
- import {
646
- ${Array.from(arcjetImports).join(",\n")}
647
- } from "arcjet"
648
-
649
- /**
650
- * Instance of the Astro integration of Arcjet.
651
- *
652
- * Primarily has a \`protect()\` method to make a decision about how a request
653
- * should be handled.
654
- *
655
- * > 👉 **Note**: this is generated by \`@arcjet/astro\` based on how you configure
656
- * > Arcjet in your \`astro.config.mjs\` file.
657
- * > In that configuration file, you can pass the (serializable) options that apply
658
- * > to all requests.
659
- * > You can call \`aj.withRule\` *on* this default client to extend its behavior.
660
- *
661
- * @template Props
662
- * Configuration.
663
- */
664
- const client = createArcjetClient({
665
- rules: [
666
- ${arcjetRules.join(",\n")}
667
- ],
668
- ${characteristicsInjection}
669
- ${proxiesInjection}
670
- ${clientInjection}
671
- })
672
- export default client
673
- }`,
674
- filename: "client.d.ts",
675
- });
676
- },
677
- },
678
- };
679
- }
680
-
681
- export { createRemoteClient, arcjet as default, detectBot, detectPromptInjection, experimental_detectPromptInjection, filter, fixedWindow, protectSignup, sensitiveInfo, shield, slidingWindow, tokenBucket, validateEmail };