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/docs/tr/README.md CHANGED
@@ -10,13 +10,14 @@ TypeScript/JavaScript projeleri için kriptografik güvenlik sunan güvenli, haf
10
10
 
11
11
  - **Kriptografik Güvenlik**: NIST SP 800-90A standardına uyumluluk sağlanmıştır
12
12
  - **10 CAPTCHA Türü**: Matematik, metin, dizi, karıştırma, ters çevirme, karma, çok adımlı, görsel, emoji ve özel doğrulama yöntemleri
13
+ - **Adaptif Zorluk**: Kullanıcı başarı oranına göre zorluk seviyesini otomatik ayarlar
13
14
  - **Güvenlik Odaklı**: SHA-256 tuzlu hash algoritması, sunucu taraflı challenge deposu, nonce tabanlı oturum yönetimi ve 5 dakikalık geçerlilik süresi
14
15
  - **Tek Kullanımlık Challenge**: Her nonce, `validate()` çağrısında — doğru ya da yanlış fark etmeksizin — tüketilir; replay ve brute-force saldırıları engellenir
15
16
  - **Katı Yapılandırma**: Geçersiz `type` veya `difficulty` değerleri anında hata fırlatır; sessiz fallback yoktur
16
17
  - **Girdi Doğrulama**: Enjeksiyon saldırılarını önlemek için uzunluk sınırlamaları, katı sayısal ayrıştırma, tip kontrolü ve sanitizasyon
17
18
  - **Özel Sorular**: Doğrulama ve sanitizasyon ile kendi sorularınızı tanımlama desteği
18
19
  - **Sıfır Bağımlılık**: Harici bağımlılık gerektirmeyen hafif yapı
19
- - **Kapsamlı Test Edilmiş**: durumlar ve güvenlik senaryoları dahil olmak üzere geniş test kapsama alanı
20
+ - **Kapsamlı Test Edilmiş**: 228+ test ile birim, entegrasyon, güvenlik, durum ve benchmark senaryoları
20
21
  - **OWASP Uyumlu**: OWASP Top 10 güvenlik yönergelerine uygun geliştirme
21
22
  - **Gizlilik Uyumlu**: Kişisel veri saklamayan GDPR/KVKK uyumlu mimari
22
23
 
@@ -192,6 +193,101 @@ const challenge = captcha.generate();
192
193
  const isValid = captcha.validate(challenge, "ankara");
