@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 +12 -5
- package/package.json +1 -1
- package/src/csrf.test.js +1 -1
- package/src/rate-limiter.js +7 -1
- package/src/rate-limiter.test.js +44 -0
package/README.md
CHANGED
|
@@ -34,7 +34,10 @@ import {
|
|
|
34
34
|
} from "@usequark/quark-core";
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
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` |
|
|
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
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/
|
|
156
|
+
url: "http://localhost/api/files",
|
|
157
157
|
headers: { authorization: "Bearer some-jwt-token" },
|
|
158
158
|
});
|
|
159
159
|
|
package/src/rate-limiter.js
CHANGED
|
@@ -99,7 +99,13 @@ class MemoryRateLimiter {
|
|
|
99
99
|
class RedisRateLimiter {
|
|
100
100
|
constructor(redisClient, options = {}) {
|
|
101
101
|
this.redis = redisClient;
|
|
102
|
-
|
|
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
|
/**
|
package/src/rate-limiter.test.js
CHANGED
|
@@ -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
|
});
|