@bloomneo/appkit 1.2.9 โ 1.5.2
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/AGENTS.md +195 -0
- package/CHANGELOG.md +253 -0
- package/README.md +147 -799
- package/bin/commands/generate.js +7 -7
- package/cookbook/README.md +26 -0
- package/cookbook/api-key-service.ts +106 -0
- package/cookbook/auth-protected-crud.ts +112 -0
- package/cookbook/file-upload-pipeline.ts +113 -0
- package/cookbook/multi-tenant-saas.ts +87 -0
- package/cookbook/real-time-chat.ts +121 -0
- package/dist/auth/auth.d.ts +21 -4
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/auth/auth.js +56 -44
- package/dist/auth/auth.js.map +1 -1
- package/dist/auth/defaults.d.ts +1 -1
- package/dist/auth/defaults.js +35 -35
- package/dist/cache/cache.d.ts +29 -6
- package/dist/cache/cache.d.ts.map +1 -1
- package/dist/cache/cache.js +72 -44
- package/dist/cache/cache.js.map +1 -1
- package/dist/cache/defaults.js +29 -29
- package/dist/cache/index.d.ts +19 -10
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +21 -18
- package/dist/cache/index.js.map +1 -1
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +11 -11
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +4 -4
- package/dist/database/adapters/mongoose.d.ts +4 -4
- package/dist/database/adapters/mongoose.js +7 -7
- package/dist/database/adapters/prisma.d.ts +4 -4
- package/dist/database/adapters/prisma.js +7 -7
- package/dist/database/defaults.d.ts +1 -1
- package/dist/database/defaults.js +4 -4
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/defaults.js +26 -26
- package/dist/email/index.js +7 -7
- package/dist/email/strategies/resend.js +1 -1
- package/dist/error/defaults.d.ts +1 -1
- package/dist/error/defaults.js +13 -13
- package/dist/error/error.d.ts +12 -0
- package/dist/error/error.d.ts.map +1 -1
- package/dist/error/error.js +19 -0
- package/dist/error/error.js.map +1 -1
- package/dist/error/index.d.ts +14 -3
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +14 -3
- package/dist/error/index.js.map +1 -1
- package/dist/event/defaults.js +35 -35
- package/dist/event/index.js +7 -7
- package/dist/logger/defaults.d.ts +1 -1
- package/dist/logger/defaults.js +40 -40
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/logger/logger.d.ts +8 -0
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.js +13 -3
- package/dist/logger/logger.js.map +1 -1
- package/dist/logger/transports/console.js +2 -2
- package/dist/logger/transports/http.d.ts +1 -1
- package/dist/logger/transports/http.js +2 -2
- package/dist/logger/transports/webhook.d.ts +1 -1
- package/dist/logger/transports/webhook.js +3 -3
- package/dist/queue/defaults.d.ts +2 -2
- package/dist/queue/defaults.js +38 -38
- package/dist/security/defaults.d.ts +1 -1
- package/dist/security/defaults.js +30 -30
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +3 -3
- package/dist/security/security.d.ts +1 -1
- package/dist/security/security.js +4 -4
- package/dist/storage/defaults.js +26 -26
- package/dist/storage/index.js +3 -3
- package/dist/util/defaults.d.ts +1 -1
- package/dist/util/defaults.js +41 -41
- package/dist/util/env.d.ts +35 -0
- package/dist/util/env.d.ts.map +1 -0
- package/dist/util/env.js +50 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/errors.d.ts +52 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +82 -0
- package/dist/util/errors.js.map +1 -0
- package/dist/util/util.js +1 -1
- package/examples/.env.example +80 -0
- package/examples/README.md +16 -0
- package/examples/auth.ts +228 -0
- package/examples/cache.ts +36 -0
- package/examples/config.ts +45 -0
- package/examples/database.ts +69 -0
- package/examples/email.ts +53 -0
- package/examples/error.ts +50 -0
- package/examples/event.ts +42 -0
- package/examples/logger.ts +41 -0
- package/examples/queue.ts +58 -0
- package/examples/security.ts +46 -0
- package/examples/storage.ts +44 -0
- package/examples/util.ts +47 -0
- package/llms.txt +591 -0
- package/package.json +19 -10
- package/src/auth/README.md +850 -0
- package/src/cache/README.md +756 -0
- package/src/config/README.md +604 -0
- package/src/database/README.md +818 -0
- package/src/email/README.md +759 -0
- package/src/error/README.md +660 -0
- package/src/event/README.md +729 -0
- package/src/logger/README.md +435 -0
- package/src/queue/README.md +851 -0
- package/src/security/README.md +612 -0
- package/src/storage/README.md +1008 -0
- package/src/util/README.md +955 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
# @bloomneo/appkit - Security Module ๐
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@bloomneo/appkit)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
> Ultra-simple enterprise security that just works
|
|
7
|
+
|
|
8
|
+
**One function** returns a security object with enterprise-grade protection.
|
|
9
|
+
Zero configuration needed, production-ready by default, with built-in CSRF
|
|
10
|
+
protection, rate limiting, input sanitization, and AES-256-GCM encryption.
|
|
11
|
+
|
|
12
|
+
## ๐ Why Choose This?
|
|
13
|
+
|
|
14
|
+
- **โก One Function** - Just `securityClass.get()`, everything else is automatic
|
|
15
|
+
- **๐ Enterprise Security** - Production-grade CSRF, rate limiting, encryption
|
|
16
|
+
- **๐ง Zero Configuration** - Smart defaults with environment variable override
|
|
17
|
+
- **๐ Environment-First** - Auto-detects from `BLOOM_SECURITY_*` variables
|
|
18
|
+
- **๐ก๏ธ Complete Protection** - CSRF, XSS, rate limiting, data encryption
|
|
19
|
+
- **๐ฏ Framework Ready** - Express middleware with proper headers
|
|
20
|
+
- **๐ค AI-Ready** - Optimized for LLM code generation
|
|
21
|
+
|
|
22
|
+
## ๐ฆ Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @bloomneo/appkit
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## ๐โโ๏ธ Quick Start (30 seconds)
|
|
29
|
+
|
|
30
|
+
### 1. Environment Variables
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Essential security configuration
|
|
34
|
+
BLOOM_SECURITY_CSRF_SECRET=your-csrf-secret-key-2024-minimum-32-chars
|
|
35
|
+
BLOOM_SECURITY_ENCRYPTION_KEY=64-char-hex-key-for-aes256-encryption
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Basic Setup
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import express from 'express';
|
|
42
|
+
import session from 'express-session';
|
|
43
|
+
import { securityClass } from '@bloomneo/appkit/security';
|
|
44
|
+
|
|
45
|
+
const app = express();
|
|
46
|
+
const security = securityClass.get();
|
|
47
|
+
|
|
48
|
+
// Session middleware (required for CSRF)
|
|
49
|
+
app.use(session({ secret: process.env.SESSION_SECRET }));
|
|
50
|
+
|
|
51
|
+
// Security middleware
|
|
52
|
+
app.use(secure.forms()); // CSRF protection
|
|
53
|
+
app.use('/api', secure.requests()); // Rate limiting
|
|
54
|
+
|
|
55
|
+
// Secure route
|
|
56
|
+
app.post('/profile', (req, res) => {
|
|
57
|
+
const safeName = secure.input(req.body.name);
|
|
58
|
+
const safeBio = secure.html(req.body.bio, { allowedTags: ['p', 'b'] });
|
|
59
|
+
const encryptedSSN = secure.encrypt(req.body.ssn);
|
|
60
|
+
|
|
61
|
+
// Save to database...
|
|
62
|
+
res.json({ success: true });
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## ๐ง Mental Model
|
|
67
|
+
|
|
68
|
+
### Security Layer Architecture
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Request โ CSRF Check โ Rate Limit โ Input Sanitization โ Business Logic
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Protection Types
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Form Protection (CSRF)
|
|
78
|
+
secure.forms(); // Prevents cross-site request forgery
|
|
79
|
+
|
|
80
|
+
// Traffic Protection (Rate Limiting)
|
|
81
|
+
secure.requests(); // Prevents abuse and brute force
|
|
82
|
+
|
|
83
|
+
// Input Protection (XSS Prevention)
|
|
84
|
+
secure.input(text); // Cleans user text input
|
|
85
|
+
secure.html(content); // Sanitizes HTML content
|
|
86
|
+
|
|
87
|
+
// Data Protection (Encryption)
|
|
88
|
+
secure.encrypt(data); // AES-256-GCM encryption
|
|
89
|
+
secure.decrypt(data); // Authenticated decryption
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## ๐ค LLM Quick Reference - Copy These Patterns
|
|
93
|
+
|
|
94
|
+
### **Basic Security Setup (Copy Exactly)**
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// โ
CORRECT - Complete security setup
|
|
98
|
+
import { securityClass } from '@bloomneo/appkit/security';
|
|
99
|
+
const security = securityClass.get();
|
|
100
|
+
|
|
101
|
+
// Required order
|
|
102
|
+
app.use(session({ secret: process.env.SESSION_SECRET }));
|
|
103
|
+
app.use(secure.forms()); // CSRF protection
|
|
104
|
+
app.use('/api', secure.requests()); // Rate limiting
|
|
105
|
+
|
|
106
|
+
// Form with CSRF token
|
|
107
|
+
app.get('/form', (req, res) => {
|
|
108
|
+
const csrfToken = req.csrfToken();
|
|
109
|
+
res.render('form', { csrfToken });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Secure input processing
|
|
113
|
+
app.post('/form', (req, res) => {
|
|
114
|
+
const clean = secure.input(req.body.data);
|
|
115
|
+
const safeHtml = secure.html(req.body.content, { allowedTags: ['p'] });
|
|
116
|
+
const encrypted = secure.encrypt(req.body.sensitive);
|
|
117
|
+
// Process...
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### **Different Rate Limits (Copy These)**
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// โ
CORRECT - Endpoint-specific limits
|
|
125
|
+
app.use('/api', secure.requests(100, 900000)); // 100/15min
|
|
126
|
+
app.use('/auth', secure.requests(5, 3600000)); // 5/hour
|
|
127
|
+
app.post('/upload', secure.requests(10), handler); // 10/15min
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### **Input Sanitization (Copy These)**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// โ
CORRECT - Clean all user input
|
|
134
|
+
const safeName = secure.input(req.body.name, { maxLength: 50 });
|
|
135
|
+
const safeEmail = secure.input(req.body.email?.toLowerCase());
|
|
136
|
+
const safeContent = secure.html(req.body.content, {
|
|
137
|
+
allowedTags: ['p', 'b', 'i', 'a'],
|
|
138
|
+
});
|
|
139
|
+
const safeDisplay = secure.escape(userText);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### **Encryption Patterns (Copy These)**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// โ
CORRECT - Encrypt sensitive data
|
|
146
|
+
const encryptedSSN = secure.encrypt(user.ssn);
|
|
147
|
+
const encryptedPhone = secure.encrypt(user.phone);
|
|
148
|
+
|
|
149
|
+
// โ
CORRECT - Decrypt for authorized access
|
|
150
|
+
const originalSSN = secure.decrypt(encryptedSSN);
|
|
151
|
+
const originalPhone = secure.decrypt(encryptedPhone);
|
|
152
|
+
|
|
153
|
+
// โ
CORRECT - Generate keys
|
|
154
|
+
const newKey = secure.generateKey(); // For production use
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## โ ๏ธ Common LLM Mistakes - Avoid These
|
|
158
|
+
|
|
159
|
+
### **Wrong Middleware Order**
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// โ WRONG - CSRF without sessions
|
|
163
|
+
app.use(secure.forms());
|
|
164
|
+
app.use(session(config)); // Too late!
|
|
165
|
+
|
|
166
|
+
// โ
CORRECT - Sessions first
|
|
167
|
+
app.use(session(config));
|
|
168
|
+
app.use(secure.forms());
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### **Raw Input Storage**
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// โ WRONG - Store raw user input
|
|
175
|
+
await db.save({ content: req.body.content });
|
|
176
|
+
|
|
177
|
+
// โ
CORRECT - Clean first
|
|
178
|
+
const clean = secure.input(req.body.content);
|
|
179
|
+
await db.save({ content: clean });
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### **Missing CSRF Tokens**
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// โ WRONG - Form without CSRF
|
|
186
|
+
res.send('<form method="POST">...');
|
|
187
|
+
|
|
188
|
+
// โ
CORRECT - Include CSRF token
|
|
189
|
+
const csrfToken = req.csrfToken();
|
|
190
|
+
res.send(
|
|
191
|
+
`<form method="POST"><input type="hidden" name="_csrf" value="${csrfToken}">...`
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### **Unsafe Output Display**
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// โ WRONG - Direct user content
|
|
199
|
+
res.send(`<p>User: ${userComment}</p>`);
|
|
200
|
+
|
|
201
|
+
// โ
CORRECT - Escape output
|
|
202
|
+
const safe = secure.escape(userComment);
|
|
203
|
+
res.send(`<p>User: ${safe}</p>`);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## ๐จ Error Handling Patterns
|
|
207
|
+
|
|
208
|
+
### **Startup Validation**
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// โ
App startup validation
|
|
212
|
+
try {
|
|
213
|
+
securityClass.validateRequired({ csrf: true, encryption: true });
|
|
214
|
+
console.log('โ
Security validation passed');
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('โ Security failed:', error.message);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### **Runtime Error Handling**
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// โ
Safe configuration access
|
|
225
|
+
function getDatabaseConfig() {
|
|
226
|
+
try {
|
|
227
|
+
return {
|
|
228
|
+
host: config.getRequired('database.host'),
|
|
229
|
+
ssl: config.get('database.ssl', false),
|
|
230
|
+
};
|
|
231
|
+
} catch (error) {
|
|
232
|
+
throw new Error(`Database config error: ${error.message}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## ๐ฏ Usage Examples
|
|
238
|
+
|
|
239
|
+
### **User Registration**
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
app.post('/auth/register', secure.requests(10, 3600000), async (req, res) => {
|
|
243
|
+
// Clean input
|
|
244
|
+
const email = secure.input(req.body.email?.toLowerCase());
|
|
245
|
+
const name = secure.input(req.body.name, { maxLength: 50 });
|
|
246
|
+
|
|
247
|
+
// Validate
|
|
248
|
+
if (!email || !name || !req.body.password) {
|
|
249
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Hash password
|
|
253
|
+
const hashedPassword = await bcrypt.hash(req.body.password, 12);
|
|
254
|
+
|
|
255
|
+
// Encrypt sensitive data
|
|
256
|
+
const encryptedPhone = req.body.phone ? secure.encrypt(req.body.phone) : null;
|
|
257
|
+
|
|
258
|
+
// Save user
|
|
259
|
+
const user = await createUser({
|
|
260
|
+
email,
|
|
261
|
+
name,
|
|
262
|
+
password: hashedPassword,
|
|
263
|
+
phone: encryptedPhone,
|
|
264
|
+
});
|
|
265
|
+
res.status(201).json({ user: { id: user.id, email, name } });
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### **Blog Post Creation**
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
app.post('/api/posts', async (req, res) => {
|
|
273
|
+
// Sanitize content
|
|
274
|
+
const title = secure.input(req.body.title, { maxLength: 200 });
|
|
275
|
+
const content = secure.html(req.body.content, {
|
|
276
|
+
allowedTags: ['p', 'h1', 'h2', 'b', 'i', 'a', 'ul', 'ol', 'li'],
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Validate
|
|
280
|
+
if (!title || !content) {
|
|
281
|
+
return res.status(400).json({ error: 'Title and content required' });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Create post
|
|
285
|
+
const post = await createBlogPost({ title, content, authorId: req.user.id });
|
|
286
|
+
res.status(201).json({ post });
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### **Comment System**
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
app.post(
|
|
294
|
+
'/api/posts/:id/comments',
|
|
295
|
+
secure.requests(5, 300000),
|
|
296
|
+
async (req, res) => {
|
|
297
|
+
const postId = secure.input(req.params.id);
|
|
298
|
+
const content = secure.html(req.body.content, {
|
|
299
|
+
allowedTags: ['p', 'b', 'i'],
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (!content || content.length < 10) {
|
|
303
|
+
return res.status(400).json({ error: 'Comment too short' });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const comment = await createComment({
|
|
307
|
+
postId: parseInt(postId),
|
|
308
|
+
content,
|
|
309
|
+
authorId: req.user.id,
|
|
310
|
+
});
|
|
311
|
+
res.status(201).json({ comment });
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### **Data Encryption Service**
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
class UserDataService {
|
|
320
|
+
static async createProfile(userData) {
|
|
321
|
+
// Encrypt PII
|
|
322
|
+
const encryptedSSN = userData.ssn ? secure.encrypt(userData.ssn) : null;
|
|
323
|
+
const encryptedPhone = userData.phone
|
|
324
|
+
? secure.encrypt(userData.phone)
|
|
325
|
+
: null;
|
|
326
|
+
|
|
327
|
+
// Clean public data
|
|
328
|
+
const name = secure.input(userData.name, { maxLength: 100 });
|
|
329
|
+
const bio = secure.html(userData.bio, { allowedTags: ['p', 'b', 'i'] });
|
|
330
|
+
|
|
331
|
+
return await db.users.create({
|
|
332
|
+
name,
|
|
333
|
+
bio,
|
|
334
|
+
email: userData.email,
|
|
335
|
+
ssn: encryptedSSN,
|
|
336
|
+
phone: encryptedPhone,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
static async getProfile(userId, requestingUserId) {
|
|
341
|
+
const user = await db.users.findById(userId);
|
|
342
|
+
if (!user) throw new Error('User not found');
|
|
343
|
+
|
|
344
|
+
const profile = {
|
|
345
|
+
id: user.id,
|
|
346
|
+
name: user.name,
|
|
347
|
+
bio: user.bio,
|
|
348
|
+
email: user.email,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Decrypt for authorized users
|
|
352
|
+
if (userId === requestingUserId || (await isAdmin(requestingUserId))) {
|
|
353
|
+
if (user.ssn) profile.ssn = secure.decrypt(user.ssn);
|
|
354
|
+
if (user.phone) profile.phone = secure.decrypt(user.phone);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return profile;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## ๐ Complete API Reference
|
|
363
|
+
|
|
364
|
+
### **Core Function**
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
const security = securityClass.get(); // One function, everything you need
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### **Middleware Methods**
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
secure.forms(options?); // CSRF protection
|
|
374
|
+
secure.requests(max?, window?); // Rate limiting
|
|
375
|
+
securityClass.quickSetup(options?); // Quick middleware array
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### **Input Sanitization**
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
secure.input(text, options?); // XSS prevention
|
|
382
|
+
secure.html(html, options?); // HTML sanitization
|
|
383
|
+
secure.escape(text); // HTML entity escaping
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### **Data Encryption**
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
secure.encrypt(data, key?); // AES-256-GCM encryption
|
|
390
|
+
secure.decrypt(data, key?); // Authenticated decryption
|
|
391
|
+
secure.generateKey(); // 256-bit key generation
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### **Utility Methods**
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
securityClass.getConfig(); // Current configuration
|
|
398
|
+
securityClass.getStatus(); // Security feature status
|
|
399
|
+
securityClass.validateRequired(checks); // Startup validation
|
|
400
|
+
securityClass.isDevelopment(); // Environment helpers
|
|
401
|
+
securityClass.isProduction();
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## ๐ Environment Variables
|
|
405
|
+
|
|
406
|
+
### **Required Configuration**
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
# CSRF Protection
|
|
410
|
+
BLOOM_SECURITY_CSRF_SECRET=your-csrf-secret-key-2024-minimum-32-chars
|
|
411
|
+
|
|
412
|
+
# Data Encryption
|
|
413
|
+
BLOOM_SECURITY_ENCRYPTION_KEY=64-char-hex-key-for-aes256-encryption
|
|
414
|
+
|
|
415
|
+
# Generate encryption key:
|
|
416
|
+
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### **Optional Configuration**
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# Rate Limiting
|
|
423
|
+
BLOOM_SECURITY_RATE_LIMIT=100 # Requests per window
|
|
424
|
+
BLOOM_SECURITY_RATE_WINDOW=900000 # Window in ms (15 min)
|
|
425
|
+
|
|
426
|
+
# Input Sanitization
|
|
427
|
+
BLOOM_SECURITY_MAX_INPUT_LENGTH=1000 # Max input length
|
|
428
|
+
BLOOM_SECURITY_ALLOWED_TAGS=p,b,i,a # Allowed HTML tags
|
|
429
|
+
|
|
430
|
+
# CSRF Settings
|
|
431
|
+
BLOOM_SECURITY_CSRF_EXPIRY=60 # Token expiry minutes
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## ๐ Security Features
|
|
435
|
+
|
|
436
|
+
### **CSRF Protection** (`secure.forms()`)
|
|
437
|
+
|
|
438
|
+
- Generates cryptographically secure tokens using `crypto.randomBytes()`
|
|
439
|
+
- Stores tokens in sessions with expiration timestamps
|
|
440
|
+
- Validates using timing-safe comparison with `crypto.timingSafeEqual()`
|
|
441
|
+
- Automatically checks POST/PUT/DELETE/PATCH requests
|
|
442
|
+
|
|
443
|
+
### **Rate Limiting** (`secure.requests()`)
|
|
444
|
+
|
|
445
|
+
- In-memory tracking with automatic cleanup
|
|
446
|
+
- Sliding window algorithm for accurate limiting
|
|
447
|
+
- Standard HTTP headers (X-RateLimit-\*, Retry-After)
|
|
448
|
+
- Configurable per endpoint
|
|
449
|
+
|
|
450
|
+
### **Input Sanitization** (`secure.input()`, `secure.html()`)
|
|
451
|
+
|
|
452
|
+
- Removes dangerous patterns: `<script>`, `javascript:`, `on*=` handlers
|
|
453
|
+
- Whitelist-based HTML tag filtering
|
|
454
|
+
- Length limiting to prevent memory exhaustion
|
|
455
|
+
- HTML entity escaping for safe display
|
|
456
|
+
|
|
457
|
+
### **Data Encryption** (`secure.encrypt()`, `secure.decrypt()`)
|
|
458
|
+
|
|
459
|
+
- AES-256-GCM authenticated encryption
|
|
460
|
+
- Random IV per encryption operation
|
|
461
|
+
- Authentication tags to detect tampering
|
|
462
|
+
- Optional Associated Additional Data (AAD)
|
|
463
|
+
|
|
464
|
+
## ๐ก๏ธ Production Deployment
|
|
465
|
+
|
|
466
|
+
### **Environment Setup**
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
# โ
Required in production
|
|
470
|
+
BLOOM_SECURITY_CSRF_SECRET=64-char-random-string
|
|
471
|
+
BLOOM_SECURITY_ENCRYPTION_KEY=64-char-hex-string
|
|
472
|
+
|
|
473
|
+
# โ
Optional but recommended
|
|
474
|
+
BLOOM_SECURITY_RATE_LIMIT=100
|
|
475
|
+
BLOOM_SECURITY_RATE_WINDOW=900000
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### **Security Middleware Order**
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
// โ
Correct order for maximum protection
|
|
482
|
+
app.use(express.json({ limit: '10mb' }));
|
|
483
|
+
app.use(session(config)); // 1. Sessions first
|
|
484
|
+
app.use(secure.forms()); // 2. CSRF protection
|
|
485
|
+
app.use('/api', secure.requests()); // 3. Rate limiting
|
|
486
|
+
app.use('/auth', secure.requests(5, 3600000)); // 4. Strict auth limits
|
|
487
|
+
app.use('/api', apiRoutes); // 5. Application routes
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### **Input Validation Pattern**
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// โ
Comprehensive input validation middleware
|
|
494
|
+
function validateInput(req, res, next) {
|
|
495
|
+
if (req.body.name)
|
|
496
|
+
req.body.name = secure.input(req.body.name, { maxLength: 50 });
|
|
497
|
+
if (req.body.email)
|
|
498
|
+
req.body.email = secure.input(req.body.email?.toLowerCase());
|
|
499
|
+
if (req.body.content)
|
|
500
|
+
req.body.content = secure.html(req.body.content, {
|
|
501
|
+
allowedTags: ['p', 'b', 'i'],
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
if (!req.body.name || !req.body.email) {
|
|
505
|
+
return res.status(400).json({ error: 'Name and email required' });
|
|
506
|
+
}
|
|
507
|
+
next();
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### **Key Management**
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
// โ
Generate production keys
|
|
515
|
+
const encryptionKey = securityClass.generateKey();
|
|
516
|
+
console.log(`BLOOM_SECURITY_ENCRYPTION_KEY=${encryptionKey}`);
|
|
517
|
+
|
|
518
|
+
// โ
Validate configuration at startup
|
|
519
|
+
securityClass.validateRequired({ csrf: true, encryption: true });
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## ๐งช Testing
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import { securityClass } from '@bloomneo/appkit/security';
|
|
526
|
+
|
|
527
|
+
describe('Security Tests', () => {
|
|
528
|
+
beforeEach(() => securityClass.clearCache());
|
|
529
|
+
|
|
530
|
+
test('should generate and verify CSRF tokens', () => {
|
|
531
|
+
const secure = securityClass.reset({
|
|
532
|
+
csrf: { secret: 'test-secret-32-characters-long' },
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
const mockReq = { session: {} };
|
|
536
|
+
const middleware = secure.forms();
|
|
537
|
+
middleware(mockReq, {}, () => {});
|
|
538
|
+
|
|
539
|
+
const token = mockReq.csrfToken();
|
|
540
|
+
expect(token).toBeDefined();
|
|
541
|
+
expect(typeof token).toBe('string');
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test('should encrypt and decrypt correctly', () => {
|
|
545
|
+
const secure = securityClass.reset({
|
|
546
|
+
encryption: { key: 'a'.repeat(64) },
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const data = 'sensitive information';
|
|
550
|
+
const encrypted = secure.encrypt(data);
|
|
551
|
+
const decrypted = secure.decrypt(encrypted);
|
|
552
|
+
|
|
553
|
+
expect(decrypted).toBe(data);
|
|
554
|
+
expect(encrypted).toMatch(/^[0-9a-f]+:[0-9a-f]+:[0-9a-f]+$/);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test('should sanitize malicious input', () => {
|
|
558
|
+
const security = securityClass.get();
|
|
559
|
+
const malicious = '<script>alert("xss")</script><p>Safe</p>';
|
|
560
|
+
const cleaned = secure.html(malicious, { allowedTags: ['p'] });
|
|
561
|
+
|
|
562
|
+
expect(cleaned).toBe('<p>Safe</p>');
|
|
563
|
+
expect(cleaned).not.toContain('<script>');
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### **Mock Configuration**
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
function createTestSecurity(overrides = {}) {
|
|
572
|
+
return securityClass.reset({
|
|
573
|
+
csrf: { secret: 'test-secret-32-characters-long' },
|
|
574
|
+
encryption: { key: 'a'.repeat(64) },
|
|
575
|
+
environment: { isDevelopment: true, isTest: true },
|
|
576
|
+
...overrides,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
## ๐ Performance
|
|
582
|
+
|
|
583
|
+
- **CSRF Operations**: ~1ms per token generation/verification
|
|
584
|
+
- **Rate Limiting**: In-memory with O(1) lookup, automatic cleanup
|
|
585
|
+
- **Input Sanitization**: ~0.1ms per operation
|
|
586
|
+
- **Encryption**: ~2ms per encrypt/decrypt (AES-256-GCM)
|
|
587
|
+
- **Memory Usage**: <2MB additional overhead
|
|
588
|
+
|
|
589
|
+
## ๐ TypeScript Support
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
import type {
|
|
593
|
+
SecurityConfig,
|
|
594
|
+
ExpressMiddleware,
|
|
595
|
+
CSRFOptions,
|
|
596
|
+
RateLimitOptions,
|
|
597
|
+
} from '@bloomneo/appkit/security';
|
|
598
|
+
|
|
599
|
+
const security = securityClass.get();
|
|
600
|
+
const middleware: ExpressMiddleware = secure.forms();
|
|
601
|
+
const encrypted: string = secure.encrypt(sensitiveData);
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
## ๐ License
|
|
605
|
+
|
|
606
|
+
MIT ยฉ [Bloomneo](https://github.com/bloomneo)
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
<p align="center">
|
|
611
|
+
Built with โค๏ธ in India by the <a href="https://github.com/orgs/bloomneo/people">Bloomneo Team</a>
|
|
612
|
+
</p>
|