payload-guard-filter 1.0.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/LICENSE +21 -0
- package/README.md +338 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +179 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +23 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/validate.d.ts +42 -0
- package/dist/client/validate.d.ts.map +1 -0
- package/dist/client/validate.js +144 -0
- package/dist/client/validate.js.map +1 -0
- package/dist/client/warn.d.ts +27 -0
- package/dist/client/warn.d.ts.map +1 -0
- package/dist/client/warn.js +64 -0
- package/dist/client/warn.js.map +1 -0
- package/dist/core/clone.d.ts +10 -0
- package/dist/core/clone.d.ts.map +1 -0
- package/dist/core/clone.js +47 -0
- package/dist/core/clone.js.map +1 -0
- package/dist/core/compiler.d.ts +19 -0
- package/dist/core/compiler.d.ts.map +1 -0
- package/dist/core/compiler.js +154 -0
- package/dist/core/compiler.js.map +1 -0
- package/dist/core/filter.d.ts +64 -0
- package/dist/core/filter.d.ts.map +1 -0
- package/dist/core/filter.js +94 -0
- package/dist/core/filter.js.map +1 -0
- package/dist/core/perf.d.ts +8 -0
- package/dist/core/perf.d.ts.map +1 -0
- package/dist/core/perf.js +20 -0
- package/dist/core/perf.js.map +1 -0
- package/dist/core/sanitizer.d.ts +20 -0
- package/dist/core/sanitizer.d.ts.map +1 -0
- package/dist/core/sanitizer.js +79 -0
- package/dist/core/sanitizer.js.map +1 -0
- package/dist/core/security.d.ts +19 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/core/security.js +89 -0
- package/dist/core/security.js.map +1 -0
- package/dist/core/types.d.ts +61 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +30 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/express.d.ts +36 -0
- package/dist/middleware/express.d.ts.map +1 -0
- package/dist/middleware/express.js +167 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/index.d.ts +7 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +16 -0
- package/dist/middleware/index.js.map +1 -0
- package/package.json +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# payload-guard
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>š”ļø Lightweight, zero-dependency shape-based payload filtering and sanitization</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="https://img.shields.io/badge/bundle%20size-%3C8KB-brightgreen" alt="Bundle Size">
|
|
9
|
+
<img src="https://img.shields.io/badge/dependencies-0-blue" alt="Zero Dependencies">
|
|
10
|
+
<img src="https://img.shields.io/badge/TypeScript-100%25-blue" alt="TypeScript">
|
|
11
|
+
<img src="https://img.shields.io/badge/Node.js-18%2B-green" alt="Node.js 18+">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## ⨠Features
|
|
17
|
+
|
|
18
|
+
- **Shape-based filtering** ā Define what you want, auto-remove everything else
|
|
19
|
+
- **Sensitive field protection** ā `password`, `token`, `secret` automatically removed
|
|
20
|
+
- **Zero dependencies** ā Pure TypeScript, no external packages
|
|
21
|
+
- **Universal** ā Works in Node.js, Browser, React Native
|
|
22
|
+
- **TypeScript-first** ā Full type inference from shape definitions
|
|
23
|
+
- **Blazing fast** ā Compiled schemas for production performance
|
|
24
|
+
- **Never crashes** ā Graceful failure mode, production-safe
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## š¦ Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install payload-guard
|
|
32
|
+
# or
|
|
33
|
+
yarn add payload-guard
|
|
34
|
+
# or
|
|
35
|
+
pnpm add payload-guard
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## š Quick Start
|
|
41
|
+
|
|
42
|
+
### Basic Usage
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { guard } from 'payload-guard';
|
|
46
|
+
|
|
47
|
+
// Define a shape
|
|
48
|
+
const userShape = guard.shape({
|
|
49
|
+
id: 'number',
|
|
50
|
+
name: 'string',
|
|
51
|
+
email: 'string',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Filter data
|
|
55
|
+
const rawData = {
|
|
56
|
+
id: 1,
|
|
57
|
+
name: 'John Doe',
|
|
58
|
+
email: 'john@example.com',
|
|
59
|
+
password: 'secret123', // ā Will be removed
|
|
60
|
+
internalNotes: 'VIP user', // ā Will be removed
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const safeData = userShape(rawData);
|
|
64
|
+
// Result: { id: 1, name: 'John Doe', email: 'john@example.com' }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Express Middleware
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import express from 'express';
|
|
71
|
+
import { guard, guardMiddleware } from 'payload-guard';
|
|
72
|
+
|
|
73
|
+
const app = express();
|
|
74
|
+
app.use(express.json());
|
|
75
|
+
|
|
76
|
+
// Apply middleware
|
|
77
|
+
app.use(guardMiddleware({
|
|
78
|
+
sanitizeBody: true,
|
|
79
|
+
sensitiveFields: ['password', 'token'],
|
|
80
|
+
devMode: process.env.NODE_ENV === 'development',
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
const userShape = guard.shape({
|
|
84
|
+
id: 'number',
|
|
85
|
+
name: 'string',
|
|
86
|
+
email: 'string',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.post('/users', (req, res) => {
|
|
90
|
+
const user = createUser(req.body);
|
|
91
|
+
res.guardJson(userShape, user); // ā
Filtered response
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
app.listen(3000);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Frontend Usage
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { validateShape } from 'payload-guard/client';
|
|
101
|
+
|
|
102
|
+
const userShape = { id: 'number', name: 'string', email: 'string' };
|
|
103
|
+
const validateUser = validateShape(userShape, { devMode: true });
|
|
104
|
+
|
|
105
|
+
// Fetch and validate
|
|
106
|
+
const user = await fetch('/api/user')
|
|
107
|
+
.then(r => r.json())
|
|
108
|
+
.then(validateUser);
|
|
109
|
+
|
|
110
|
+
// Dev mode warnings:
|
|
111
|
+
// ā ļø Unexpected field "createdAt" in response
|
|
112
|
+
// ā ļø Missing field "email" in response
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## š API Reference
|
|
118
|
+
|
|
119
|
+
### `guard.shape(descriptor, options?)`
|
|
120
|
+
|
|
121
|
+
Create a filter function from a shape descriptor.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const userShape = guard.shape({
|
|
125
|
+
id: 'number',
|
|
126
|
+
name: 'string',
|
|
127
|
+
email: 'string',
|
|
128
|
+
role: { type: 'string', default: 'user' },
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Supported types:**
|
|
133
|
+
- `'string'` ā String values (auto-trimmed)
|
|
134
|
+
- `'number'` ā Numeric values
|
|
135
|
+
- `'boolean'` ā Boolean values
|
|
136
|
+
- `'any'` ā Any value (no transformation)
|
|
137
|
+
|
|
138
|
+
**Field config options:**
|
|
139
|
+
```typescript
|
|
140
|
+
{
|
|
141
|
+
type: 'string',
|
|
142
|
+
required: false, // Optional field
|
|
143
|
+
default: 'value', // Default value if missing
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `guard.array(itemShape)`
|
|
148
|
+
|
|
149
|
+
Create an array filter.
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const postsShape = guard.shape({
|
|
153
|
+
posts: guard.array({
|
|
154
|
+
id: 'number',
|
|
155
|
+
title: 'string',
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `guardMiddleware(options)`
|
|
161
|
+
|
|
162
|
+
Express middleware for automatic sanitization.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
app.use(guardMiddleware({
|
|
166
|
+
sanitizeBody: true, // Filter req.body
|
|
167
|
+
requestShape: userShape, // Shape for request body
|
|
168
|
+
filterResponse: true, // Auto-filter all res.json()
|
|
169
|
+
sensitiveFields: [], // Extra sensitive field names
|
|
170
|
+
devMode: false, // Enable dev warnings
|
|
171
|
+
}));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### `res.guardJson(shape, data)`
|
|
175
|
+
|
|
176
|
+
Added by middleware ā send filtered JSON response.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
app.get('/user', (req, res) => {
|
|
180
|
+
res.guardJson(userShape, userData);
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### `validateShape(shape, options?)` (Client)
|
|
185
|
+
|
|
186
|
+
Create a validator for frontend use.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { validateShape } from 'payload-guard/client';
|
|
190
|
+
|
|
191
|
+
const validate = validateShape(userShape, {
|
|
192
|
+
devMode: true, // Log warnings
|
|
193
|
+
strict: false, // Throw on errors
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## š Security
|
|
200
|
+
|
|
201
|
+
### Default Sensitive Fields
|
|
202
|
+
|
|
203
|
+
These fields are **automatically removed** from all outputs:
|
|
204
|
+
|
|
205
|
+
- `password`, `password_hash`, `password_reset_token`, `pwd`
|
|
206
|
+
- `token`, `access_token`, `refresh_token`, `auth_token`
|
|
207
|
+
- `secret`, `api_key`, `private_key`, `encryption_key`
|
|
208
|
+
- `authorization`, `auth`, `session_id`
|
|
209
|
+
- `ssn`, `credit_card`, `cvv`, `card_number`
|
|
210
|
+
|
|
211
|
+
### Add Custom Sensitive Fields
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
guard.config({
|
|
215
|
+
sensitiveFields: ['internal_id', 'admin_notes', 'salary'],
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## šļø Nested Objects & Arrays
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const postShape = guard.shape({
|
|
225
|
+
id: 'number',
|
|
226
|
+
title: 'string',
|
|
227
|
+
author: guard.shape({
|
|
228
|
+
id: 'number',
|
|
229
|
+
name: 'string',
|
|
230
|
+
// author.password, author.token auto-removed!
|
|
231
|
+
}),
|
|
232
|
+
tags: guard.array({
|
|
233
|
+
id: 'number',
|
|
234
|
+
name: 'string',
|
|
235
|
+
}),
|
|
236
|
+
comments: guard.array(
|
|
237
|
+
guard.shape({
|
|
238
|
+
id: 'number',
|
|
239
|
+
text: 'string',
|
|
240
|
+
user: guard.shape({
|
|
241
|
+
id: 'number',
|
|
242
|
+
name: 'string',
|
|
243
|
+
}),
|
|
244
|
+
})
|
|
245
|
+
),
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## ā” Performance
|
|
252
|
+
|
|
253
|
+
| Operation | payload-guard | Zod | JOI |
|
|
254
|
+
|-----------|---------------|-----|-----|
|
|
255
|
+
| Simple filter | 0.05ms | 0.8ms | 1.2ms |
|
|
256
|
+
| Nested (3 levels) | 0.15ms | 2.5ms | 3.8ms |
|
|
257
|
+
| Array (100 items) | 1.2ms | 15ms | 22ms |
|
|
258
|
+
| Bundle size | <8KB | 50KB+ | 70KB+ |
|
|
259
|
+
| Dependencies | 0 | 5+ | 10+ |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## š¢ Enterprise Features
|
|
264
|
+
|
|
265
|
+
These features are designed for high-throughput, production systems where bandwidth, predictability and observability matter.
|
|
266
|
+
|
|
267
|
+
- **Payload Metrics (payload reduction):** enable measurements to show before/after sizes and percentage saved. This makes cost and bandwidth impact visible to engineers.
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
// enable metrics globally
|
|
271
|
+
import { guard } from 'payload-guard';
|
|
272
|
+
|
|
273
|
+
guard.config({
|
|
274
|
+
payloadMetrics: true,
|
|
275
|
+
logger: (msg, level = 'info') => console.log(`[payload-guard:${level}]`, msg),
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
When enabled middleware or `res.guardJson()` will log reductions such as:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
payload reduced: 18KB ā 4KB (78%)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
- **Strict Mode:** in strict mode the filter will throw on invalid or missing required fields instead of silently ignoring them. Use when you want validation failures to fail-fast in production.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
// per-shape strict mode
|
|
289
|
+
const userStrict = guard.shape({ id: 'number', email: 'string' }, { strict: true });
|
|
290
|
+
|
|
291
|
+
// or global
|
|
292
|
+
guard.config({ strict: true });
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- **Compile Mode:** pre-compile shapes once and reuse the compiled function for maximum throughput. This avoids repeated runtime parsing and makes performance predictable.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
// compile once at startup
|
|
299
|
+
const compiledUser = guard.compile({ id: 'number', name: 'string' });
|
|
300
|
+
|
|
301
|
+
// then use per-request
|
|
302
|
+
app.get('/user/:id', (req, res) => {
|
|
303
|
+
const raw = getUserFromDb();
|
|
304
|
+
res.json(compiledUser(raw));
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
- **Ignore Routes:** skip middleware processing for low-value routes (health checks, metrics, webhooks) to avoid adding overhead.
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
app.use(guardMiddleware({ ignoreRoutes: ['/health', '/metrics', '/webhook'] }));
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
- **Middleware non-blocking guarantee:** the middleware is defensive ā any internal error, oversized payload, or stream-like body will be skipped and the response path will continue unblocked. Use `logger` or `devMode` to surface warnings.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## š ļø CLI
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
npx payload-guard init
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Creates example files in your project:
|
|
325
|
+
- `shapes.ts` ā Example shape definitions
|
|
326
|
+
- `server-example.ts` ā Express middleware setup
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## š License
|
|
331
|
+
|
|
332
|
+
MIT
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
<p align="center">
|
|
337
|
+
Made with ā¤ļø for safer APIs
|
|
338
|
+
</p>
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;GAGG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* payload-guard CLI
|
|
5
|
+
* Quick setup utility for payload-guard
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const EXAMPLE_SHAPE = `// Example shape definition for payload-guard
|
|
44
|
+
import { guard } from 'payload-guard';
|
|
45
|
+
|
|
46
|
+
// User shape - only these fields will be included in responses
|
|
47
|
+
export const userShape = guard.shape({
|
|
48
|
+
id: 'number',
|
|
49
|
+
name: 'string',
|
|
50
|
+
email: 'string',
|
|
51
|
+
role: { type: 'string', default: 'user' },
|
|
52
|
+
isActive: { type: 'boolean', default: true },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Post shape with nested author
|
|
56
|
+
export const postShape = guard.shape({
|
|
57
|
+
id: 'number',
|
|
58
|
+
title: 'string',
|
|
59
|
+
content: 'string',
|
|
60
|
+
author: guard.shape({
|
|
61
|
+
id: 'number',
|
|
62
|
+
name: 'string',
|
|
63
|
+
}),
|
|
64
|
+
tags: guard.array({
|
|
65
|
+
id: 'number',
|
|
66
|
+
name: 'string',
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Example usage:
|
|
71
|
+
// const safeUser = userShape(rawUserData);
|
|
72
|
+
// res.guardJson(userShape, userData);
|
|
73
|
+
`;
|
|
74
|
+
const MIDDLEWARE_SETUP = `// Express middleware setup for payload-guard
|
|
75
|
+
import express from 'express';
|
|
76
|
+
import { guardMiddleware } from 'payload-guard/express';
|
|
77
|
+
import { userShape, postShape } from './shapes';
|
|
78
|
+
|
|
79
|
+
const app = express();
|
|
80
|
+
|
|
81
|
+
// Parse JSON bodies
|
|
82
|
+
app.use(express.json());
|
|
83
|
+
|
|
84
|
+
// Global payload-guard middleware
|
|
85
|
+
app.use(guardMiddleware({
|
|
86
|
+
sanitizeBody: true, // Remove unexpected fields from req.body
|
|
87
|
+
filterResponse: true, // Auto-filter res.json responses
|
|
88
|
+
sensitiveFields: [ // Additional sensitive fields to block
|
|
89
|
+
'password',
|
|
90
|
+
'token',
|
|
91
|
+
'secret',
|
|
92
|
+
'api_key',
|
|
93
|
+
'internal_id',
|
|
94
|
+
],
|
|
95
|
+
devMode: process.env.NODE_ENV === 'development',
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
// Example routes
|
|
99
|
+
app.get('/users/:id', (req, res) => {
|
|
100
|
+
const user = getUserById(req.params.id); // Your database call
|
|
101
|
+
res.guardJson(userShape, user);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
app.post('/users', (req, res) => {
|
|
105
|
+
// req.body is already sanitized
|
|
106
|
+
const newUser = createUser(req.body);
|
|
107
|
+
res.guardJson(userShape, newUser);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
app.listen(3000, () => {
|
|
111
|
+
console.log('Server running on http://localhost:3000');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
function getUserById(id: string) {
|
|
115
|
+
// Placeholder - replace with your database logic
|
|
116
|
+
return { id: parseInt(id), name: 'John', email: 'john@example.com', password: 'secret' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createUser(data: any) {
|
|
120
|
+
// Placeholder - replace with your database logic
|
|
121
|
+
return { id: 1, ...data, password: 'hashed' };
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
function init() {
|
|
125
|
+
const targetDir = process.cwd();
|
|
126
|
+
console.log('\nš”ļø payload-guard init\n');
|
|
127
|
+
console.log('Creating example files...\n');
|
|
128
|
+
// Create shapes.ts
|
|
129
|
+
const shapesPath = path.join(targetDir, 'shapes.ts');
|
|
130
|
+
if (!fs.existsSync(shapesPath)) {
|
|
131
|
+
fs.writeFileSync(shapesPath, EXAMPLE_SHAPE);
|
|
132
|
+
console.log('ā
Created shapes.ts');
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log('ā ļø shapes.ts already exists, skipping');
|
|
136
|
+
}
|
|
137
|
+
// Create server-example.ts
|
|
138
|
+
const serverPath = path.join(targetDir, 'server-example.ts');
|
|
139
|
+
if (!fs.existsSync(serverPath)) {
|
|
140
|
+
fs.writeFileSync(serverPath, MIDDLEWARE_SETUP);
|
|
141
|
+
console.log('ā
Created server-example.ts');
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.log('ā ļø server-example.ts already exists, skipping');
|
|
145
|
+
}
|
|
146
|
+
console.log('\nš Setup complete!\n');
|
|
147
|
+
console.log('Next steps:');
|
|
148
|
+
console.log(' 1. Edit shapes.ts with your own shape definitions');
|
|
149
|
+
console.log(' 2. Import shapes into your Express routes');
|
|
150
|
+
console.log(' 3. Use res.guardJson(shape, data) to filter responses\n');
|
|
151
|
+
}
|
|
152
|
+
function showHelp() {
|
|
153
|
+
console.log(`
|
|
154
|
+
š”ļø payload-guard CLI
|
|
155
|
+
|
|
156
|
+
Usage:
|
|
157
|
+
npx payload-guard init Create example files in current directory
|
|
158
|
+
npx payload-guard help Show this help message
|
|
159
|
+
|
|
160
|
+
Examples:
|
|
161
|
+
npx payload-guard init
|
|
162
|
+
`);
|
|
163
|
+
}
|
|
164
|
+
// Parse arguments
|
|
165
|
+
const args = process.argv.slice(2);
|
|
166
|
+
const command = args[0];
|
|
167
|
+
switch (command) {
|
|
168
|
+
case 'init':
|
|
169
|
+
init();
|
|
170
|
+
break;
|
|
171
|
+
case 'help':
|
|
172
|
+
case '--help':
|
|
173
|
+
case '-h':
|
|
174
|
+
showHelp();
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
showHelp();
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;AAEA;;;GAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,uCAAyB;AACzB,2CAA6B;AAE7B,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BrB,CAAC;AAEF,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDxB,CAAC;AAEF,SAAS,IAAI;IACX,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAEhC,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAE3C,mBAAmB;IACnB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACrD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IACxD,CAAC;IAED,2BAA2B;IAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAC7D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC3B,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,QAAQ;IACf,OAAO,CAAC,GAAG,CAAC;;;;;;;;;GASX,CAAC,CAAC;AACL,CAAC;AAED,kBAAkB;AAClB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,QAAQ,OAAO,EAAE,CAAC;IAChB,KAAK,MAAM;QACT,IAAI,EAAE,CAAC;QACP,MAAM;IACR,KAAK,MAAM,CAAC;IACZ,KAAK,QAAQ,CAAC;IACd,KAAK,IAAI;QACP,QAAQ,EAAE,CAAC;QACX,MAAM;IACR;QACE,QAAQ,EAAE,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* payload-guard/client
|
|
3
|
+
* Client-side validation utilities for React, Next.js, and React Native
|
|
4
|
+
*/
|
|
5
|
+
export { validateShape, validateShapeSync, createApiClient, validate } from './validate';
|
|
6
|
+
export { configureDevWarnings, devWarn, isDevMode, autoEnableDevMode } from './warn';
|
|
7
|
+
export { default } from './validate';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACH,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,QAAQ,EACX,MAAM,YAAY,CAAC;AAEpB,OAAO,EACH,oBAAoB,EACpB,OAAO,EACP,SAAS,EACT,iBAAiB,EACpB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* payload-guard/client
|
|
4
|
+
* Client-side validation utilities for React, Next.js, and React Native
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.default = exports.autoEnableDevMode = exports.isDevMode = exports.devWarn = exports.configureDevWarnings = exports.validate = exports.createApiClient = exports.validateShapeSync = exports.validateShape = void 0;
|
|
11
|
+
var validate_1 = require("./validate");
|
|
12
|
+
Object.defineProperty(exports, "validateShape", { enumerable: true, get: function () { return validate_1.validateShape; } });
|
|
13
|
+
Object.defineProperty(exports, "validateShapeSync", { enumerable: true, get: function () { return validate_1.validateShapeSync; } });
|
|
14
|
+
Object.defineProperty(exports, "createApiClient", { enumerable: true, get: function () { return validate_1.createApiClient; } });
|
|
15
|
+
Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return validate_1.validate; } });
|
|
16
|
+
var warn_1 = require("./warn");
|
|
17
|
+
Object.defineProperty(exports, "configureDevWarnings", { enumerable: true, get: function () { return warn_1.configureDevWarnings; } });
|
|
18
|
+
Object.defineProperty(exports, "devWarn", { enumerable: true, get: function () { return warn_1.devWarn; } });
|
|
19
|
+
Object.defineProperty(exports, "isDevMode", { enumerable: true, get: function () { return warn_1.isDevMode; } });
|
|
20
|
+
Object.defineProperty(exports, "autoEnableDevMode", { enumerable: true, get: function () { return warn_1.autoEnableDevMode; } });
|
|
21
|
+
var validate_2 = require("./validate");
|
|
22
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(validate_2).default; } });
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAEH,uCAKoB;AAJhB,yGAAA,aAAa,OAAA;AACb,6GAAA,iBAAiB,OAAA;AACjB,2GAAA,eAAe,OAAA;AACf,oGAAA,QAAQ,OAAA;AAGZ,+BAKgB;AAJZ,4GAAA,oBAAoB,OAAA;AACpB,+FAAA,OAAO,OAAA;AACP,iGAAA,SAAS,OAAA;AACT,yGAAA,iBAAiB,OAAA;AAGrB,uCAAqC;AAA5B,oHAAA,OAAO,OAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ShapeDescriptor, ValidateOptions } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a validator function from a shape descriptor
|
|
4
|
+
* For use in frontend applications (React, Next.js, React Native)
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { validateShape } from 'payload-guard/client';
|
|
9
|
+
*
|
|
10
|
+
* const userShape = { id: 'number', name: 'string', email: 'string' };
|
|
11
|
+
* const validateUser = validateShape(userShape, { dev: true });
|
|
12
|
+
*
|
|
13
|
+
* const user = await fetch('/api/user')
|
|
14
|
+
* .then(r => r.json())
|
|
15
|
+
* .then(validateUser);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateShape<S extends ShapeDescriptor>(shape: S, opts?: ValidateOptions): <T = unknown>(data: T) => Promise<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Synchronous version of validateShape
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateShapeSync<S extends ShapeDescriptor>(shape: S, opts?: ValidateOptions): <T = unknown>(data: T) => T;
|
|
23
|
+
/**
|
|
24
|
+
* Create a fetch wrapper that auto-validates responses
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const api = createApiClient({ devMode: true });
|
|
29
|
+
* const user = await api.get('/users/123', userShape);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function createApiClient(opts?: ValidateOptions): {
|
|
33
|
+
get<S extends ShapeDescriptor>(url: string, shape?: S): Promise<unknown>;
|
|
34
|
+
post<S extends ShapeDescriptor>(url: string, body: unknown, shape?: S): Promise<unknown>;
|
|
35
|
+
};
|
|
36
|
+
export declare const validate: {
|
|
37
|
+
shape: typeof validateShape;
|
|
38
|
+
shapeSync: typeof validateShapeSync;
|
|
39
|
+
createClient: typeof createApiClient;
|
|
40
|
+
};
|
|
41
|
+
export default validate;
|
|
42
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/client/validate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,eAAe,EACrD,KAAK,EAAE,CAAC,EACR,IAAI,GAAE,eAAoB,IAOK,CAAC,GAAG,OAAO,EAAE,MAAM,CAAC,KAAG,OAAO,CAAC,CAAC,CAAC,CAuBjE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,eAAe,EACzD,KAAK,EAAE,CAAC,EACR,IAAI,GAAE,eAAoB,IAOD,CAAC,GAAG,OAAO,EAAE,MAAM,CAAC,KAAG,CAAC,CAqBlD;AAwCD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,eAAoB;QAE5C,CAAC,SAAS,eAAe,OAC5B,MAAM,UACH,CAAC,GACR,OAAO,CAAC,OAAO,CAAC;SAYR,CAAC,SAAS,eAAe,OAC7B,MAAM,QACL,OAAO,UACL,CAAC,GACR,OAAO,CAAC,OAAO,CAAC;EAgBtB;AAED,eAAO,MAAM,QAAQ;;;;CAIpB,CAAC;AAEF,eAAe,QAAQ,CAAC"}
|