k9guard 1.0.3 → 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 +196 -5
- 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 +196 -5
- package/package.json +35 -13
- package/index.ts +0 -4
- package/src/K9Guard.ts +0 -97
- package/src/core/captchaGenerator.ts +0 -372
- package/src/core/captchaValidator.ts +0 -198
- package/src/types/index.ts +0 -84
- package/src/utils/crypto.ts +0 -336
- package/src/utils/customQuestionGenerator.ts +0 -40
- package/src/utils/emojiGenerator.ts +0 -88
- package/src/utils/imageGenerator.ts +0 -154
- 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 -34
- package/src/validators/customQuestionValidator.ts +0 -88
- package/tsconfig.json +0 -29
package/README.md
CHANGED
|
@@ -10,13 +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
|
|
15
16
|
- **Strict Configuration**: Invalid `type` or `difficulty` values throw immediately; no silent fallbacks
|
|
16
17
|
- **Input Validation**: Length limits, strict numeric parsing, type checking, and sanitization to prevent injection attacks
|
|
17
18
|
- **Custom Questions**: Support for your own questions with validation and sanitization
|
|
18
19
|
- **Zero Dependencies**: Lightweight with no external dependencies
|
|
19
|
-
- **Well Tested**:
|
|
20
|
+
- **Well Tested**: 228+ tests covering unit, integration, security, edge-case, and benchmark scenarios
|
|
20
21
|
- **OWASP Compliant**: Follows OWASP Top 10 security guidelines
|
|
21
22
|
- **Privacy Compliant**: GDPR/KVKK compliant with no personal data storage
|
|
22
23
|
|
|
@@ -192,6 +193,101 @@ const challenge = captcha.generate();
|
|
|
192
193
|
const isValid = captcha.validate(challenge, "paris");
|
|
193
194
|
```
|
|
194
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
|
+
|
|
195
291
|
## API Reference
|
|
196
292
|
|
|
197
293
|
### Constructor Options
|
|
@@ -203,7 +299,8 @@ Both `type` and `difficulty` are **required** and strictly validated. Passing an
|
|
|
203
299
|
```typescript
|
|
204
300
|
interface K9GuardOptions {
|
|
205
301
|
type: 'math' | 'text' | 'sequence' | 'scramble' | 'reverse' | 'mixed' | 'multi' | 'image' | 'emoji';
|
|
206
|
-
difficulty: 'easy' | 'medium' | 'hard';
|
|
302
|
+
difficulty: 'easy' | 'medium' | 'hard' | 'adaptive';
|
|
303
|
+
sessionId?: string; // optional, required when difficulty is 'adaptive'
|
|
207
304
|
}
|
|
208
305
|
```
|
|
209
306
|
|
|
@@ -213,6 +310,7 @@ interface K9GuardOptions {
|
|
|
213
310
|
interface K9GuardCustomOptions {
|
|
214
311
|
type: 'custom';
|
|
215
312
|
questions: CustomQuestion[];
|
|
313
|
+
sessionId?: string;
|
|
216
314
|
}
|
|
217
315
|
|
|
218
316
|
interface CustomQuestion {
|
|
@@ -224,10 +322,12 @@ interface CustomQuestion {
|
|
|
224
322
|
|
|
225
323
|
### Methods
|
|
226
324
|
|
|
227
|
-
#### `generate(): CaptchaChallenge`
|
|
325
|
+
#### `generate(sessionId?: string): CaptchaChallenge`
|
|
228
326
|
|
|
229
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`.
|
|
230
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
|
+
|
|
231
331
|
```typescript
|
|
232
332
|
const challenge = captcha.generate();
|
|
233
333
|
console.log(challenge.question); // the question to show the user
|
|
@@ -239,10 +339,12 @@ console.log(challenge.category); // category name (only for type: 'emoji')
|
|
|
239
339
|
// challenge.answer / .hashedAnswer / .salt — NOT present; never sent to client
|
|
240
340
|
```
|
|
241
341
|
|
|
242
|
-
#### `validate(challenge: CaptchaChallenge, userInput: string): boolean`
|
|
342
|
+
#### `validate(challenge: CaptchaChallenge, userInput: string, sessionId?: string): boolean`
|
|
243
343
|
|
|
244
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.
|
|
245
345
|
|
|
346
|
+
When `difficulty` is `'adaptive'`, the validation result is automatically recorded for the session and the difficulty is adjusted accordingly.
|
|
347
|
+
|
|
246
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.
|
|
247
349
|
|
|
248
350
|
```typescript
|
|
@@ -255,6 +357,95 @@ if (!isValid) {
|
|
|
255
357
|
}
|
|
256
358
|
```
|
|
257
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';
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Testing
|
|
423
|
+
|
|
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
|
|
427
|
+
|
|
428
|
+
```bash
|
|
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
|
|
437
|
+
```
|
|
438
|
+
|
|
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) |
|
|
448
|
+
|
|
258
449
|
## Contributing
|
|
259
450
|
|
|
260
451
|
We welcome contributions! Here's how you can help:
|
|
@@ -262,7 +453,7 @@ We welcome contributions! Here's how you can help:
|
|
|
262
453
|
1. **Fork the repository**
|
|
263
454
|
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
|
|
264
455
|
3. **Add tests** for your changes
|
|
265
|
-
4. **Run tests**: `bun
|
|
456
|
+
4. **Run tests**: `bun test`
|
|
266
457
|
5. **Commit your changes**: `git commit -m 'feat: add amazing feature'`
|
|
267
458
|
6. **Push to branch**: `git push origin feature/amazing-feature`
|
|
268
459
|
7. **Open a Pull Request**
|