k9guard 1.0.2 → 1.0.4
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 +202 -14
- package/dist/index.cjs +1555 -0
- package/dist/index.d.cts +141 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +1526 -0
- package/docs/tr/README.md +203 -15
- package/package.json +35 -13
- package/index.ts +0 -4
- package/src/K9Guard.ts +0 -91
- package/src/core/captchaGenerator.ts +0 -298
- package/src/core/captchaValidator.ts +0 -182
- package/src/types/index.ts +0 -84
- package/src/utils/crypto.ts +0 -193
- package/src/utils/customQuestionGenerator.ts +0 -40
- package/src/utils/emojiGenerator.ts +0 -88
- package/src/utils/imageGenerator.ts +0 -152
- package/src/utils/random.ts +0 -43
- package/src/utils/reverseGenerator.ts +0 -54
- package/src/utils/scrambleGenerator.ts +0 -49
- package/src/utils/sequenceGenerator.ts +0 -30
- package/src/validators/customQuestionValidator.ts +0 -88
- package/tsconfig.json +0 -29
package/README.md
CHANGED
|
@@ -10,11 +10,14 @@ A secure, lightweight, and flexible CAPTCHA module for TypeScript/JavaScript pro
|
|
|
10
10
|
|
|
11
11
|
- **Cryptographically Secure**: NIST SP 800-90A compliant random generation
|
|
12
12
|
- **10 CAPTCHA Types**: Math, text, sequence, scramble, reverse, mixed, multi-step, image, emoji, and custom challenges
|
|
13
|
+
- **Adaptive Difficulty**: Automatically adjusts challenge difficulty based on user success rate
|
|
13
14
|
- **Security First**: SHA-256 salted hashing, server-side challenge store, nonce-based session management, and 5-minute expiry
|
|
14
|
-
- **
|
|
15
|
+
- **Single-Use Challenges**: Every nonce is consumed on the first `validate()` call — success or failure — preventing replay and brute-force attacks
|
|
16
|
+
- **Strict Configuration**: Invalid `type` or `difficulty` values throw immediately; no silent fallbacks
|
|
17
|
+
- **Input Validation**: Length limits, strict numeric parsing, type checking, and sanitization to prevent injection attacks
|
|
15
18
|
- **Custom Questions**: Support for your own questions with validation and sanitization
|
|
16
19
|
- **Zero Dependencies**: Lightweight with no external dependencies
|
|
17
|
-
- **Well Tested**:
|
|
20
|
+
- **Well Tested**: 228+ tests covering unit, integration, security, edge-case, and benchmark scenarios
|
|
18
21
|
- **OWASP Compliant**: Follows OWASP Top 10 security guidelines
|
|
19
22
|
- **Privacy Compliant**: GDPR/KVKK compliant with no personal data storage
|
|
20
23
|
|
|
@@ -190,16 +193,114 @@ const challenge = captcha.generate();
|
|
|
190
193
|
const isValid = captcha.validate(challenge, "paris");
|
|
191
194
|
```
|
|
192
195
|
|
|
196
|
+
### Adaptive Difficulty
|
|
197
|
+
|
|
198
|
+
Adaptive difficulty automatically adjusts the challenge difficulty based on the user's success rate. This provides a better user experience — easy challenges for struggling users and harder challenges for those who solve quickly.
|
|
199
|
+
|
|
200
|
+
#### How It Works
|
|
201
|
+
|
|
202
|
+
- Tracks the last 10 attempts per session (sliding window)
|
|
203
|
+
- **80%+ success rate** — difficulty increases (easy -> medium -> hard)
|
|
204
|
+
- **40% or lower success rate** — difficulty decreases (hard -> medium -> easy)
|
|
205
|
+
- **40-80% success rate** — difficulty stays stable
|
|
206
|
+
- Minimum 3 attempts required before any adjustment (hysteresis)
|
|
207
|
+
- Sessions expire after 30 minutes of inactivity
|
|
208
|
+
- Maximum 10,000 concurrent sessions (oldest evicted automatically)
|
|
209
|
+
|
|
210
|
+
#### Option 1: Session ID in Constructor
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
const captcha = new K9Guard({
|
|
214
|
+
type: 'math',
|
|
215
|
+
difficulty: 'adaptive',
|
|
216
|
+
sessionId: 'user-123' // any unique string (user ID, session token, IP, etc.)
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const challenge = captcha.generate(); // uses user-123's current difficulty
|
|
220
|
+
const isValid = captcha.validate(challenge, userAnswer); // records result automatically
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Option 2: Session ID as Parameter
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const captcha = new K9Guard({ type: 'math', difficulty: 'adaptive' });
|
|
227
|
+
|
|
228
|
+
// pass sessionId with each call
|
|
229
|
+
const challenge = captcha.generate('user-123');
|
|
230
|
+
const isValid = captcha.validate(challenge, userAnswer, 'user-123');
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Option 3: Flexible (Both)
|
|
234
|
+
|
|
235
|
+
Constructor `sessionId` is the default. Parameter `sessionId` overrides it.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const captcha = new K9Guard({
|
|
239
|
+
type: 'math',
|
|
240
|
+
difficulty: 'adaptive',
|
|
241
|
+
sessionId: 'default-user'
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
captcha.generate(); // uses 'default-user'
|
|
245
|
+
captcha.generate('other-user'); // uses 'other-user' (overrides default)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### Session Management
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// get current difficulty for a session
|
|
252
|
+
const difficulty = captcha.getSessionDifficulty('user-123'); // 'easy' | 'medium' | 'hard' | null
|
|
253
|
+
|
|
254
|
+
// clear a specific session (resets to 'medium')
|
|
255
|
+
captcha.clearSession('user-123');
|
|
256
|
+
|
|
257
|
+
// clear all sessions
|
|
258
|
+
captcha.clearAllSessions();
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Works with All CAPTCHA Types
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// adaptive works with any captcha type
|
|
265
|
+
const captcha = new K9Guard({ type: 'image', difficulty: 'adaptive', sessionId: 'user-1' });
|
|
266
|
+
const captcha = new K9Guard({ type: 'emoji', difficulty: 'adaptive', sessionId: 'user-1' });
|
|
267
|
+
const captcha = new K9Guard({ type: 'reverse', difficulty: 'adaptive', sessionId: 'user-1' });
|
|
268
|
+
// ... and so on
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### Express.js Example
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import express from 'express';
|
|
275
|
+
import K9Guard from 'k9guard';
|
|
276
|
+
|
|
277
|
+
const app = express();
|
|
278
|
+
const captcha = new K9Guard({ type: 'math', difficulty: 'adaptive' });
|
|
279
|
+
|
|
280
|
+
app.get('/captcha', (req, res) => {
|
|
281
|
+
const challenge = captcha.generate(req.sessionID);
|
|
282
|
+
res.json(challenge);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
app.post('/verify', (req, res) => {
|
|
286
|
+
const isValid = captcha.validate(req.body.challenge, req.body.answer, req.sessionID);
|
|
287
|
+
res.json({ valid: isValid });
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
193
291
|
## API Reference
|
|
194
292
|
|
|
195
293
|
### Constructor Options
|
|
196
294
|
|
|
295
|
+
Both `type` and `difficulty` are **required** and strictly validated. Passing an invalid value throws an error immediately.
|
|
296
|
+
|
|
197
297
|
#### Standard CAPTCHA Options
|
|
198
298
|
|
|
199
299
|
```typescript
|
|
200
300
|
interface K9GuardOptions {
|
|
201
301
|
type: 'math' | 'text' | 'sequence' | 'scramble' | 'reverse' | 'mixed' | 'multi' | 'image' | 'emoji';
|
|
202
|
-
difficulty: 'easy' | 'medium' | 'hard';
|
|
302
|
+
difficulty: 'easy' | 'medium' | 'hard' | 'adaptive';
|
|
303
|
+
sessionId?: string; // optional, required when difficulty is 'adaptive'
|
|
203
304
|
}
|
|
204
305
|
```
|
|
205
306
|
|
|
@@ -209,6 +310,7 @@ interface K9GuardOptions {
|
|
|
209
310
|
interface K9GuardCustomOptions {
|
|
210
311
|
type: 'custom';
|
|
211
312
|
questions: CustomQuestion[];
|
|
313
|
+
sessionId?: string;
|
|
212
314
|
}
|
|
213
315
|
|
|
214
316
|
interface CustomQuestion {
|
|
@@ -220,10 +322,12 @@ interface CustomQuestion {
|
|
|
220
322
|
|
|
221
323
|
### Methods
|
|
222
324
|
|
|
223
|
-
#### `generate(): CaptchaChallenge`
|
|
325
|
+
#### `generate(sessionId?: string): CaptchaChallenge`
|
|
224
326
|
|
|
225
327
|
Generates a new CAPTCHA challenge. Returns a **public** object safe to send to the client — `answer`, `hashedAnswer` and `salt` are stripped and stored server-side, keyed by `nonce`.
|
|
226
328
|
|
|
329
|
+
When `difficulty` is `'adaptive'`, the `sessionId` parameter is used to look up the user's current difficulty level. If a `sessionId` was provided in the constructor, it is used as the default.
|
|
330
|
+
|
|
227
331
|
```typescript
|
|
228
332
|
const challenge = captcha.generate();
|
|
229
333
|
console.log(challenge.question); // the question to show the user
|
|
@@ -235,28 +339,112 @@ console.log(challenge.category); // category name (only for type: 'emoji')
|
|
|
235
339
|
// challenge.answer / .hashedAnswer / .salt — NOT present; never sent to client
|
|
236
340
|
```
|
|
237
341
|
|
|
238
|
-
#### `validate(challenge: CaptchaChallenge, userInput: string): boolean`
|
|
342
|
+
#### `validate(challenge: CaptchaChallenge, userInput: string, sessionId?: string): boolean`
|
|
239
343
|
|
|
240
344
|
Validates user input against the stored server-side record (looked up by `challenge.nonce`). Returns `true` if correct, `false` otherwise. Tampered `hashedAnswer` or `salt` on the public challenge object have no effect.
|
|
241
345
|
|
|
346
|
+
When `difficulty` is `'adaptive'`, the validation result is automatically recorded for the session and the difficulty is adjusted accordingly.
|
|
347
|
+
|
|
348
|
+
> **⚠️ Single-use semantics:** `validate()` consumes the nonce on the **first call**, regardless of whether the answer is correct or not. After any validation attempt, the challenge is invalidated. Always call `generate()` again before presenting a new challenge to the user.
|
|
349
|
+
|
|
242
350
|
```typescript
|
|
243
351
|
const isValid = captcha.validate(challenge, userAnswer);
|
|
352
|
+
|
|
353
|
+
// After validate(), the challenge is consumed.
|
|
354
|
+
// For a retry, generate a fresh challenge:
|
|
355
|
+
if (!isValid) {
|
|
356
|
+
const newChallenge = captcha.generate();
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### `getSessionDifficulty(sessionId: string): Difficulty | null`
|
|
361
|
+
|
|
362
|
+
Returns the current adaptive difficulty for a session. Returns `null` if the instance is not in adaptive mode.
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const difficulty = captcha.getSessionDifficulty('user-123');
|
|
366
|
+
// 'easy' | 'medium' | 'hard' | null
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### `clearSession(sessionId: string): boolean`
|
|
370
|
+
|
|
371
|
+
Removes a specific adaptive session. Returns `true` if the session existed, `false` otherwise.
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
captcha.clearSession('user-123');
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### `clearAllSessions(): void`
|
|
378
|
+
|
|
379
|
+
Removes all adaptive sessions.
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
captcha.clearAllSessions();
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Exported Utilities
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import K9Guard, { AdaptiveTracker, CustomQuestionValidator, CustomQuestionGenerator } from 'k9guard';
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
| Export | Description |
|
|
392
|
+
|--------|-------------|
|
|
393
|
+
| `K9Guard` (default) | Main CAPTCHA class |
|
|
394
|
+
| `AdaptiveTracker` | Standalone adaptive difficulty tracker (useful for custom integrations) |
|
|
395
|
+
| `CustomQuestionValidator` | Validate and sanitize custom question arrays |
|
|
396
|
+
| `CustomQuestionGenerator` | Generate from custom question pools |
|
|
397
|
+
|
|
398
|
+
### Type Exports
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import type {
|
|
402
|
+
K9GuardOptions,
|
|
403
|
+
K9GuardCustomOptions,
|
|
404
|
+
CaptchaChallenge,
|
|
405
|
+
CustomQuestion,
|
|
406
|
+
Difficulty,
|
|
407
|
+
AdaptiveSession,
|
|
408
|
+
AdaptiveAttempt,
|
|
409
|
+
StoredChallenge,
|
|
410
|
+
ImageCaptcha,
|
|
411
|
+
MathCaptcha,
|
|
412
|
+
TextCaptcha,
|
|
413
|
+
SequenceCaptcha,
|
|
414
|
+
ScrambleCaptcha,
|
|
415
|
+
ReverseCaptcha,
|
|
416
|
+
MixedCaptcha,
|
|
417
|
+
CustomCaptcha,
|
|
418
|
+
EmojiCaptcha,
|
|
419
|
+
} from 'k9guard';
|
|
244
420
|
```
|
|
245
421
|
|
|
246
422
|
## Testing
|
|
247
423
|
|
|
248
|
-
|
|
424
|
+
K9Guard uses `bun:test` for its test suite with 228+ tests covering unit, integration, security, edge-case, and benchmark scenarios.
|
|
425
|
+
|
|
426
|
+
### Run Tests
|
|
249
427
|
|
|
250
428
|
```bash
|
|
251
|
-
|
|
429
|
+
# run all tests
|
|
430
|
+
bun test
|
|
431
|
+
|
|
432
|
+
# run tests in watch mode
|
|
433
|
+
bun run test:watch
|
|
434
|
+
|
|
435
|
+
# run tests with coverage
|
|
436
|
+
bun run test:coverage
|
|
252
437
|
```
|
|
253
438
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
-
|
|
439
|
+
### Test Categories
|
|
440
|
+
|
|
441
|
+
| Category | Coverage |
|
|
442
|
+
|----------|----------|
|
|
443
|
+
| **Unit** | Each module tested independently with correct outputs and edge cases |
|
|
444
|
+
| **Integration** | Full generate-validate flow for all 10 captcha types + adaptive mode |
|
|
445
|
+
| **Security** | Timing attack resistance, nonce replay prevention, hash injection, input sanitization, SVG injection |
|
|
446
|
+
| **Edge Cases** | Division by zero, unicode characters, concurrent generators, invalid inputs |
|
|
447
|
+
| **Benchmark** | Performance assertions for all captcha types (generate < 5ms, validate < 5ms) |
|
|
260
448
|
|
|
261
449
|
## Contributing
|
|
262
450
|
|
|
@@ -265,7 +453,7 @@ We welcome contributions! Here's how you can help:
|
|
|
265
453
|
1. **Fork the repository**
|
|
266
454
|
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
|
|
267
455
|
3. **Add tests** for your changes
|
|
268
|
-
4. **Run tests**: `bun
|
|
456
|
+
4. **Run tests**: `bun test`
|
|
269
457
|
5. **Commit your changes**: `git commit -m 'feat: add amazing feature'`
|
|
270
458
|
6. **Push to branch**: `git push origin feature/amazing-feature`
|
|
271
459
|
7. **Open a Pull Request**
|