193
194
  ```
194
195
 
196
+ ### Adaptif Zorluk
197
+
198
+ Adaptif zorluk, kullanıcının başarı oranına göre zorluk seviyesini otomatik olarak ayarlar. Bu sayede zorlanan kullanıcılara kolay, hızlı çözen kullanıcılara ise daha zor challenge'lar sunulur.
199
+
200
+ #### Nasıl Çalışır?
201
+
202
+ - Oturum başına son 10 denemeyi takip eder (kayan pencere)
203
+ - **%80+ başarı oranı** — zorluk artar (easy -> medium -> hard)
204
+ - **%40 ve altı başarı oranı** — zorluk düşer (hard -> medium -> easy)
205
+ - **%40-80 arası** — zorluk sabit kalır
206
+ - Herhangi bir ayarlama öncesinde minimum 3 deneme gerekli (histerezis)
207
+ - Oturumlar 30 dakika hareketsizlik sonra otomatik sona erer
208
+ - Maksimum 10.000 eş zamanlı oturum (en eski otomatik temizlenir)
209
+
210
+ #### Seçenek 1: Constructor'da Session ID
211
+
212
+ ```typescript
213
+ const captcha = new K9Guard({
214
+ type: 'math',
215
+ difficulty: 'adaptive',
216
+ sessionId: 'user-123' // herhangi benzersiz string (kullanıcı ID, oturum token'ı, IP vb.)
217
+ });
218
+
219
+ const challenge = captcha.generate(); // user-123'ün mevcut zorluğunu kullanır
220
+ const isValid = captcha.validate(challenge, userAnswer); // sonucu otomatik kaydeder
221
+ ```
222
+
223
+ #### Seçenek 2: Parametre Olarak Session ID
224
+
225
+ ```typescript
226
+ const captcha = new K9Guard({ type: 'math', difficulty: 'adaptive' });
227
+
228
+ // her çağrıda sessionId geçir
229
+ const challenge = captcha.generate('user-123');
230
+ const isValid = captcha.validate(challenge, userAnswer, 'user-123');
231
+ ```
232
+
233
+ #### Seçenek 3: Esnek (Her İkisi)
234
+
235
+ Constructor'daki `sessionId` varsayılan olarak kullanılır. Parametre olarak verilen `sessionId` onu override eder.
236
+
237
+ ```typescript
238
+ const captcha = new K9Guard({
239
+ type: 'math',
240
+ difficulty: 'adaptive',
241
+ sessionId: 'varsayilan-kullanici'
242
+ });
243
+
244
+ captcha.generate(); // 'varsayilan-kullanici' kullanır
245
+ captcha.generate('diger-kullanici'); // 'diger-kullanici' kullanır (override)
246
+ ```
247
+
248
+ #### Oturum Yönetimi
249
+
250
+ ```typescript
251
+ // oturumun mevcut zorluğunu öğren
252
+ const difficulty = captcha.getSessionDifficulty('user-123'); // 'easy' | 'medium' | 'hard' | null
253
+
254
+ // belirli bir oturumu temizle (zorluk 'medium' olarak sıfırlanır)
255
+ captcha.clearSession('user-123');
256
+
257
+ // tüm oturumları temizle
258
+ captcha.clearAllSessions();
259
+ ```
260
+
261
+ #### Tüm CAPTCHA Türleriyle Uyumlu
262
+
263
+ ```typescript
264
+ // adaptif zorluk herhangi bir captcha türüyle çalışır
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
+ // ... ve diğerleri
269
+ ```
270
+
271
+ #### Express.js Örneği
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 Referansı
196
292
 
197
293
  ### Yapıcı Metod Seçenekleri
@@ -203,7 +299,8 @@ const isValid = captcha.validate(challenge, "ankara");
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; // opsiyonel, difficulty 'adaptive' iken gerekli
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
  ### Metotlar
226
324
 
227
- #### `generate(): CaptchaChallenge`
325
+ #### `generate(sessionId?: string): CaptchaChallenge`
228
326
 
229
327
  İstemciye gönderilmesi güvenli bir **public** nesne döndürür — `answer`, `hashedAnswer` ve `salt` çıkarılarak `nonce` ile anahtarlanmış şekilde sunucu tarafında saklanır.
230
328
 
329
+ `difficulty` `'adaptive'` olduğunda, `sessionId` parametresi kullanıcının mevcut zorluk seviyesini belirlemek için kullanılır. Constructor'da `sessionId` verilmişse varsayılan olarak kullanılır.
330
+
231
331
  ```typescript
232
332
  const challenge = captcha.generate();
233
333
  console.log(challenge.question); // kullanıcıya gösterilecek soru
@@ -239,10 +339,12 @@ console.log(challenge.category); // kategori adı (yalnızca type: 'emoji' içi
239
339
  // challenge.answer / .hashedAnswer / .salt — MEVCUT DEĞİL; istemciye hiç gönderilmez
