@usequark/quark-core 2.6.3 → 2.7.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 CHANGED
@@ -34,7 +34,10 @@ import {
34
34
  } from "@usequark/quark-core";
35
35
  ```
36
36
 
37
- All modules are re-exported from the package root. See JSDoc comments on each function for options and usage details.
37
+ Most modules are re-exported from the package root, but not all: `locale`,
38
+ `stripe`, `storage/s3`, `csrf-client`, and `testing` are **subpath-only** —
39
+ import them from their subpath. See JSDoc comments on each function for options
40
+ and usage details.
38
41
 
39
42
  ### Subpath exports
40
43
 
@@ -44,22 +47,26 @@ Every subpath in `packages/core/package.json` `exports`, verified against the fi
44
47
 
45
48
  | Subpath | Contents |
46
49
  |---|---|
47
- | `@usequark/quark-core` | everything below, re-exported from the root |
50
+ | `@usequark/quark-core` | most modules re-exported from the root (exceptions listed below) |
48
51
  | `@usequark/quark-core/core` | errors, logger, validation, utils, pagination, csrf, rate limiter, file validation, db |
49
52
  | `@usequark/quark-core/auth` | auth config helpers + session guards |
50
53
  | `@usequark/quark-core/auth/middleware` | session guards only |
54
+ | `@usequark/quark-core/csrf-client` | browser-side CSRF token helper (subpath-only) |
51
55
  | `@usequark/quark-core/db` | `createPrismaClient`, connection string, pool config, `pingDatabase()` |
52
56
  | `@usequark/quark-core/queue` | BullMQ queue and worker helpers |
53
57
  | `@usequark/quark-core/email` | `EmailProvider`, `registerEmailProvider`, `createEmailService` |
58
+ | `@usequark/quark-core/email-templates` | `welcomeEmail`, `passwordResetEmail` templates |
54
59
  | `@usequark/quark-core/errors` | error classes only |
55
60
  | `@usequark/quark-core/health` | health check runners and default probes |
56
61
  | `@usequark/quark-core/metrics` | metrics registry and default instruments |
62
+ | `@usequark/quark-core/multipart` | multipart form parsing |
63
+ | `@usequark/quark-core/redis` | `resolveRedisConnection` and Redis helpers |
57
64
  | `@usequark/quark-core/storage` | local adapter, storage factory, key/URL helpers, `createS3Storage` |
58
- | `@usequark/quark-core/storage/s3` | S3 adapter only |
65
+ | `@usequark/quark-core/storage/s3` | S3 adapter only (subpath-only) |
59
66
  | `@usequark/quark-core/sms` | SMS provider |
60
- | `@usequark/quark-core/stripe` | Stripe client + webhook helpers |
67
+ | `@usequark/quark-core/stripe` | Stripe client + webhook helpers (subpath-only) |
61
68
  | `@usequark/quark-core/admin` | Prisma schema introspection helpers |
62
- | `@usequark/quark-core/locale` | locale helpers |
69
+ | `@usequark/quark-core/locale` | locale helpers (subpath-only) |
63
70
  | `@usequark/quark-core/logger` | browser-safe logger |
64
71
  | `@usequark/quark-core/testing` | test factories, mocks, helpers |
65
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usequark/quark-core",
3
- "version": "2.6.3",
3
+ "version": "2.7.0",
4
4
  "description": "Core utilities for Quark apps: auth, BullMQ queues, Redis, email, storage, rate limiting, logging, and error handling",
5
5
  "keywords": [
6
6
  "quark",
package/src/csrf.test.js CHANGED
@@ -153,7 +153,7 @@ test("CSRF Module", async (t) => {
153
153
  await t.test("requireCsrfToken skips Bearer-authenticated requests", () => {
154
154
  const request = mockRequest({
155
155
  method: "POST",
156
- url: "http://localhost/api/device/register",
156
+ url: "http://localhost/api/files",
157
157
  headers: { authorization: "Bearer some-jwt-token" },
158
158
  });
159
159
 
@@ -99,7 +99,13 @@ class MemoryRateLimiter {
99
99
  class RedisRateLimiter {
100
100
  constructor(redisClient, options = {}) {
101
101
  this.redis = redisClient;
102
- this.failOpen = options.failOpen ?? true;
102
+ // Fails CLOSED by default: if Redis errors mid-request, the request is
103
+ // treated as rate-limited rather than allowed through. A limiter that
104
+ // disables itself precisely when it is being hammered (the classic
105
+ // fail-open) is worst exactly when it matters. Opt back in with
106
+ // `failOpen: true` only if an outage-driven 429 is worse for you than
107
+ // an attacker-driven flood.
108
+ this.failOpen = options.failOpen ?? false;
103
109
  }
104
110
 
105
111
  /**
@@ -127,4 +127,48 @@ test("Rate Limiter Module", async (t) => {
127
127
  limiter.stopCleanup();
128
128
  assert(limiter.cleanupInterval === null);
129
129
  });
130
+
131
+ // A Redis-backed limiter that fails OPEN disables itself precisely when it
132
+ // is being hammered — Redis erroring under load is the moment an attacker
133
+ // most needs the limiter to hold. Default must be fail-CLOSED.
134
+ await t.test(
135
+ "Redis limiter fails CLOSED by default when Redis errors",
136
+ async () => {
137
+ const limiter = createRateLimiter({
138
+ type: "redis",
139
+ redisClient: {
140
+ eval: async () => {
141
+ throw new Error("redis down");
142
+ },
143
+ },
144
+ });
145
+
146
+ const result = await limiter.checkLimit("k", 100, 60000);
147
+ assert.strictEqual(
148
+ result.limited,
149
+ true,
150
+ "must block, not allow, on error",
151
+ );
152
+ assert.strictEqual(result.remaining, 0);
153
+ },
154
+ );
155
+
156
+ await t.test(
157
+ "Redis limiter can opt back into fail-open explicitly",
158
+ async () => {
159
+ const limiter = createRateLimiter({
160
+ type: "redis",
161
+ failOpen: true,
162
+ redisClient: {
163
+ eval: async () => {
164
+ throw new Error("redis down");
165
+ },
166
+ },
167
+ });
168
+
169
+ const result = await limiter.checkLimit("k", 100, 60000);
170
+ assert.strictEqual(result.limited, false);
171
+ assert.strictEqual(result.remaining, 100);
172
+ },
173
+ );
130
174
  });