240
340
  ```
241
341
 
242
- #### `validate(challenge: CaptchaChallenge, userInput: string): boolean`
342
+ #### `validate(challenge: CaptchaChallenge, userInput: string, sessionId?: string): boolean`
243
343
 
244
344
  Kullanıcı girdisini `challenge.nonce` üzerinden bulunan sunucu taraflı kayıtla karşılaştırır. Doğruysa `true`, yanlışsa `false` döndürür. Public challenge nesnesindeki `hashedAnswer` veya `salt` değiştirme girişimlerinin hiçbir etkisi yoktur.
245
345
 
346
+ `difficulty` `'adaptive'` olduğunda, doğrulama sonucu otomatik olarak oturuma kaydedilir ve zorluk buna göre ayarlanır.
347
+
246
348
  > **⚠️ Tek kullanımlık semantik:** `validate()`, **ilk çağrıda** — cevap doğru ya da yanlış olsun fark etmeksizin — nonce'u tüketir. Her doğrulama denemesinden sonra challenge geçersiz hale gelir. Kullanıcıya yeni bir challenge sunmadan önce mutlaka `generate()` yeniden çağrılmalıdır.
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
+ Oturumun mevcut adaptif zorluk seviyesini döndürür. Instance adaptif modda değilse `null` döndürür.
363
+
364
+ ```typescript
365
+ const difficulty = captcha.getSessionDifficulty('user-123');
366
+ // 'easy' | 'medium' | 'hard' | null
367
+ ```
368
+
369
+ #### `clearSession(sessionId: string): boolean`
370
+
371
+ Belirli bir adaptif oturumu temizler. Oturum mevcutsa `true`, değilse `false` döndürür.
372
+
373
+ ```typescript
374
+ captcha.clearSession('user-123');
375
+ ```
376
+
377
+ #### `clearAllSessions(): void`
378
+
379
+ Tüm adaptif oturumları temizler.
380
+
381
+ ```typescript
382
+ captcha.clearAllSessions();
383
+ ```
384
+
385
+ ### Dışa Aktarılan Yardımcılar
386
+
387
+ ```typescript
388
+ import K9Guard, { AdaptiveTracker, CustomQuestionValidator, CustomQuestionGenerator } from 'k9guard';
389
+ ```
390
+
391
+ | Export | Açıklama |
392
+ |--------|----------|
393
+ | `K9Guard` (varsayılan) | Ana CAPTCHA sınıfı |
394
+ | `AdaptiveTracker` | Bağımsız adaptif zorluk takipçisi (özel entegrasyonlar için) |
395
+ | `CustomQuestionValidator` | Özel soru dizilerini doğrula ve sanitize et |
396
+ | `CustomQuestionGenerator` | Özel soru havuzlarından üretim |
397
+
398
+ ### Tip Exportları
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
+ ## Testler
423
+
424
+ K9Guard, `bun:test` kullanarak 228+ test ile birim, entegrasyon, güvenlik, uç durum ve benchmark senaryolarını kapsar.
425
+
426
+ ### Testleri Çalıştırma
427
+
428
+ ```bash
429
+ # tüm testleri çalıştır
430
+ bun test
431
+
432
+ # izleme modunda çalıştır
433
+ bun run test:watch
434
+
435
+ # kapsam ile çalıştır
436
+ bun run test:coverage
437
+ ```
438
+
439
+ ### Test Kategorileri
440
+
441
+ | Kategori | Kapsam |
442
+ |----------|--------|
443
+ | **Birim** | Her modül bağımsız olarak doğru çıktılar ve uç durumlarla test edilir |
444
+ | **Entegrasyon** | Tüm 10 captcha türü + adaptif mod için tam generate-validate akışı |
445
+ | **Güvenlik** | Timing saldırısı direnci, nonce replay önleme, hash enjeksiyonu, girdi sanitizasyonu, SVG enjeksiyonu |
446
+ | **Uç Durumlar** | Sıfıra bölme, unicode karakterler, eş zamanlı üreteçler, geçersiz girdiler |
447
+ | **Benchmark** | Tüm captcha türleri için performans garantileri (generate < 5ms, validate < 5ms) |
448
+
258
449
  ## Katkıda Bulunma
259
450
 
260
451
  Katkılarınızı memnuniyetle karşılıyoruz! Nasıl yardımcı olabilirsiniz:
@@ -262,7 +453,7 @@ Katkılarınızı memnuniyetle karşılıyoruz! Nasıl yardımcı olabilirsiniz:
262
453
  1. **Depoyu fork edin**
263
454
  2. **Özellik dalı oluşturun**: `git checkout -b feature/harika-ozellik`
264
455
  3. **Değişiklikleriniz için testler ekleyin**
265
- 4. **Testleri çalıştırın**: `bun run src/test.ts`
456
+ 4. **Testleri çalıştırın**: `bun test`
266
457
  5. **Değişikliklerinizi commit edin**: `git commit -m 'feat: harika özellik eklendi'`
267
458
  6. **Dalınıza push edin**: `git push origin feature/harika-ozellik`
268
459
  7. **Pull Request oluşturun**
package/package.json CHANGED
@@ -1,11 +1,29 @@
1
1
  {
2
2
  "name": "k9guard",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A secure, lightweight, and flexible CAPTCHA module for TypeScript/JavaScript projects with cryptographic security and multi-language support",
5
- "main": "index.ts",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
6
8
  "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
7
21
  "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 1"
22
+ "build": "tsup",
23
+ "test": "bun test",
24
+ "test:watch": "bun test --watch",
25
+ "test:coverage": "bun test --coverage",
26
+ "prepublishOnly": "bun run build"
9
27
  },
10
28
  "keywords": [
11
29
  "captcha",
@@ -42,20 +60,24 @@
42
60
  "url": "https://github.com/K9Crypt/k9guard/issues"
43
61
  },
44
62
  "homepage": "https://github.com/K9Crypt/k9guard#readme",
63
+ "files": [
64
+ "dist/",
65
+ "README.md",
66
+ "LICENSE",
67
+ "docs/"
68
+ ],
45
69
  "engines": {
46
70
  "node": ">=16.0.0"
47
71
  },
48
72
  "devDependencies": {
49
- "@types/bun": "latest",
50
- "@types/node": "^20.19.33",
51
- "@typescript-eslint/eslint-plugin": "^6.21.0",
52
- "@typescript-eslint/parser": "^6.21.0",
53
- "eslint": "^8.57.1",
54
- "tsx": "^4.21.0",
55
- "typescript": "^5.9.3"
56
- },
57
- "peerDependencies": {
58
- "typescript": "^5"
73
+ "@types/bun": "^1.3.14",
74
+ "@types/node": "^25.9.1",
75
+ "@typescript-eslint/eslint-plugin": "^8.59.4",
76
+ "@typescript-eslint/parser": "^8.59.4",
77
+ "eslint": "^10.4.0",
78
+ "tsup": "^8.5.1",
79
+ "tsx": "^4.22.3",
80
+ "typescript": "^6.0.3"
59
81
  },
60
82
  "publishConfig": {
61
83
  "access": "public",
package/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export { K9Guard as default } from './src/K9Guard';
2
- export * from './src/types';
3
- export { CustomQuestionValidator } from './src/validators/customQuestionValidator';
4
- export { CustomQuestionGenerator } from './src/utils/customQuestionGenerator';
package/src/K9Guard.ts DELETED
@@ -1,97 +0,0 @@
1
- import type { K9GuardOptions, K9GuardCustomOptions, CaptchaChallenge, CustomQuestion } from './types';
2
- import { CaptchaGenerator } from './core/captchaGenerator';
3
- import { CaptchaValidator } from './core/captchaValidator';
4
- import { CustomQuestionValidator } from './validators/customQuestionValidator';
5
-
6
- export class K9Guard {
7
- private options: K9GuardOptions | K9GuardCustomOptions;
8
- private generator: CaptchaGenerator;
9
-
10
- constructor(options: K9GuardOptions | K9GuardCustomOptions | { type: 'custom'; questions: CustomQuestion[] }) {
11
- const processedOptions = this.processOptions(options);
12
- this.generator = new CaptchaGenerator(processedOptions);
13
- this.options = processedOptions;
14
- }
15
-
16
- private processOptions(options: unknown): K9GuardOptions | K9GuardCustomOptions {
17
- if (typeof options !== 'object' || options === null) {
18
- throw new Error('Options must be an object');
19
- }
20
-
21
- const opt = options as Record<string, unknown>;
22
-
23
- if (opt.type === 'custom') {
24
- if (!Array.isArray(opt.questions)) {
25
- throw new Error('Custom type requires questions array');
26
- }
27
-
28
- const validation = CustomQuestionValidator.validate(opt.questions);
29
- if (!validation.valid) {
30
- throw new Error(`Invalid custom questions: ${validation.error}`);
31
- }
32
-
33
- return {
34
- type: 'custom',
35
- questions: CustomQuestionValidator.sanitize(opt.questions as CustomQuestion[])
36
- } as K9GuardCustomOptions;
37
- }
38
-
39
- const validTypes = ['math', 'text', 'sequence', 'scramble', 'reverse', 'mixed', 'multi', 'image', 'emoji'] as const;
40
- if (!validTypes.includes(opt.type as any)) {
41
- throw new Error(`Invalid type. Must be one of: ${validTypes.join(', ')}`);
42
- }
43
-
44
- const validDifficulties = ['easy', 'medium', 'hard'] as const;
45
- if (!validDifficulties.includes(opt.difficulty as any)) {
46
- throw new Error(`Invalid difficulty. Must be one of: ${validDifficulties.join(', ')}`);
47
- }
48
-
49
- return {
50
- type: opt.type,
51
- difficulty: opt.difficulty
52
- } as K9GuardOptions;
53
- }
54
-
55
- generate(): CaptchaChallenge {
56
- return this.generator.generate();
57
- }
58
-
59
- validate(challenge: CaptchaChallenge, userInput: string): boolean {
60
- if (!this.isValidChallenge(challenge)) {
61
- return false;
62
- }
63
-
64
- if (typeof userInput !== 'string') {
65
- return false;
66
- }
67
-
68
- // consume() atomically removes the nonce from the store — single-use semantics.
69
- // hashedAnswer and salt come from the server-side store, never from the client,
70
- // which prevents hash-injection and replay attacks.
71
- const stored = this.generator.consume(challenge.nonce);
72
- if (!stored) {
73
- return false;
74
- }
75
-
76
- if (Date.now() > stored.expiry) {
77
- return false;
78
- }
79
-
80
- return CaptchaValidator.validate(stored, userInput);
81
- }
82
-
83
- private isValidChallenge(challenge: unknown): boolean {
84
- if (typeof challenge !== 'object' || challenge === null) {
85
- return false;
86
- }
87
-
88
- const c = challenge as Record<string, unknown>;
89
-
90
- return (
91
- typeof c.type === 'string' &&
92
- typeof c.question === 'string' &&
93
- typeof c.nonce === 'string' && c.nonce.length > 0 &&
94
- typeof c.expiry === 'number'
95
- );
96
- }
97
- }